Home > Blockchain >  Access BuildContext to navigate from Push Notification Service (Flutter)
Access BuildContext to navigate from Push Notification Service (Flutter)

Time:03-17

I am struggling to understand how to navigate from a Push Notification class on selecting a notification in flutter. I need access to the BuildContext or somehow figure out a way to tell my app to navigate without this.

My code looks like below:

main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  await PushNotificationService().setupInteractedMessage();
  runApp(const MyApp());
}

Future awaitDeepLink() async {
  StreamSubscription _sub;
  try {
    await getInitialLink();
    _sub = uriLinkStream.listen((Uri uri) {
      runApp(MyApp(uri: uri));
    }, one rror: (err) {

    });
  } on PlatformException {
    print("PlatformException");
  } on Exception {
    print('Exception thrown');
  }
}

class MyApp extends StatelessWidget {
  final Uri uri;

  static final FirebaseAnalytics analytics = FirebaseAnalytics.instance;

  const MyApp({Key key, this.uri}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return OverlaySupport(
      child: GestureDetector(
        behavior: HitTestBehavior.opaque,
        onTap: () {
          FocusScopeNode currentFocus = FocusScope.of(context);

          if (!currentFocus.hasPrimaryFocus &&
              currentFocus.focusedChild != null) {
            FocusManager.instance.primaryFocus.unfocus();
          }
        },
        child: MaterialApp(
          debugShowCheckedModeBanner: false,
          theme: buildThemeData(),
          home: CheckAuth(uri: uri),
        ),
      ),
    );
  }
}

PushNotificationService.dart

class PushNotificationService {
  Future<void> setupInteractedMessage() async {
    RemoteMessage initialMessage =
        await FirebaseMessaging.instance.getInitialMessage();
    String token = await FirebaseMessaging.instance.getToken();

    var storage = const FlutterSecureStorage();
    storage.write(key: "fcm_token", value: token);

    if (initialMessage != null) {
      print(initialMessage.data['type']);
    }

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) {
      print("message opened app:"   message.toString());
    });

    await enableIOSNotifications();
    await registerNotificationListeners();
  }

  registerNotificationListeners() async {
    AndroidNotificationChannel channel = androidNotificationChannel();

    final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
        FlutterLocalNotificationsPlugin();

    await flutterLocalNotificationsPlugin
        .resolvePlatformSpecificImplementation<
            AndroidFlutterLocalNotificationsPlugin>()
        ?.createNotificationChannel(channel);

    var androidSettings =
        const AndroidInitializationSettings('@mipmap/ic_launcher');

    var iOSSettings = const IOSInitializationSettings(
      requestSoundPermission: false,
      requestBadgePermission: false,
      requestAlertPermission: false,
    );

    var initSettings = InitializationSettings(
      android: androidSettings,
      iOS: iOSSettings,
    );

    flutterLocalNotificationsPlugin.initialize(
      initSettings,
      onSelectNotification: onSelectNotification,
    );

    FirebaseMessaging.onMessage.listen((RemoteMessage message) {
      RemoteNotification notification = message.notification;
      AndroidNotification android = message.notification.android;

      if (notification != null && android != null) {
        flutterLocalNotificationsPlugin.show(
          notification.hashCode,
          notification.title,
          notification.body,
          NotificationDetails(
            android: AndroidNotificationDetails(
              channel.id,
              channel.name,
              icon: android.smallIcon,
              playSound: true,
            ),
          ),
          payload: json.encode(message.data),
        );
      }
    });

    FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) async {
      print("onMessageOpenedApp: $message");

      if (message.data != null) {
        print(message.data);
      }
    });

    FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
  }

  Future onSelectNotification(String payload) async {
    Map data = json.decode(payload);

    if (data['type'] == 'message') {
      // NEED TO ACCESS CONTEXT HERE
      // Navigator.push(
      //   navigatorKey.currentState.context,
      //   CupertinoPageRoute(
      //     builder: (navigatorKey.currentState.context) => MessagesScreen(
      //       conversationId: data['conversation_id'],
      //       userId: data['user_id'],
      //       name: data['name'],
      //       avatar: data['avatar'],
      //       projectName: data['project_name'],
      //       projectId: data['project_id'],
      //       plus: data['plus'],
      //     ),
      //   ),
      // );
    }
  }

  Future<void> _firebaseMessagingBackgroundHandler(
      RemoteMessage message) async {
    print("onBackgroundMessage: $message");
  }

  enableIOSNotifications() async {
    await FirebaseMessaging.instance
        .setForegroundNotificationPresentationOptions(
      alert: true,
      badge: true,
      sound: true,
    );
  }

  androidNotificationChannel() => const AndroidNotificationChannel(
        'high_importance_channel', // id
        'High Importance Notifications', // title
        importance: Importance.max,
      );
}

As you can see in the onSelectNotification() function I am trying to navigate but do not know how.

I am quite new to dart/flutter so any guidance would be appreciated.

CodePudding user response:

//You can set a global key for your navigation:

final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();

// Pass it to MaterialApp:

new MaterialApp(
      title: 'MyApp',
      onGenerateRoute: generateRoute,
      navigatorKey: navigatorKey,
    );
    
// Push routes:

navigatorKey.currentState.pushNamed('/someRoute');

CodePudding user response:

I need access to the BuildContext

Yes you need context to navigate. In flutter its best practice to have navigation code in the widgets. and you have your context

From a tweet thread by Andrea Bizzotto

RULE: Navigation code belongs to the widgets

If you try to put your navigation code in the business logic, you'll have a hard time because you need a BuildContext to do so.

Solution:

  • emit a new widget state
  • listen to the state in the widget and perform the navigation there

CodePudding user response:

create a stream

StreamController<Map<String, dynamic>> streamController = StreamController<Map<String, dynamic>>();

then use it here

Future onSelectNotification(String payload) async {
    Map data = json.decode(payload);
      _streamController.add(data)
  }
}

and then you can listen to the stream on the home page under your MaterialApp widget

  @override
  void initState() {
    streamController.stream.listen((event) {
      if (data['type'] == 'message') {
        Navigator.of(context).push(
          CupertinoPageRoute(
            builder: (context) => MessagesScreen(
              conversationId: data['conversation_id'],
              userId: data['user_id'],
              name: data['name'],
              avatar: data['avatar'],
              projectName: data['project_name'],
              projectId: data['project_id'],
              plus: data['plus'],
            ),
          ),
        );
      }
    });
    super.initState();
  }
  • Related