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í.
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ằngflutterfire configure, rồi:pubspec.yaml# pubspec.yaml dependencies: firebase_core: ^4.0.0 firebase_messaging: ^16.0.0 push_console: path: ./PushConsoleSDK/flutter/push_consoleAndroid: 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.
Khởi tạo trong main.dart
lib/main.dartimport '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).Gắn user khi đăng nhập
Dartawait 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ọiiOS: 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.swiftimport 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() } } }Android: hiện trên màn hình khoá
Trong
AndroidManifest.xml, thẻ<activity android:name=".MainActivity">thêmandroid:showWhenLocked="true"vàandroid:turnScreenOn="true". Android 14+: gọiPushConsole.canUseFullScreenIntent()/requestFullScreenIntentPermission().
✅ Kiểm tra sau khi tích hợp
- Chạy app trên máy thật, bật log: thấy
[PushConsole] Đã đăng ký thiết bị …. - 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.
- Tab Gửi thông báo: gửi thử tới thiết bị đó.
- 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á.