Chuyển đến nội dung chính

Lesson 16: Push Notifications & Background Tasks

Firebase Cloud Messaging setup. Local notifications. Background fetch, WorkManager. Foreground services. Deep linking from notifications.

💻 Programming — Lesson 16 Lesson 16: Push Notifications & Background Tasks

Flutter & Dart: From Basics to Advanced

Part 4: Advanced UI & Features

xdev.asia

1. Firebase Cloud Messaging

class PushNotificationService {
  final _messaging = FirebaseMessaging.instance;

  Future<void> init() async {
    // Xin quyền
    final settings = await _messaging.requestPermission(
      alert: true,
      badge: true,
      sound: true,
    );

    if (settings.authorizationStatus == AuthorizationStatus.authorized) {
      final token = await _messaging.getToken();
      debugPrint('FCM Token: $token');

      // Token refresh
      _messaging.onTokenRefresh.listen((newToken) {
        // Gửi token mới lên server
      });
    }

    // Foreground messages
    FirebaseMessaging.onMessage.listen(_handleForegroundMessage);

    // Background messages
    FirebaseMessaging.onBackgroundMessage(_firebaseBackgroundHandler);

    // User tapped notification
    FirebaseMessaging.onMessageOpenedApp.listen(_handleNotificationTap);
  }

  void _handleForegroundMessage(RemoteMessage message) {
    // Hiển thị local notification
    _showLocalNotification(
      title: message.notification?.title ?? '',
      body: message.notification?.body ?? '',
      payload: message.data['route'] ?? '',
    );
  }

  void _handleNotificationTap(RemoteMessage message) {
    final route = message.data['route'];
    if (route != null) {
      // Navigate to route
    }
  }
}

@pragma('vm:entry-point')
Future<void> _firebaseBackgroundHandler(RemoteMessage message) async {
  await Firebase.initializeApp();
  // Handle background message
}

2. Local Notifications

class LocalNotificationService {
  final _plugin = FlutterLocalNotificationsPlugin();

  Future<void> init() async {
    const androidSettings = AndroidInitializationSettings('@mipmap/ic_launcher');
    const iosSettings = DarwinInitializationSettings(
      requestAlertPermission: true,
      requestBadgePermission: true,
      requestSoundPermission: true,
    );

    await _plugin.initialize(
      const InitializationSettings(android: androidSettings, iOS: iosSettings),
      onDidReceiveNotificationResponse: (response) {
        // Handle tap
        final payload = response.payload;
        if (payload != null) {
          // Navigate
        }
      },
    );
  }

  Future<void> show({required String title, required String body, String? payload}) async {
    await _plugin.show(
      DateTime.now().millisecondsSinceEpoch ~/ 1000,
      title,
      body,
      const NotificationDetails(
        android: AndroidNotificationDetails(
          'default_channel',
          'Default',
          importance: Importance.high,
          priority: Priority.high,
        ),
      ),
      payload: payload,
    );
  }

  // Scheduled notification
  Future<void> schedule({
    required String title,
    required String body,
    required DateTime scheduledDate,
  }) async {
    await _plugin.zonedSchedule(
      DateTime.now().millisecondsSinceEpoch ~/ 1000,
      title,
      body,
      tz.TZDateTime.from(scheduledDate, tz.local),
      const NotificationDetails(
        android: AndroidNotificationDetails('scheduled', 'Scheduled'),
      ),
      androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle,
      uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime,
    );
  }
}

3. Background Tasks with WorkManager

// Setup
Workmanager().initialize(callbackDispatcher, isInDebugMode: true);

// Register periodic task
Workmanager().registerPeriodicTask(
  'sync-data',
  'syncDataTask',
  frequency: const Duration(hours: 1),
  constraints: Constraints(
    networkType: NetworkType.connected,
    requiresBatteryNotLow: true,
  ),
);

@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((taskName, inputData) async {
    switch (taskName) {
      case 'syncDataTask':
        await _syncData();
        return true;
      default:
        return false;
    }
  });
}

Future<void> _syncData() async {
  // Sync logic — chạy trong isolate riêng
}

Next article: Clean Architecture for Flutter.