Tích hợp push notification Flutter (iOS & Android)

Dart, Flutter 3.22+. Dựa trên firebase_messaging và flutter_callkit_incoming.

💙 Flutter

Dart, Flutter 3.22+. Dựa trên firebase_messaging và flutter_callkit_incoming.

Tải SDK trong bảng điều khiển (mục Hướng dẫn tích hợp) sau khi tạo tài khoản miễn phí.

  1. Thêm package

    Giải nén file tải về vào thư mục gốc của project (tạo ra thư mục PushConsoleSDK/), cấu hình Firebase bằng flutterfire configure, rồi:

    pubspec.yaml
    # pubspec.yaml
    dependencies:
      firebase_core: ^4.0.0
      firebase_messaging: ^16.0.0
      push_console:
        path: ./PushConsoleSDK/flutter/push_console

    Android: upload service account JSON của cùng project Firebase lên TingPush. iOS: server gửi thẳng tới APNs nên cần key .p8.

  2. Khởi tạo trong main.dart

    lib/main.dart
    import 'package:firebase_core/firebase_core.dart';
    import 'package:firebase_messaging/firebase_messaging.dart';
    import 'package:flutter/material.dart';
    import 'package:push_console/push_console.dart';
    import 'firebase_options.dart';
    
    @pragma('vm:entry-point')
    Future<void> firebaseBackgroundHandler(RemoteMessage message) async {
      await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
      await PushConsole.handleBackgroundMessage(message); // Android: hiện màn hình cuộc gọi
    }
    
    Future<void> main() async {
      WidgetsFlutterBinding.ensureInitialized();
      await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
      FirebaseMessaging.onBackgroundMessage(firebaseBackgroundHandler);
    
      await PushConsole.initialize(
        appId: 'YOUR_APP_ID',
        serverUrl: 'https://tingpush.com',
        callStyle: const PushConsoleCallStyle(appName: 'MyApp'),
      );
    
      PushConsole.onCallEvent.listen((e) {
        if (e.action == PushConsoleCallAction.accepted) {
          // Người dùng bấm Nghe → kết nối cuộc gọi (e.call.id, e.call.data['room'])
        }
      });
      PushConsole.onNotificationOpened.listen((m) { /* điều hướng theo m.data */ });
    
      runApp(const MyApp());
    }

    Môi trường APNs: debug/profile = sandbox, release = production (đổi bằng iosSandbox).

  3. Gắn user khi đăng nhập

    Dart
    await PushConsole.login('user-123');       // sau khi đăng nhập
    await PushConsole.logout();                // khi đăng xuất
    await PushConsole.setTags({'plan': 'vip'});
    await PushConsole.endCall(callId);         // tắt cuộc gọi
  4. iOS: bật capability và sửa AppDelegate (cho cuộc gọi)

    Mở ios/Runner.xcworkspace → Signing & Capabilities: thêm Push Notifications, Background Modes (Remote notifications + Voice over IP). Cuộc gọi trên iOS là VoIP push, bắt buộc báo CallKit bằng code native:

    ios/Runner/AppDelegate.swift
    import UIKit
    import Flutter
    import PushKit
    import flutter_callkit_incoming
    
    @main
    @objc class AppDelegate: FlutterAppDelegate, PKPushRegistryDelegate {
        override func application(_ application: UIApplication,
                                  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
            GeneratedPluginRegistrant.register(with: self)
            let voipRegistry = PKPushRegistry(queue: .main)
            voipRegistry.delegate = self
            voipRegistry.desiredPushTypes = [.voIP]
            return super.application(application, didFinishLaunchingWithOptions: launchOptions)
        }
    
        func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {
            let token = credentials.token.map { String(format: "%02x", $0) }.joined()
            SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP(token)
        }
    
        func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
            SwiftFlutterCallkitIncomingPlugin.sharedInstance?.setDevicePushTokenVoIP("")
        }
    
        func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload,
                          for type: PKPushType, completion: @escaping () -> Void) {
            guard type == .voIP else { return completion() }
            let p = payload.dictionaryPayload
            let data = flutter_callkit_incoming.Data(
                id: (p["call_id"] as? String) ?? UUID().uuidString,
                nameCaller: (p["caller_name"] as? String) ?? "",
                handle: (p["caller_handle"] as? String) ?? "",
                type: (p["is_video"] as? String) == "true" ? 1 : 0)
            data.duration = (Int((p["timeout"] as? String) ?? "") ?? 45) * 1000
            var extra: [String: Any] = [:]
            for (key, value) in p { if let key = key as? String, key != "aps" { extra[key] = value } }
            data.extra = extra as NSDictionary
            SwiftFlutterCallkitIncomingPlugin.sharedInstance?.showCallkitIncoming(data, fromPushKit: true) {
                completion()
            }
        }
    }
  5. Android: hiện trên màn hình khoá

    Trong AndroidManifest.xml, thẻ <activity android:name=".MainActivity"> thêm android:showWhenLocked="true" và android:turnScreenOn="true". Android 14+: gọi PushConsole.canUseFullScreenIntent() / requestFullScreenIntentPermission().

✅ Kiểm tra sau khi tích hợp

  1. Chạy app trên máy thật, bật log: thấy [PushConsole] Đã đăng ký thiết bị ….
  2. Tab Thiết bị của dự án: thiết bị xuất hiện; máy iOS có badge VoIP nếu dùng cuộc gọi.
  3. Tab Gửi thông báo: gửi thử tới thiết bị đó.
  4. Chọn 📞 Cuộc gọi, gửi thử khi app đang mở, khi đã tắt app và khi màn hình đang khoá.

Sẵn sàng tích hợp?

Tạo tài khoản để nhận App ID, API Key và tải SDK. Code mẫu trong bảng điều khiển được điền sẵn thông tin dự án của bạn.