lemnisk_flutter 1.1.9
lemnisk_flutter: ^1.1.9 copied to clipboard
The Lemnisk Flutter Plugin allows you to track user event data from your Android or IOS app. The Plugin can be easily imported into any Android or iOS app. Based on the data it receives from the user [...]
example/lib/main.dart
import 'dart:async';
import 'dart:developer' as developer;
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:lemnisk_flutter/flutter_wrapper_method_channel.dart';
import 'package:lemnisk_flutter/flutter_wrapper.dart';
// Import the separated pages
import 'notification_details_page.dart';
import 'button_screen.dart';
// Global navigator key to access navigator from anywhere
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
// Global initialization flag to prevent duplicate initialization
bool _firebaseInitialized = false;
// Global stream controller for notification data
final StreamController<Map<String, dynamic>> notificationDataController =
StreamController<Map<String, dynamic>>.broadcast();
Stream<Map<String, dynamic>> get notificationDataStream =>
notificationDataController.stream;
// Cache the last notification data to prevent loss during race conditions
Map<String, dynamic>? _lastNotificationData;
// Getter and setter for notification data cache
Map<String, dynamic>? getLastNotificationData() => _lastNotificationData;
void setLastNotificationData(Map<String, dynamic>? data) => _lastNotificationData = data;
void clearLastNotificationData() => _lastNotificationData = null;
// Background message handler (must be top-level function)
@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
await Firebase.initializeApp();
print('\n========== FCM BACKGROUND MESSAGE ==========');
print('Message ID: ${message.messageId}');
print('Notification: ${message.notification?.toMap()}');
print('Data: ${message.data}');
print('==========================================\n');
}
// Safe Firebase initialization that won't cause duplicate initialization errors
Future<FirebaseApp> _initializeFirebase() async {
if (_firebaseInitialized) {
// Return the existing instance if already initialized
return Firebase.app();
}
_firebaseInitialized = true;
return await Firebase.initializeApp();
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
try {
await _initializeFirebase();
// Set up background message handler
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
print('\n========== FIREBASE INITIALIZED ==========');
print('Firebase initialized successfully');
print('==========================================\n');
runApp(MyApp());
} catch (e) {
print('\n========== FIREBASE INIT ERROR ==========');
print('Error initializing Firebase: $e');
print('==========================================\n');
runApp(MyApp());
}
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// Initialize method channel in the app's state
static const MethodChannel _channel = MethodChannel('com.example.lemnisk/notification_navigation');
@override
void initState() {
super.initState();
_setupMethodChannel();
_setupFirebaseMessaging();
_setupLemniskNotificationHandler();
_handleInitialIntent();
}
Future<void> _handleInitialIntent() async {
print('\n========== HANDLING INITIAL INTENT ==========');
try {
// Check if app was opened with a deep link (terminated state)
final intent = await _channel.invokeMethod('getInitialIntent');
if (intent != null && intent is Map) {
print('Initial intent data: $intent');
// Check if there's a deep link
final deepLink = intent['deepLink'];
if (deepLink != null && deepLink.toString().isNotEmpty) {
print('Found deep link in initial intent: $deepLink');
// Parse the deep link to determine the route
final uri = Uri.parse(deepLink.toString());
if (uri.host == 'notification-details' || uri.path.contains('notification-details')) {
print('Navigating to notification-details from initial intent');
// Navigate to the page
navigatorKey.currentState?.pushNamed(
'/notification-details'
);
}
}
} else {
print('No initial intent found');
}
} catch (e) {
print('Error handling initial intent: $e');
}
print('==========================================\n');
}
void _setupMethodChannel() {
print('Setting up notification method channel');
_channel.setMethodCallHandler((call) async {
print('Received method call: ${call.method}');
// Handle notification navigation
if (call.method == 'navigateToNotificationPage') {
if (call.arguments == null) {
print('ERROR: Null arguments received');
return false;
}
try {
// Convert arguments to the right format
final arguments = Map<String, dynamic>.from(call.arguments);
// Navigate to notification details page
if (navigatorKey.currentState != null) {
navigatorKey.currentState!.pushNamed(
'/notification-details',
arguments: arguments,
);
return true;
} else {
print('ERROR: Navigator not available');
return false;
}
} catch (e) {
print('ERROR: Failed to handle notification: $e');
return false;
}
}
return null;
});
print('Method channel setup complete');
}
void _setupFirebaseMessaging() {
print('\n========== SETTING UP FCM LISTENERS ==========');
// 1. Foreground messages (app is open and visible)
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
print('\n========== FCM FOREGROUND MESSAGE ==========');
print('Message ID: ${message.messageId}');
print('Sent Time: ${message.sentTime}');
print('From: ${message.from}');
print('Category: ${message.category}');
print('Collapse Key: ${message.collapseKey}');
print('Thread ID: ${message.threadId}');
print('Message Type: ${message.messageType}');
if (message.notification != null) {
print('\n--- Notification Payload ---');
print('Title: ${message.notification!.title}');
print('Body: ${message.notification!.body}');
print('Android: ${message.notification!.android?.toMap()}');
print('Apple: ${message.notification!.apple?.toMap()}');
} else {
print('\n--- No Notification Payload (Data-only message) ---');
}
print('\n--- Data Payload ---');
message.data.forEach((key, value) {
print(' $key: $value');
});
print('==========================================\n');
});
// 2. Background tap (app was in background, user tapped notification)
FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
print('\n========== FCM NOTIFICATION TAPPED (BACKGROUND) ==========');
print('Message ID: ${message.messageId}');
print('Sent Time: ${message.sentTime}');
if (message.notification != null) {
print('\n--- Notification Payload ---');
print('Title: ${message.notification!.title}');
print('Body: ${message.notification!.body}');
}
print('\n--- Data Payload ---');
message.data.forEach((key, value) {
print(' $key: $value');
});
print('==========================================\n');
// Navigate to notification details if needed
if (message.data.isNotEmpty) {
navigatorKey.currentState?.pushNamed(
'/notification-details',
arguments: message.data,
);
}
});
// 3. Terminated tap (app was killed, user tapped notification)
FirebaseMessaging.instance.getInitialMessage().then((RemoteMessage? message) {
if (message != null) {
print('\n========== FCM NOTIFICATION TAPPED (TERMINATED) ==========');
print('Message ID: ${message.messageId}');
print('Sent Time: ${message.sentTime}');
if (message.notification != null) {
print('\n--- Notification Payload ---');
print('Title: ${message.notification!.title}');
print('Body: ${message.notification!.body}');
}
print('\n--- Data Payload ---');
message.data.forEach((key, value) {
print(' $key: $value');
});
print('==========================================\n');
// Navigate to notification details if needed
if (message.data.isNotEmpty) {
Future.delayed(Duration(seconds: 1), () {
navigatorKey.currentState?.pushNamed(
'/notification-details',
arguments: message.data,
);
});
}
} else {
print('\n========== NO INITIAL MESSAGE ==========');
print('App was not opened from a notification');
print('==========================================\n');
}
});
print('FCM listeners setup complete');
print('==========================================\n');
}
void _setupLemniskNotificationHandler() {
print('\n========== SETTING UP LEMNISK NOTIFICATION HANDLER ==========');
// Listen to Lemnisk notification taps
LemniskFlutter.onNotificationTapped.listen((data) {
print('\n========== LEMNISK NOTIFICATION TAPPED ==========');
print('Received notification tap with data:');
print('\n--- Notification Data ---');
data.forEach((key, value) {
print(' $key: $value');
});
print('==========================================\n');
// Cache the data globally
_lastNotificationData = data;
// Emit data to stream for any listening pages
notificationDataController.add(data);
// DON'T navigate here - let the deep link handle navigation
// The deep link from LemActivity will open the correct page
print('Notification data cached and emitted to stream');
});
print('Lemnisk notification handler setup complete');
print('==========================================\n');
}
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
routes: {
'/': (context) => MyButtonScreen(),
'/notification-details': (context) => NotificationDetailsPage(),
},
);
}
}