53

I have an app that recieves push notification using OneSignal. I have made a notification opened handler that should open specific screen on click of the notification. How can i navigate to a screen without context. or how can I open specific screen on app startup. My code:

OneSignal.shared.setNotificationOpenedHandler((notification) {
  var notify = notification.notification.payload.additionalData;
  if (notify["type"] == "message") {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => DM(user: notify['id']),
      ),
    );
  }
  if (notify["type"] == "user") {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => Profileo(notify["id"]),
      ),
    );
  }
  if (notify["type"] == "post") {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => ViewPost(notify["id"]),
      ),
    );
  }
});

I am able to achieve this when the app is opened for the first time but It only opens the homepage If i close the app and even if I re-open it. I guess that is because the context is changed.

Please Help!!

Ashutosh Sharma
  • 1,059
  • 1
  • 11
  • 21

6 Answers6

110

Look at this here: https://github.com/brianegan/flutter_redux/issues/5#issuecomment-361215074

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');
BIS Tech
  • 12,833
  • 7
  • 71
  • 107
tsdevelopment
  • 1,134
  • 1
  • 6
  • 7
33

You can use this wonderful plugin: https://pub.dev/packages/get

Description from the package: A consistent navigation library that lets you navigate between screens, open dialogs, and display snackbars from anywhere in your code without context.

Get.to(NextScreen()); // look at this simplicity :)
Get.back(); //  pop()
Get.off(NextScreen()); // clears the previous routes and opens a new screen.
y.selimdogan
  • 548
  • 4
  • 11
1

Quickest fix is above using global navigatorKey (like @tsdevelopment answered). To fix undefined navigatorKey, it must be imported from where it is instantiated (for this example in main.dart).

Your main.dart

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

void main() {
  runApp(CupertinoApp(
    title: 'Navigate without context',
    initialRoute: '/',
    navigatorKey: navigatorKey, // important
    onGenerateRoute: ...
  ));
}

For example you are in your lib/utils/api.dart

import 'package:your_package_name/main.dart'; // important

abstract class API {
  static Future<dynamic> get() async {
     // call some api
     ...
     // then you want to navigate to specific screen like login
     navigatorKey.currentState?.pushNamed('/login'); // navigate to login, with null-aware check
  }
}

Also have a gist example if you prefer in a service approach. Check this: https://gist.github.com/josephdicdican/81e59fad70530eac251ad6c28e2dcd4b

jdme
  • 445
  • 4
  • 11
0

This solution is general if you want to navigate or to show dialog without context using globalKey especially with Bloc or when your logic is separated from your UI part.

Firstly install this package:

Not: I'm using null safety version

  get_it: ^7.2.0

Then create a separate file for your service locator:

service_location.dart

    import 'package:get_it/get_it.dart';
    
    GetIt locator = GetIt.instance;
    
    class NavigationService {
      final GlobalKey<NavigatorState> navigatorKey =
          new GlobalKey<NavigatorState>();
      Future<dynamic> navigateTo(String routeName) {
        return navigatorKey.currentState!.pushNamed(routeName);
      }
    
      void setupLocator() {
        locator.registerLazySingleton(() => NavigationService());
      }

  void showMyDialog() {
    showDialog(
        context: navigatorKey.currentContext!,
        builder: (context) => Center(
              child: Material(
                color: Colors.transparent,
                child: Text('Hello'),
              ),
            ));
  }
    }

on main.dart:

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  NavigationService().setupLocator();
  runApp(MyApp());
}
// add navigatorKey for MaterialApp

 MaterialApp(
        navigatorKey: locator<NavigationService>().navigatorKey,
      ),

at your business logic file bloc.dart define this inside the bloc class or at whatever class you want to use navigation inside Then start to navigate inside any function inside.

class Cubit extends Cubit<CubitState> {
  final NavigationService _navigationService = locator<NavigationService>();
  void sampleFunction(){
       _navigationService.navigateTo('/home_screen'); // to navigate
       _navigationService.showMyDialog(); // to show dialog

    }
}

Not: I'm using generateRoute for routing.

Yusuf Amr
  • 179
  • 2
  • 3
0

You can actually pass the context outside the builder(), by adding parameter value to the method.

for example, if you need to use navigator push in dart file without builder.

you can do this

methodName(String name, String address, context){
Navigator.pushNamed(context, '/login');
}
0

You can use this no_context_navigation plugin

as the name suggests, we can navigate without context

navService.pushNamed('/detail_screen', args: 'From Home Screen');
a0x2
  • 1,726
  • 1
  • 16
  • 25