Push-уведомления с использованием Firebase, Node.js, Flutter/Dart
Если вы начинающий разработчик или впервые знакомитесь с этой концепцией, то наверняка задавались вопросом: "Как обеспечить удаленное взаимодействие двух или более устройств в режиме реального времени?". Если вы очень умный человек, то, возможно, вы сами придумали несколько решений. Ваши решения могут заключаться в следующем:
Использование потока: Если вы знакомы с концепцией потоковой передачи данных в программировании, вы можете подумать, что это будет решением вашей проблемы. Все, что вам нужно сделать, - это сохранить данные в базе данных и прослушивать их наличие на устройстве получателя.
Использование веб-сокетов: веб-сокеты используются в большинстве коммуникаций реального времени, таких как каналы обмена текстовыми, аудио- и видеосообщениями. Так что, возможно, этот вариант окажется для вас очень надежным.
Можно даже придумать какую-нибудь грубую технику, которая будет заключаться в периодическом запросе к API внутри приложения для получения новых данных. Хотя все эти способы обмена данными в реальном времени являются допустимыми и стандартными для отрасли, их применение зависит от конкретного случая использования в приложении.
Если данные могут передаваться между устройствами описанным выше способом, то что же тогда представляют собой push-уведомления? Почему они являются критически важной функцией в современных приложениях? Как можно реализовать push-уведомления в приложении? В этой статье мы дадим чёткие ответы на эти вопросы.
Что такое Push-уведомление?
Большинство мобильных приложений работает под управлением операционной системы Android или iOS. Каждое из устройств, работающих под управлением этих операционных систем, имеет уникальный идентификатор. Этот идентификатор принято называть маркером устройства. В каждой операционной системе есть служба, которая может удаленно взаимодействовать с каждым устройством, работающим под управлением этой операционной системы. В iOS таким сервисом является сервис Apple Push Notification (APNs), а в Android - сервис Firebase Cloud Messaging (FCM).
Push-уведомления включаются в приложении, когда приложение отправляет данные (которые также включают токен устройства, на которое направлено приложение) в службу push-уведомлений (APN или FCM). Служба push-уведомлений, в свою очередь, отправляет данные на конкретное устройство, чтобы вызвать на нем отображение уведомления независимо от того, запущено ли приложение на устройстве или нет.
Отметим, что push-уведомления могут быть реализованы и в других операционных системах, помимо Android и iOS, но в контексте данной статьи мы остановимся только на использовании push-уведомлений в Android и iOS.
Зачем использовать Push-уведомления в современных приложениях?
Push-уведомления стали очень важной функцией для большинства мобильных и веб-приложений. Почему это так? Вот некоторые причины:
Оповещение: Как и следует из названия, push-уведомление уведомляет или предупреждает пользователя приложения о входящих или уже полученных данных.
Маркетинг: push-уведомления используются в качестве маркетингового инструмента для информирования пользователей приложения о предлагаемых услугах или других рекламных сообщениях.
Удержание пользователей: Некоторые пользователи склонны забывать о приложении, которым они редко пользуются. Стратегические оповещения от таких приложений помогают пользователю вернуться к приложению.
Вовлечение: Приложения для социальных сетей и другие "ищущие вовлеченности" приложения могут использовать возможности push-уведомлений для повышения вовлеченности в приложение и привлечения пользователя к работе с приложением.
Одним из интересных моментов в push-уведомлениях является то, что целевой пользователь может быть зарегистрированным пользователем приложения, а может и не быть. Если приложение работает на устройстве пользователя и пользователь включил функцию push-уведомлений, то информация может быть передана на это устройство. Кроме того, для получения push-уведомления не требуется, чтобы приложение было активно открыто (или использовалось). Это одна из наиболее весомых причин использования push-уведомлений. Различные методы удаленного обмена данными, о которых говорилось ранее, требуют активного использования приложения для успешной работы. Например, я не узнаю о получении сообщения в чате Whatsapp, если у меня не открыт Whatsapp. Но с помощью push-уведомления я могу получить уведомление о получении сообщения Whatsapp (даже если телефон находится в режиме ожидания), прежде чем открою Whatsapp, чтобы просмотреть содержимое сообщения или продолжить общение с отправителем. Таким образом, push-уведомление можно использовать в сочетании с другими методами обмена данными в приложении.
Как реализовать push-уведомления в приложении?
Push-уведомление может быть программно реализовано в приложении. Для этого приложению необходимо обратиться к службе push-уведомлений, чтобы служба push-уведомлений могла обратиться к целевому устройству. Мы рассмотрим реализацию push-уведомления как на бэкенде, так и на фронтенде. Push-уведомление может быть реализовано только на фронтенде без какой-либо поддержки со стороны бэкенда, но это не самая лучшая практика.
Необходимые условия
Прежде чем мы сможем полностью приступить к работе с программной реализацией push-уведомлений, необходимо настроить учетную запись firebase. Если у вас нет учетной записи firebase, вы можете создать ее здесь. Учетная запись firebase - это то же самое, что и учетная запись google. Поэтому если у вас есть учетная запись google, вы можете приступать к работе.
Теперь, когда у вас есть учетная запись google, вы можете перейти к консоли firebase. В консоли Firebase необходимо создать новый проект или использовать существующий проект (если он уже есть).
Нажмите кнопку Add Project (Добавить проект) и заполните форму для создания нового проекта. Убедитесь, что все данные введены точно. После заполнения каждой формы нажмите кнопку Continue (Продолжить) и перейдите к созданию проекта.
После создания проекта можно загрузить json-файл учетной записи сервиса. Для этого перейдите в консоль firebase, в консоли щелкните your_project_name
> project overview settings(icon)
> project settings
> service accounts
> generate new private key
> generate key
.
Обратите внимание, что вы должны загрузить этот ключ и хранить его в надежном месте. Вы не сможете извлечь этот ключ, а если он будет потерян, вам придется сгенерировать другой ключ. Создание нескольких ключей не имеет серьезных последствий, ваше приложение будет работать нормально.
Внутренняя реализация push-уведомлений
Мы рассмотрим реализацию push-уведомлений на бэкенде с помощью node.js. Если вы знакомы с JavaScript, это будет легко сделать. Нам потребуется добавить в проект один крупный пакет:
- Firebase admin: Firebase admin - это пакет ядра firebase, который помогает нам работать со многими службами firebase. Одним из необходимых нам сервисов firebase является сервис обмена сообщениями firebase. Обратите внимание, что ранее мы уже рассказывали о том, что Firebase Cloud messaging - это служба push-уведомлений для устройств Android, и у вас может возникнуть вопрос: "А если я хочу отправлять push-уведомления на устройства iOS, нужен ли мне другой пакет?". - Нет, не нужен! Пакет firebase messaging поможет нам в работе с push-уведомлениями как для Android, так и для iOS. Он будет пинговать сервисы push-уведомлений для Android и iOS и доставлять уведомление на соответствующее устройство. После установки firebase admin настройте его в своем проекте. Конфигурация может выглядеть следующим образом:
// in your main.js or app.js file
const admin = require('firebase-admin');
// download your service account credential from firebase
// and add it to a config folder in your project
const serviceAccount = require('../config/serviceAccountKey.json');
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: 'https://yourFirebaseProjectName.firebaseio.com',
});
К этому моменту я хочу предположить, что вы настроили свой проект node.js и готовы к интеграции push-уведомлений. Я не буду вдаваться в подробности других настроек проекта, а сосредоточусь только на функции push-уведомлений. Теперь давайте приступим к выполнению следующих шагов:
- Напишите функцию отправки уведомления: Эта функция будет асинхронной и потребует таких параметров, как идентификатор пользователя, url изображения (для уведомления с изображением), заголовок, тело, идентификатор ресурса. Идентификатор ресурса - это идентификатор базы данных информации, которую вы хотите отправить. Например, если вы отправляете уведомление о сообщении, то идентификатором ресурса будет идентификатор сообщения. Идентификатор ресурса важен потому, что при открытии уведомления может потребоваться выполнить какой-либо запрос к внешнему интерфейсу. Этот запрос может быть выполнен с использованием идентификатора ресурса или другого параметра, который используется при запросе данных в собственном бэкенде. Тело - это более длинный текст, который будет показан пользователю при раскрытии уведомления. Обратите внимание, что все эти параметры, кроме заголовка, являются необязательными.
//this is your service.js file or some file where you want to handle push notification
const admin = require('firebase-admin');
async sendPushNotification(title, body, userId, imageUrl, resourceId){}
- Запросите идентификатор пользователя, чтобы получить дополнительные данные о пользователе для уведомления: Для отправки push-уведомления нам необходима дополнительная информация. Нам нужны такие данные, как токен устройства и количество значков. Количество значков не является обязательным параметром. Он будет важен только для устройств на базе iOS. Значок используется для обозначения количества непрочитанных уведомлений для пользователя. Токен - это токен устройства. Он может быть получен с самого устройства и сохранен в коллекции пользователей в базе данных. Когда мы будем обсуждать фронтенд-реализацию push-уведомлений, мы поговорим о том, как получить токен устройства. Отметим, что в рамках данной статьи значок и токен устройства будут храниться в коллекции пользователей. В некоторых случаях эти данные могут храниться в собственной коллекции вместе с другой информацией. Выбор полностью зависит от вас.
//this is your service.js file or some file where you want to handle push notification
const admin = require('firebase-admin');
async sendPushNotification(title, body, userId, imageUrl, resourceId){
// the function below is a db call. replace with your respective db query
const user = await findUserById(userId);
const token = user.token;
let badge = user.badge ?? 0; // if user.badge is null badge is assigned a 0 value
if(token === null || token === undefined){
throw new Error('this is an invalid token');
}
badge++; // increament the badge.
}
- Подготовьте объект сообщения push-уведомления: Если обратиться к официальной документации firebase messaging, то можно заметить, что объект сообщения имеет тип
TokenMessage | TopicMessage | ConditionMessage
. И все три типа расширяются от типаBaseMessage
. Вы можете обратиться к официальной документации Firebase, чтобы узнать все возможные параметры объекта Firebase. Подготовим сценарий использования нашего уведомления.
//this is your service.js file or some file where you want to handle push notification
const admin = require('firebase-admin');
async sendPushNotification(title, body, userId, imageUrl, resourceId){
// the function below is a db call. replace with your respective db query
const user = await findUserById(userId);
const token = user.token;
let badge = user.badge ?? 0; // if user.badge is null badge is assigned a 0 value
if(token === null || token === undefined || token.length < 2){
throw new Error('this is an invalid token');
}
badge++
const message = {
notification: {
title,
body,
},
android: {
notification: {
channel_id: 'MESSAGE_CHANNEL',// *
icon: 'message_icon', // *
tag: 'message', // *
image: imageUrl,
},
},
apns: {
payload: {
aps: {
badge,
sound: 'chime.caf',
},
},
},
data: {
click_action: 'FLUTTER_NOTIFICATION_CLICK', // *
type: 'MESSAGE', // *
resourceId,
},
token,
};
}
Примечание: Все параметры в приведенном выше фрагменте кода с символом "*" могут быть изменены в соответствии с требованиями вашего приложения. Подробнее о каждом параметре можно прочитать в документации Firebase.
- Отправьте push-уведомление: Используйте пакет администрирования firebase для отправки push-уведомления, как показано ниже.
//this is your service.js file or some file where you want to handle push notification
const admin = require('firebase-admin');
async sendPushNotification(title, body, userId, imageUrl, resourceId){
// the function below is a db call. replace with your respective db query
const user = await findUserById(userId);
const token = user.token;
let badge = user.badge ?? 0; // if user.badge is null badge is assigned a 0 value
if(token === null || token === undefined || token.length < 2){
throw new Error('this is an invalid token');
}
badge++
const message = {
notification: {
title,
body,
},
android: {
notification: {
channel_id: 'MESSAGE_CHANNEL',// *
icon: 'message_icon', // *
tag: 'message', // *
image: imageUrl,
},
},
apns: {
payload: {
aps: {
badge,
sound: 'chime.caf',
},
},
},
data: {
click_action: 'FLUTTER_NOTIFICATION_CLICK', // *
type: 'MESSAGE', // *
resourceId,
},
token,
};
await admin.messaging().send(message);
}
- Окончательная проверка: Здесь, возможно, потребуется обновить количество значков в коллекции пользователя и выполнить некоторую работу над ошибками.
//this is your service.js file or some file where you want to handle push notification
const admin = require('firebase-admin');
async sendPushNotification(title, body, userId, imageUrl, resourceId){
// the function below is a db call. replace with your respective db query
const user = await findUserById(userId);
const token = user.token;
let badge = user.badge ?? 0; // if user.badge is null badge is assigned a 0 value
if(token === null || token === undefined || token.length < 2){
throw new Error('this is an invalid token');
}
badge++
const message = {
notification: {
title,
body,
},
android: {
notification: {
channel_id: 'MESSAGE_CHANNEL',// *
icon: 'message_icon', // *
tag: 'message', // *
image: imageUrl,
},
},
apns: {
payload: {
aps: {
badge,
sound: 'chime.caf',
},
},
},
data: {
click_action: 'FLUTTER_NOTIFICATION_CLICK', // *
type: 'MESSAGE', // *
resourceId,
},
token,
};
await admin.messaging().send(message);
// update user badge count.
// this is a db call. replace this method with your appropraite db update method
await user.update({badge});
}
Важно использовать try-catch
в теле функции. Это позволит правильно обрабатывать ошибки, не нарушая работу сервера. Одна из ошибок, которую можно обработать, - это недействительный токен устройства. Возможно, потребуется удалить токен из хранилища в базе данных. Это поможет впоследствии обратиться к фронтенду для получения точного токена устройства.
- Напишите еще одну функцию для сброса количества значков: После доставки уведомления пользователь нажимает на уведомление, чтобы просмотреть его. На фронтенде нам необходимо сбросить счетчик значков в базе данных, чтобы получить нулевое значение. Это поможет нам управлять просмотренными уведомлениями. Этот счетчик значков в основном актуален для приложений на iOS.
async resetBadgeCount(userId){
// this is a db call, replace with appropriate query to fetch user from db
const user = await findUserById(userId);
// this is a db update method. update the badge property of the user to 0
await user.update({badge: 0});
}
Эти функции push-уведомлений, написанные нами для бэкенда, могут вызываться в других сервисных функциях приложения, либо могут быть переданы в API. Если они подключены к API, это означает, что push-уведомления могут быть вызваны через вызов API.
Функцию resetBadgeCount
можно вынести в API, поскольку она будет вызываться на фронтенде, когда пользователь нажмет на уведомление. Вы можете настроить API самостоятельно.
Теперь, когда мы завершили внутреннюю реализацию функции push-уведомлений, пришло время сосредоточиться на её внешней реализации в вашем приложении.
Фронтенд-реализация push-уведомлений
Поскольку мы обсуждали push-уведомления в мобильных приложениях, нам необходимо рассмотреть различные подходы к фронтенду для push-уведомлений. Либо мы будем использовать нативный язык, такой как Java или Swift, либо гибридный язык, такой как Flutter и React native. В этой статье мы остановимся только на Flutter. Если вы обратите внимание на нашу реализацию бэкенда, то увидите FLUTTER_NOTIFICATION_CLICK
в качестве одного из значений объекта сообщения push-уведомления. Это связано с тем, что мы используем Flutter для фронт-энда. Пожалуйста, следуйте документации Firebase по настройке клиента. Это очень простой процесс.
Итак, вот следующие шаги для реализации фронтенда в Flutter:
- Установите пакеты Flutter: Вам потребуется установить следующие пакеты:
- Firebase core: Прежде чем использовать Firebase, необходимо установить в приложение пакет firebase core.
- Firebase Messaging: Это основной пакет, необходимый нам для работы функции push-уведомлений.
- Flutter Local Notification: Этот пакет поможет настроить фронтенд на получение push-уведомлений нужным образом. Он работает в связке с пакетом Firebase messaging.
- Flutter App Badger: Этот пакет будет использоваться для сброса значка уведомлений в приложениях для iOS.
- Добавьте новое приложение firebase: В учетной записи firebase перейдите в настройки проекта и добавьте новое приложение. Выберите значок iOS, чтобы добавить приложение для iOS.
- Загрузите файл
info.plist
: ФайлGoogleService.info.plist
автоматически генерируется внутри проекта после создания приложения для iOS. Загрузите этот файл. Он понадобится вам в вашем проекте. - Добавьте в проект файл
info.plist
: Откройте папку ios каталога проекта в Xcode. Перетащите загруженный файл во вложенную папку Runner. В появившемся диалоговом окне убедитесь, что в свойстве Destination установлен флажок Copy items if needed, а в поле Add to targets выбран Runner. Затем нажмите кнопку Finish. - Загрузите файл
google-services.json
: Скачайте файлgoogle-services.json
, перетащите его в папкуyour_project_directory
>android
>app
, нажмите кнопку Next, затем следуйте инструкциям на странице. Обязательно выполняйте все инструкции. - Создайте класс Flutter Local Notification и настройте локальное уведомление: В одном из служебных файлов можно создать класс для установки и настройки локальных уведомлений.
// import all relevant packages here
class LocalNotificationService {
static final localNotification = FlutterLocalNotificationsPlugin();
static Future<void> initialize()async{
// configure android to use the app icon for notification
const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('@mipmap/ic_launcher');
// this function would be used for iOS apps when notification is received
// it can be configured as needed.
void onDidReceiveLocalNotification(
int? id, String? title, String? body, String? payload) async {
print('the notification arrived ');
}
// darwin settings is for iOS and MacOS
final DarwinInitializationSettings initializationSettingsDarwin =
DarwinInitializationSettings(
requestSoundPermission: true,
requestBadgePermission: true,
requestAlertPermission: true,
onDidReceiveLocalNotification: onDidReceiveLocalNotification,
);
// android and ios settings are thus embedded together
final InitializationSettings settings = InitializationSettings(
android: initializationSettingsAndroid,
iOS: initializationSettingsDarwin,
);
// initialize the plugin with the configured settings.
await localNotification.initialize(
settings,
);
}
}
- Инициализируйте службу уведомлений: В файле
main.dart
инициализируйте службу уведомлений flutter.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
// this is where the notification service was initialized
await LocalNotificationService.initialize();
runApp(MyApp());
}
- Создайте класс firebase messaging и настройте firebase messaging: Для различных событий уведомлений необходимо инициализировать Firebase messaging.
// import all necessary packages
// this function was written out of the class intentionally.
// if written in a class, it might cause a null check bug within the app.
Future<void> _backgroundMsgHandler(RemoteMessage? message) async {
// you can specify how you want to handle bugs in your app when messages...
// are received and the app is in background mode.
}
class FirebaseMessagingService {
static final firebaseInstance = FirebaseMessaging.instance;
static Future<void> init() async {
/**
* When the app is completely closed (not in the background)
* and opened directly from the push notification
*/
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? event) {
// specify how you want to handle the notification
});
//Background
FirebaseMessaging.onBackgroundMessage(_backgroundMsgHandler);
/**
* When the app is open and it receives a push notification
*/
FirebaseMessaging.onMessage.listen((RemoteMessage message) async {
// specify how you want to handle the notification
});
/**
* When the app is in the background and is opened directly
* from the push notification.
*/
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
// specify how you want to handle the notification
});
}
// this function handles app permissions
Future<void> grantAppPermission() async {
NotificationSettings settings = await firebaseInstance.requestPermission(
alert: true,
announcement: true,
badge: true,
provisional: false,
sound: true,
);
// handle final permission settings from user as appropriate
if (settings.authorizationStatus == AuthorizationStatus.authorized) {
print('User granted permission');
} else if (settings.authorizationStatus ==
AuthorizationStatus.provisional) {
print('User granted provisional permission');
} else {
print('User declined or has not accepted permission');
}
}
}
Для каждого события сообщения могут обрабатываться по-разному. Для одних сообщений может потребоваться действие перенаправления, для других - всплывающий диалог. Каким бы ни было ваше предпочтение, вы можете реализовать его в своем приложении.
- Инициализируйте обмен сообщениями firebase на главной странице: В точке входа приложения инициализируйте firebase messaging.
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
@override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
Future<void> initFM() async {
await FirebaseMessagingService.grantAppPermission();
await FirebaseMessagingService.init();
}
@override
void initState() {
initFM();
super.initState();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
- Обновите локальную службу уведомлений: Необходимо обновить локальную службу уведомлений, чтобы добавить функцию сброса количества значков для iOS-устройств.
// import the flutter app badger package
// import http package
class LocalNotificationService {
..//all previously written codes
static Future<void> resetBadgeCountToZero(Store<AppState> store) async {
// call the api to reset badge count on the db.
http.get(Uri.parse('backend_url_to_reset_badge'));
// this updates the UI badge for iOS
FlutterAppBadger.removeBadge();
}
}
Эта функция resetBadgeCountToZero
может быть вызвана в методе firebase init для каждого из событий уведомления по мере необходимости.
Push-уведомления - это, безусловно, очень интересная функция. Чем больше вы с ней работаете, тем удобнее ее реализовывать. Обратите внимание, что вы можете отправлять push-уведомления непосредственно из панели Firebase, не взаимодействуя с бэкендом. Это в основном для целей разработки и тестирования. Она также используется во время работы приложения для отправки широковещательных уведомлений нескольким пользователям. Однако в данной статье речь идет об использовании бэкенда node.js, поскольку он отвечает потребностям большинства разработчиков.
Ресурсы
Следующие ресурсы могут помочь вам в настройке push-уведомлений для вашего приложения: