[Firebase] إرسال الإشعارات عبر Cloud Functions وبدون سيرفر خارجي
ماهي Firebase Cloud Functions؟
هي خدمة قدمتها Google منذ بضعة أشهر ومازالت في المرحلة التجريبية BETA ,تساعدك Cloud Functions بتنفيذ أمور معينة عند حدوث أمر معين (عند الكتابة في قاعدة البيانات Firebase Realtime Database) . بعض الأمثلة:
إرسال الإشعارات
تعديل بعض النصوص على سبيل المثال (تغيير كلمة “Bad” الى “Not Good” بشكل أوتوماتيكي وبدون أي تدخل منك)
توليد الصور المصغرة “Thumbnails” بمساعدة Firebase Storage
والكثير من الأشياء الأخرى(ألقِ نظرة على الروابط في أسفل المقال)
نأتي الى الأمر المهم وهو كيف سنبدأ بتجهيز بيئة العمل و نبدأ بإرسال الإشعارات .
الFirebase Cloud Functions مبنية على لغة Javascript وعلى Framework من نوع NodeJS
ولهذا يجب علينا أن نقوم بتحميل NodeJS من هذا الرابط ,بعد تثبيته نقوم بفتح CMD او Terminal ونكتب الأمر التالي لتثبيت أدوات Firebase
npm install -g firebase-tools
عند نجاح التثبيت ستظهر بهذا الشكل
ثم نكتب الأمر لتسجيل الدخول بحساب Google واختيار مشروع Firebase
firebase login
بعد ذلك ستُفتح صفحة ويب وتطلب منك تسجيل الدخول
اختر Allow
تمت عملية تسجيل الدخول
نعود الى CMD ونقوم بإنشاء مجلد جديد وثم غير مسار CMD الى هذا المجلد (او إذا كنت على ويندوز فقم بفتح المجلد واضغط على زر Shift + زر الفأرة الأيمن واختر “Open Command Window Here” )
ثم اكتب الأمر لبدء تجهيز مشروع Cloud Functions
firebase init functions
الآن سيتم عرض كافة مشاريع Firebase الموجودة في حسابك,قم باختيار المشروع الذي تريد
ثم اختر Y
تم تجهيز الملفات
الآن اذا ذهبنا الى المجلد سنجد به بعض الملفات
نتوجه الى مجلد functions وسنجد داخله ملف index.js هذا هو الملف الذي سنقوم بكتابة Cloud Functions بداخله
قم بفتحه باستخدام أي محرر أكواد,سأستخدم VSCode يمكنك تحميله من هنا
سنجد هذه الأكواد ,نقوم بمسحها
سنقوم في مثالنا هذا إرسال إشعار للمستخدم عندما يقوم أحد بمتابعته(بنفس فكرة موقع تويتر) وسنعرض إسم الشخص الذي قام بمتابعته بالإضافة الى صورته في الإشعار
ولنفترض أنه لديك مثل هذا الترتيب في قاعدة البيانات Realtime Database
جدول users يحتوي على جميع المستخدمين ونسمي كل مستخدم بناء على UID الخاص بFirebase Auth .
وداخل كل مستخدم نضع التالي:
- notificationTokens
- photo رابط صورة المستخدم
- userName اسم المستخدم
سنقوم بإنشاء جدول نسميه followers والذي سيحتوي على الأشخاص الذين قاموا بمتابعة هذا المستخدم
حان وقت العمل , فلنكتب بعض الأكواد
ولكن قبل هذا سأذكرك بأن Firebase Cloud Fucntions تعمل على مبدأ تنفيذ أمر معين عند حدوث فعل(كتابة على Firebase Realtime Database مثلاً)
//Cloud Functions Modules
const functions = require('firebase-functions');
//Firebase Admin SDK Modules (it will send the Notifications to the user)
const admin = require('firebase-admin');
//init Admin SDK
admin.initializeApp(functions.config().firebase);
السطر الثاني نقوم بتعريف او عمل import لمكتبة Firebase Cloud Functions
السطر الرابع نقوم بتعريف مكتبة Firebase Admin وهي المسؤولة عن إرسال الإشعارات والكتابة في قاعدة البيانات ثم نقوم بتهيئة مكتبة Admin في السطر الأخير
exports.sendNotificationOnNewFollow = functions.database.ref('/followers/{userId}/{followerId}/').onWrite(event => {
}
بعد ذلك نقوم بتعريف Cloud Function عبر exports.sendNotificationOnNewFollow
(sendNotificationOnNewFollow هو اسم الFunction يمكنك تسميته كما تشاء) ونجعلها تستمع الى الأحداث في جدول followers
مايكتب بين هذين القوسين{} عند تعريف Cloud Function يسمى Wildcard وببساطة يعني أننا نريد الإستماع الى الأحداث داخل جدول followers داخل userId داخل followerId ,كما أنها أيضاً تعيد لنا قيمة الحدث (كuserId و followerId)
وقد يحتوي على أي قيمة
كما أنه يمكنك تسميتهم بأي إسم تريد
الصورة التالية ستوضح لك الفكرة
.onWrite أي أنه عندما يتم الكتابة وهي تعيد لنا حدث event والذي يحتوي على الأمور التى كتبت او تغيرت في Realtime Database
const userId = event.params.userId;
const followerId = event.params.followerId
هنا قمنا بالحصول على userId و followerId عن طريق event.params
يجب عليك أن تكتب نفس الأسماء التى في البارامترز
سنقوم بكتابة هذا السطر
if (!event.data.exists()) {
return;
}
وهذا يعني أنه اذا كانت لاتتوفر بيانات(تم عمل متابعة ثم الغاؤها فوراً) عندها قم بعمل return ولا تنفذ أي شيئ آخر
ثم نقوم بتعريف ميثود getDeviceTokensPromise مهمتها جلب الnotificationTokens الخاصة بالمستخدم الذي تمت متابعته(“Ahmad”)
const getDeviceTokensPromise = admin.database().ref(`users/${userId}/notificationTokens/`).once('value');
لاحظ أنه تم استخدام userId الذي عرفناه سابقاً
ثم بنفس الطريقة نعرف ميثود أخرى تقوم بجلب اسم المستخدم وصورته
const getFollowerInfo = admin.database().ref(`users/${followerId}/`).once('value');
هذه المرة قمنا بجلب البيانات الخاصة بناءً على followerId وهو (“Sobhe”)
الآن سنستدعي هذه الميثودز
في Cloud Functions يجب علينا دائما أن نعود ب Promise وهو الذي سيقول ل Cloud Functions أنه قد انتهى من التنفيذ
return Promise.all([getDeviceTokensPromise, getFollowerInfo]).then(results => {
const tokensSnapshot = results[0];
const followerSnapshot = results[1];
}
داخل Promise نعطيه الميثودز الذي أنشأناها ونضعها داخل مصفوفة Array.
.then هذه تعني عندما ينتهي من تنفيذ الميثوذز وهي تعود لنا ب Snapshot أسميناها results
وبما أننا قد نفذنا أكثر من ميثود فقمنا بتعريف كل Snapshot على حدى وأعطيناها المكان من Array
ثم سنتأكد من أنه يوجد tokens أم لا عبر السطر
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
واذا لم يوجد سيقوم بطبع رسالة وسيتجاهل ماتبقى من الكود
بعد ذلك نقوم بأخذ userName و photo من followerSnapshot ونقوم بطباعتهم في log
const followerName = followerSnapshot.val().userName;
const followerPhoto = followerSnapshot.val().photo;
console.log('Follower Name is: ', followerName);
console.log('Follower Photo is: ', followerPhoto);
ثم نقوم بتعريف Payload وهو الإشعار الذي سنستقبله في تطبيق Android او IOS او Web (سنشرح بشكل بسيط عن طريق الأندرويد)
وهو يحتوي على:
- title عنوان الإشعار
- body مانريد كتابته داخل الإشعار(التفاصيل)
- imgUrl صورة المستخدم (“Sobhe”)
// Notification details.
const payload = {
data: {
title: 'you have a New Follower',
body: `${followerName} has followed You.`,
imgUrl: `${followerPhoto}`
}
};
ثم نقوم بأخذ notificationTokens من tokensSnapshot وبما أنه قد يحتوي على أكثر من عنصر قمنا بوضعهم في Array
const tokens = Object.keys(tokensSnapshot.val());
أخيراً نقوم بإرسال الإشعار عبر messaging وهي تأخذ 2 بارامتر tokens و payload
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
})
وتعود لنا ب response
ثم نقوم بتعريف مصفوفة Array فارغة,سنملؤها لاحقا بNotification Tokens الغير صالحة لنقوم بحذفهم
const tokensToRemove = [];
وأخيراً نقوم بكتابة هذه الأكواد
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
إذا كان هنالك خطأ سنقوم بطباعته في Log ثم نعود ب Promise لحذف الTokens الغير صالحة في حال وجودهم
ليصبح الكود الكامل كالتالي
//Cloud Functions Modules
const functions = require('firebase-functions');
//Firebase Admin SDK Modules (it will send the Notifications to the user)
const admin = require('firebase-admin');
//init Admin SDK
admin.initializeApp(functions.config().firebase);
exports.sendNotificationOnNewFollow = functions.database.ref('/followers/{userId}/{followerId}/').onWrite(event => {
const userId = event.params.userId;
const followerId = event.params.followerId
// If un-follow we exit the function.
if (!event.data.exists()) {
return;
}
// Get the list of device notification tokens.
const getDeviceTokensPromise = admin.database().ref(`users/${userId}/notificationTokens/`).once('value');
// Get the follower Info.
const getFollowerInfo = admin.database().ref(`users/${followerId}/`).once('value');
//Execute the Functions
return Promise.all([getDeviceTokensPromise, getFollowerInfo]).then(results => {
const tokensSnapshot = results[0];
const followerSnapshot = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
const followerName = followerSnapshot.val().userName;
const followerPhoto = followerSnapshot.val().photo;
console.log('Follower Name is: ', followerName);
console.log('Follower Photo is: ', followerPhoto);
// Notification details.
const payload = {
data: {
title: 'you have a New Follower',
body: `${followerName} has followed You.`,
imgUrl: `${followerPhoto}`
}
};
// Listing all tokens.
const tokens = Object.keys(tokensSnapshot.val());
// Send notifications to all tokens.
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.ref.child(tokens[index]).remove());
}
}
});
return Promise.all(tokensToRemove);
});
});
});
نقوم بحفظ الملف Ctrl + S ونعود مرة أخرى الى CMD ونتوجه الى مجلد المشروع
ونكتب الأمر
firebase deploy --only functions
سيتم بدء رفع الملفات الى Firebase وقد تأخذ العملية بعض الوقت
عند الإنتهاء نذهب الى Firebase Console الى Functions وستجد ظهور Cloud Function الذي أنشأتها
سنتوجه الآن الى Android Studio بشكل سريع لترى كيف يتم تنفيذ الإشعار (يمكنك تحميل السورس كود وتعديله كما تشاء)
نقوم بإنشاء كلاس جديد نسميه MyFCMService والذي سيقوم بتلقى الإشعارات من Cloud Functions ونجعله extends FirebaseMessaginService ولاننسَ أن نقوم بتشغيله من MainActivity
ثم نقوم بعمل Override لميثود onMessageReceived والتى تعود لنا ب remoteMessage
ثم نقوم بتعريف title و body و imgUrl ونلاحظ أنه يجب علينا كتابة نفس الإسم المكتوب في Payload الذي كتبناه في Cloud Functions
ثم استدعينا ميثود سنقوم بإنشاءها وهي sendNotification وتأخذ المتغيرات الثلاثة ك بارامترز
ثم نقوم بتعريف هذه الميثود,وهي ميثود بسيطة تقوم بأخذ البارامترز وعرضهم في Notification
ونلاحظ أنه قد وضعنا صورة الشخص عبر setLargeIcon وقمنا باستدعاء ميثود getProfilePhotoAsBitmap
والتي تأخذ imgUrl ك بارامتر
private void sendNotification(String title, String messageBody, String imgUrl) {
Intent intent = new Intent(this, MyFCMService.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* FRequest code */, intent,
PendingIntent.FLAG_ONE_SHOT);
Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this)
.setSmallIcon(android.R.drawable.star_on)
.setLargeIcon(getProfilePhotoAsBitmap(imgUrl))
.setContentTitle(title)
.setContentText(messageBody)
.setAutoCancel(true)
.setSound(defaultSoundUri)
.setContentIntent(pendingIntent);
NotificationManager notificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
}
أخيراً نقوم بإنشاء الميثود getProfilePhotoAsBitmap وهي ميثود تقوم بأخذ الرابط وتقوم بتحميل الصورة وإرجاعها ك Bitmap لعرضها في Notification,ولهذا قمنا بالإستعانة بمكتبة Glide المختصة بعرض الصور
private Bitmap getProfilePhotoAsBitmap(String url) {
Bitmap bitmap = null;
try {
bitmap = Glide.with(this).load(url).asBitmap().into(168, 168).get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
return bitmap;
}
حان وقت التجربة :]
نقوم بتشغيل تطبيق الأندرويد ثم نجعل “Sobhe” يعمل Follow ل “Ahmad” (يمكنك ادخالهم بشكل يدوي من Firebase Console كما فعلت على سبيل التجربة!)
ملاحظة:
قد تم إرفاق ملف fcm-cloud-functions-export-Data Structure وهو ملف يحتوي على قاعدة البيانات البسيطة الذي أنشأناها,يمكنك استيرادها من Realtime Database عبر خيار Import Backup (لا تنسَ عمل نسخ احتياطي لقاعدة البيانات لديك قبل تنفيذ عملية الاستيراد)
بعض المصادر التي قد تهمك
التعليقات (5)
أكيد ,وسائل التواصل موجودة في حسابي على عالم البرمجة
السلام عليكم اخوي احصل عندك رابط مشروع لكن للـ IOS ؟
هل استطيع إرسال notification من خلال ال (uid) الخاصة بالمستخدم؟
لأني مش عارف اجيب ال token لأن اتطبيق لدي لا يقوم المستخدم ب إنشاء حساب , بل يتم إنشاء الحسابات عن طريق الادمن فقط
ارجو المساعدة, وشكرا
good
لايوجد لديك حساب في عالم البرمجة؟
تحب تنضم لعالم البرمجة؟ وتنشئ عالمك الخاص، تنشر المقالات، الدورات، تشارك المبرمجين وتساعد الآخرين، اشترك الآن بخطوات يسيرة !