how can I route using GoRouter in Flutter without context?
Asked Answered
R

4

14

The problem I want to solve: My app which uses GoRouter needs to be able to route to a named route from within main(). Since most routing is of the form 'context.go' I cannot do so within main.

Background

My app uses GoRouter. The ease with which GetX had let me define named routes and pass parameters from main() was perfect.

However, GetX and GoRouter eventually causes problems for me. GoRouter would eventually have no context in other parts of the app.

If there were a way to have them co-exist simply, I'd be open to it.

I had used the service locator pattern with the GetIt package to associate with a navigatorKey. It would work when I tested it -- but this involved creating two MaterialApps.

However, this app uses GoRouter which doesn't seem to use the navigatorKey.

I would like to go to a specific route from within main (). It seems like the service locator pattern could work for GoRouter as it did with Navigator 2.0 for MaterialApp -- but I can't find an example of how to do so.

More detailed context:

Here is what I have currently in main().

You can see the key challenge I have is that the listener for the data parameters being passed in lives in main (I got this from the third-party SDK -- I don't need it to be in main but it needs to listen regardless of the state of the app).

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();

  FFAppState(); // Initialize FFAppState

  GetSocial.addOnInitializedListener(() => {
        // GetSocial SDK is ready to use
      });

  setupLocator();

  runApp(MyApp());

  locator<LandingPageData>().referralID = "defaultReferralID";

  registerListeners();
}

void registerListeners() {
  Invites.setOnReferralDataReceivedListener((received) {
    globalReferralData = received;
    print(globalReferralData);
    print(globalReferralData.linkParams);

    print("listener - socialdata");

    String passedReferralID =
        globalReferralData.linkParams['referralID'].toString();
    String passedCreatorID =
        globalReferralData.linkParams['creatorID'].toString();
    String passedCampaignID =
        globalReferralData.linkParams['\$campaign_id'].toString();

    print(passedReferralID);
    print(passedCreatorID);
    print(passedCampaignID);

    // How can I route to a named Route?

    locator<LandingPageData>().referralID = passedReferralID;
    locator<LandingPageData>().creatorID = passedCreatorID;
    locator<LandingPageData>().campaignID = passedCampaignID;
  });
}

Here is what the locator.service.dart looks like:

final locator = GetIt.instance;

class NavigationService {
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
  // final GlobalKey<ScaffoldMessengerState> navigatorKey = GlobalKey<ScaffoldMessengerState>();
}

The above worked when I could attach to a navigatorKey and then navigate from within the listener. But that doesn't seem to work since the rest of the application uses GoRouter.

Reinhart answered 21/7, 2022 at 0:30 Comment(2)
Just set your default route to a landing page, and decide from there where you really want to go.Bengali
@RandalSchwartz -- the landing Page should only ever be routed to IF the user clicks on a deep link. Will the landing page ever open under different circumstances if I set as the initial route?Reinhart
O
17
static BuildContext? get ctx => myGoRouter.routerDelegate.navigatorKey.currentContext;

you can get context in your NavigationService in this way and use it like

NavigationService.ctx?.go(...)

the problem you may face is that ctx will be null on app state till your first page starts to be built. In the case your listener has a data while ctx is still null, routing won't work. but you can handle this situation like:

define a global tempPageToGo in main func or a service and

var _ctx = NavigationService.ctx;
if(_ctx == null) {
  tempPageToGo = anyPageDataYouWant;
  while((await Future.delayed(Duration(seconds: 1))) == null) {
    if(_ctx != null) {
      _ctx!.go(...);
      break;
    }
  }
} else _ctx!.go(...);
Orianna answered 29/9, 2022 at 7:47 Comment(0)
A
15

we can use the router object to navigate without context.

final router= GoRouter(....);

now to navigate use,

router.push(...);
Aorangi answered 30/3, 2023 at 9:6 Comment(2)
this one should be the accepted answer.Decrial
When I asked to chatgpt "how to use goroute without context", it said you can use the goroute instance. so how healthy and reliable is this method? is it the same to use over instance without using context and to use with context?Persinger
D
4

Unluckily, if I were you, I'd either drop the usage of GetX or of GoRouter.

Actually, I'd just drop GetX.

The reason is that GetX performs magic under the hood that lifts the developer the responsibility and usage of BuildContext, but that's clearly an anti-pattern, as the built-in navigation from Flutter clearly uses context: think of Navigator.of, for example.

GoRouter is built around context, and simplifies a lot of the implementations needed to perform "Navigator 2.0" actions.

If you're trying to implement deep linking, your MaterialApp should look like this in your root widget:

return MaterialApp.router(  // Flutter's Router 2.0 usage
  title: 'MyApp',
  routeInformationProvider: myGoRouter.routeInformationProvider,
  routeInformationParser: myGoRouter.routeInformationParser,
  routerDelegate: myGoRouter.routerDelegate,
);

If GetX enables you to put myGoRouter there, then you should be good to go. But as I said before, everytime you need explicit navigation, you need context.

Diatom answered 25/7, 2022 at 10:37 Comment(3)
I tried to implement this and have this similar set up under materialapp.router. But how can I route to a route from main?Reinhart
The key problem I have is being able to route based on the events from the listener. Seems like putting those events into stream is the path forward but I am unclear exactly how to do that since the third party deep link SDK reference implementation puts the listener in main(). So I was hoping to route from within the listener. This works with GetX. But using GetX with GoRouter breaks things.Reinhart
@Reinhart you have to read GoRouter's deep linking docs to use deep linking. gorouter.dev/declarative-routing#deep-linking From "main" it is hardly the case you actually need to imperatively route the user. About the second comment: you probably need a redirect logic (gorouter.dev/redirection#redirection) and/or proper GoRouter routes declared (gorouter.dev/sub-routes#sub-routes).Diatom
H
3

I'm in researching to adopt go_router in my project, and i was also stuckled for this usecase ( in my case i tried to prove that i can navigate from deferred link that callback from appsflyer SDK ).

For solution, like that go_router allows us to either navigate from context that is below the router declaration or from the redirect state. So we can wrap up all the state that effect the navigation on that. This is how i redirect from appRouterState

redirect: (GoRouterState state) {
  String? redirection(GoRouterState state) {
    final appRouterState = ref.read(appRouterStateNotifierProvider);
    final isAuthed = appRouterState.email != null;

    if (appRouterState.deferredLink != state.location && appRouterState.deferredLink != null) {
        return appRouterState.deferredLink;
    }

    if (state.location != '/login' && !isAuthed) return '/login';
    if (state.location == '/login' && isAuthed) return '/';

    return null;
  }
  final result = redirection(state);
  return result;
},

In your case, you may implement setOnReferralDataReceivedListener in the appRouterStateProvider or something. And use it for refreshListenable param in the GoRouter constructor.

Hope this helps.

Heliport answered 3/9, 2022 at 18:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.