Cloud Functions — الدوال القابلة للاستدعاء من Flutter
Cloud Functions — الدوال القابلة للاستدعاء من Flutter
تتيح لك Firebase Cloud Functions تشغيل منطق آمن على جانب الخادم دون إدارة البنية التحتية. دوال HTTPS القابلة للاستدعاء (Callable) هي نوع خاص يمكن استدعاؤه مباشرةً من تطبيق Flutter باستخدام SDK مكتوبة بأنواع محددة — فهي تتولى تلقائياً سياق المصادقة والتسلسل ونشر الأخطاء البنيوية. في هذا الدرس ستنشر دالة Node.js قابلة للاستدعاء وتستدعيها من Dart.
ما الذي يجعل الدوال القابلة للاستدعاء مختلفة؟
على عكس دوال HTTPS العادية التي تُطلَق عبر رابط URL، تتواصل الدوال القابلة للاستدعاء عبر Firebase Functions SDK. يمنحك هذا عدة مزايا:
- يُضمّن SDK تلقائياً رمز المعرّف (ID token) للمستخدم المسجل دخوله، لذا يكون
context.authمتاحاً دائماً على الخادم. - الأخطاء المُرمية كـ
HttpsErrorعلى الخادم تظهر كـFirebaseFunctionsExceptionمكتوبة بنوع محدد على العميل. - لا حاجة لترميز JSON يدوياً — تمرر
MapوتتلقىMap. - تعمل بسلاسة مع Firebase Local Emulator Suite للتطوير المحلي.
firebase_functions و Node.js 18+ (أو 20+) على جانب الخادم. يُنشر وقت تشغيل الخادم عبر Firebase CLI.الخطوة 1 — كتابة دالة Node.js القابلة للاستدعاء
داخل ملف functions/index.js (أو src/index.ts) لمشروع Firebase، عرّف الدالة القابلة للاستدعاء باستخدام onCall:
functions/index.js — الدالة القابلة للاستدعاء على جانب الخادم
// Node.js 18+ / Firebase Functions v2
const { onCall, HttpsError } = require('firebase-functions/v2/https');
const { getMessaging } = require('firebase-admin/messaging');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendWelcomeNotification = onCall(async (request) => {
// 1. فرض المصادقة
if (!request.auth) {
throw new HttpsError(
'unauthenticated',
'You must be signed in to call this function.'
);
}
const { deviceToken, userName } = request.data;
// 2. التحقق من صحة المدخلات
if (!deviceToken || typeof deviceToken !== 'string') {
throw new HttpsError('invalid-argument', 'deviceToken is required.');
}
// 3. تنفيذ المنطق على جانب الخادم (إرسال إشعار FCM)
const message = {
token: deviceToken,
notification: {
title: `Welcome, ${userName ?? 'Friend'}!`,
body: 'Thanks for joining. Explore the app now.',
},
data: { screen: 'home' },
};
const messageId = await getMessaging().send(message);
// 4. إرجاع استجابة مكتوبة بنوع محدد
return { success: true, messageId };
});
انشر الدالة باستخدام Firebase CLI:
الطرفية — النشر على Firebase
# من جذر مشروع Firebase
firebase deploy --only functions:sendWelcomeNotification
الخطوة 2 — إضافة اعتماد Flutter
أضف firebase_functions إلى pubspec.yaml:
pubspec.yaml
dependencies:
firebase_core: ^3.6.0
firebase_auth: ^5.3.0
firebase_functions: ^5.1.0
الخطوة 3 — استدعاء الدالة القابلة للاستدعاء من Flutter
استخدم FirebaseFunctions.instance.httpsCallable() للحصول على مرجع، ثم استدعه بخريطة بيانات. استخدم دائماً await داخل try/catch للتعامل مع FirebaseFunctionsException:
lib/services/functions_service.dart
import 'package:cloud_functions/cloud_functions.dart';
class FunctionsService {
final FirebaseFunctions _functions = FirebaseFunctions.instance;
/// تستدعي Cloud Function باسم [sendWelcomeNotification].
/// تُرجع messageId عند النجاح أو ترمي [FirebaseFunctionsException].
Future<String> sendWelcomeNotification({
required String deviceToken,
required String userName,
}) async {
final callable = _functions.httpsCallable('sendWelcomeNotification');
try {
final HttpsCallableResult result = await callable.call<Map<String, dynamic>>({
'deviceToken': deviceToken,
'userName': userName,
});
final data = Map<String, dynamic>.from(result.data as Map);
return data['messageId'] as String;
} on FirebaseFunctionsException catch (e) {
// خطأ مكتوب بنوع محدد من HttpsError المرمية على الخادم
throw Exception('[${e.code}] ${e.message}');
}
}
}
الخطوة 4 — استدعاء الخدمة من ودجت
lib/screens/home_screen.dart — تشغيل الدالة
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool _loading = false;
String? _result;
Future<void> _triggerNotification() async {
setState(() { _loading = true; _result = null; });
try {
final service = FunctionsService();
final messageId = await service.sendWelcomeNotification(
deviceToken: 'DEVICE_FCM_TOKEN_HERE',
userName: 'Edrees',
);
setState(() { _result = 'Sent! Message ID: $messageId'; });
} catch (e) {
setState(() { _result = 'Error: $e'; });
} finally {
setState(() { _loading = false; });
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Cloud Functions Demo')),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: _loading ? null : _triggerNotification,
child: _loading
? const CircularProgressIndicator()
: const Text('Send Welcome Notification'),
),
if (_result != null) ...[
const SizedBox(height: 16),
Text(_result!, textAlign: TextAlign.center),
],
],
),
),
);
}
}
استخدام المحاكي المحلي
أثناء التطوير، وجّه SDK إلى محاكي Functions المحلي بدلاً من بيئة الإنتاج:
main.dart — الاتصال بالمحاكي
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// فقط في وضع التصحيح / المحاكي
if (kDebugMode) {
FirebaseFunctions.instance.useFunctionsEmulator('localhost', 5001);
}
runApp(const MyApp());
}
firebase emulators:start --only functions واضبط useFunctionsEmulator حتى تتمكن من التكرار دون نشر. التغييرات على كود Node.js تصبح سارية بعد إعادة تشغيل المحاكي.مرجع معالجة الأخطاء
يرمي الخادم HttpsError بسلسلة رمز حالة. على العميل، يُعيّن FirebaseFunctionsException.code إلى نفس السلسلة:
- unauthenticated — المستدعي غير مسجل الدخول
- permission-denied — المستدعي يفتقر إلى الدور/المطالبة المطلوبة
- invalid-argument — بيانات مدخلة مشوّهة
- not-found — المورد المطلوب غير موجود
- internal — استثناء غير معالَج على جانب الخادم
Error خاماً من دالة قابلة للاستدعاء — ستظهر كـ internal بدون رسالة. استخدم دائماً new HttpsError(code, message) لمنح العميل معلومات خطأ قابلة للتنفيذ.الملخص
تربط الدوال القابلة للاستدعاء Flutter بوقت تشغيل جانب الخادم في Firebase بشكل نظيف: يتولى SDK حقن رمز المصادقة والتسلسل ونشر الأخطاء المكتوبة بأنواع محددة. انشر بـ firebase deploy --only functions، استدعِ بـ httpsCallable().call(data)، وامسك FirebaseFunctionsException للتعامل البنيوي مع الأخطاء. استخدم المحاكي المحلي أثناء التطوير للتكرار بسرعة دون تكبّد تكاليف استدعاء Cloud Functions.