Сегодня поговорим про push‑уведомления в реальном проекте: как работать с FCM, показывать локальные уведомления в foreground, обрабатывать background‑события, реализовать deep links по клику и корректно запрашивать разрешения.
Так как это первая статья в новом году, поздравляю всех с праздниками! Желаю вам продуктивного и приятного 2026‑го: чтобы баги фиксились легко, а фичи шли в прод. Уже все вернулись к работе или кто‑то все еще лежит на диване?🌟
Что и зачем:
◾️FCM (Firebase Cloud Messaging) — транспорт: доставляет уведомления/дату на устройство. Поддерживает notification (авто‑показ) и data‑messages (передача произвольных данных)
◾️flutter_local_notifications — показывает локальное нотификационное состояние, полезно, когда приложение на foreground (FCM обычно не показывает системное уведомление, если приложение активно)
◾️Deep links — позволяют при клике на уведомление перейти в конкретный экран приложения (через data payload или динамические ссылки)
Код: инициализация и обработка
Пакеты: firebase_core, firebase_messaging, flutter_local_notifications, (go_router / navigatorKey для навигации).
Простой пример:
// main.dart (сокращённо)
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
final FlutterLocalNotificationsPlugin localNotif = FlutterLocalNotificationsPlugin();
Future<void> _bgHandler(RemoteMessage m) async {
await Firebase.initializeApp();
// Лёгкая логика: запись в БД или планирование локальной нотификации
print('BG message ${m.messageId}');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
FirebaseMessaging.onBackgroundMessage(_bgHandler);
await localNotif.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('@mipmap/ic_launcher'),
iOS: DarwinInitializationSettings(),
),
onDidReceiveNotificationResponse: (resp) {
final p = resp.payload;
if (p != null) navigatorKey.currentState?.pushNamed(p);
},
);
runApp(MyApp());
}
final GlobalKey<NavigatorState> navigatorKey = GlobalKey();
class MyApp extends StatefulWidget { @override State createState() => _MyAppState(); }
class _MyAppState extends State<MyApp> {
final FirebaseMessaging fm = FirebaseMessaging.instance;
@override
void initState() {
super.initState();
_requestPermissions();
FirebaseMessaging.onMessage.listen(_onMessage);
FirebaseMessaging.onMessageOpenedApp.listen((m) => _handleData(m.data));
_checkInitialMessage();
}
Future<void> _requestPermissions() async {
if (Platform.isIOS) {
await fm.requestPermission(alert: true, badge: true, sound: true);
} else {
// Android 13+: POST_NOTIFICATIONS нужно в Manifest + runtime request (permission_handler)
await fm.requestPermission();
}
}
void _onMessage(RemoteMessage m) {
// в foreground показываем локальную нотификацию
final t = m.notification?.title ?? m.data['title'];
final b = m.notification?.body ?? m.data['body'];
localNotif.show(m.hashCode, t, b, const NotificationDetails(
android: AndroidNotificationDetails('ch', 'Channel', importance: Importance.max),
), payload: m.data['deeplink']);
}
Future<void> _checkInitialMessage() async {
final m = await fm.getInitialMessage();
if (m != null) _handleData(m.data);
}
void _handleData(Map<String, dynamic> d) {
final deeplink = d['deeplink'] ?? d['url'] ?? d['screen'];
if (deeplink != null) navigatorKey.currentState?.pushNamed(deeplink);
}
@override
Widget build(BuildContext c) => MaterialApp(navigatorKey: navigatorKey, home: Scaffold(body: Center(child: Text('Ready'))));
}
Продолжение — в комментариях 👇
