How do I automatically route when in Flutter from within main()?
Asked Answered
D

1

1

I implemented a third-party "listener" in my Flutter ios app. I have two goals:

  1. Whenever the listener receives an event, route to LandingPage;
  2. Pass to LandingPage the values captured by the listener -- globalReferralData.

Values should also be written to local variables on disk. The screen/page that opens should be able read that variable.

Because the listener is in main(), there is no context.

I also cannot use Get for GetPage, because it seems to collide with using GoRouter as it is used and defined throughout the rest of the app in the nav.dart file.

ReferralData globalReferralData;
List<BuildContext> buildContextList = [];


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

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

  runApp(MaterialApp(
    home: MyApp(),
    routes: {
    '/landingPage': (context) => const LandingPageWidget(),
      },
    navigatorKey: locator<NavigationService>().navigatorKey));

  registerListeners();
}

void registerListeners() {
  Invites.setOnReferralDataReceivedListener((received) {
    globalReferralData = received;
    print(globalReferralData);
    print(globalReferralData.linkParams);
    print(globalReferralData.linkParams['referralID']);
    
    // pass value and open route -- this line failed to do anything

    Navigator.pushNamed(context, '/landingPage');

    // showAlert(buildContextList.last, 'Referral Data Received', '$received');
  });
}
Dipnoan answered 10/7, 2022 at 7:38 Comment(2)
this answer should help. Access the globalReferred with services or from some provider. Also you technically can't access context outside a build method or a State class.Corporator
@ObumunemeNwabude thanks, I checked it out....I guess what I want to do is generate state from outside build or State and then access it if I can only route that way. Ideally, I can route from within main().....Dipnoan
C
0

Based on your comment

... generate state from outside build or State and then access it if I can only route that way. Ideally, I can route from within main().....

Yes it's possible. That's with reference to routing without BuildContext. The following is the mechanism behind Navigation in Stacked Architecture.

In summary, you provide MaterialApp with your own navigatorKey and use that key to navigate anywhere in the Flutter code. A clean way to do this is to use dependency injection. Have a service provided by GetIt package that will make the navigatorKey accessible to the entire Flutter app.

In details, we can use the following 4 steps:

1. NavigationService and key

Have a navigation.service.dart file with a navigatorKey. This key will have the type and value of GlobalKey<NavigatorState>

import 'package:flutter/material.dart';

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

This NavigationService class can do other things but let's leave it like this for now.

2. GetIt package

The package provides some mechanism to make services available app-wide. You actually initialise it before the runApp() call just as you do with Firebase for example.

Install the package. Run the following command.

flutter pub add get_it

Then, have a file, let's say app.locator.dart with the following.

import 'package:get_it/get_it.dart';

import 'navigation.service.dart';

final locator = GetIt.instance;

void setupLocator { 
  locator.registerLazySingleton(() => NavigationService());
}

Then in main.dart, call setupLocator() before runApp(). Based on the main method you provided:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  FFAppState(); // Initialize FFAppState
  GetSocial.addOnInitializedListener(() => {
        // GetSocial SDK is ready to use
      });

  setupLocator(); //add this line 
  runApp(MyApp());
  registerListeners();
}

3. Key and MaterialApp

Attach the navigatorKey from the NavigationService (obtained from locator) to the navigatorKey on the topmost MaterialApp.

So in whatever file where you defined MaterialApp, have something like this

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

  // ...
);

4. Routing without BuildContext

In the registerListeners() function (called from main and not having access to BuildContext), you navigate with the navigatorKey. You can also use the context attached to the navigatorKey to showAlert too. Change your registerListeners() function to the following

void registerListeners() {
  Invites.setOnReferralDataReceivedListener((received) {
    globalReferralData = received;
    print(globalReferralData);
    print(globalReferralData.linkParams);
    print(globalReferralData.linkParams['referralID']);
    
    // instead of navigating with Navigator,
    // Navigator.pushNamed(context, '/landingPage');
    // navigate with the navigatorKey
    locator<NavigationService>().navigatorKey.currentState!.pushNamed('/landingPage');

    // You can as well use the key's context to showAlert.
    // so instead of the following
    // showAlert(buildContextList.last, 'Referral Data Received', '$received');
    // You can have 
    showAlert(
       locator<NavigationService>().navigatorKey.currentContext!, 
       'Referral Data Received', 
       '$received',
     );
  });
}

This way you have access to routing without the BuildContext and can use it in any other Dart file in the same project.

And hence also you've ideally routed from within main.

The above doesn't invalidate any routing with Navigator.of(context).navigate... in other parts of the app as it'll still be the same NavigationState that is been used by Flutter.

Update

I've updated the snippet of step 4 above.

From your comment

I'm unclear how from within main() which is where I put the listener class to capture the globalReferralData how I can pass that based on the example above to then route....

If you carry step 4 well above (and all the other steps) the problem should be solved. It should be solved because your main method calls this registerListeners() function, so that should do the trick.

Do you now understand?

It's also good that you called registerListeners after runApp, that way you're sure the navigatorKey must have been attached to MaterialApp.

...

That should solve one part of the problem, that's navigation. The above is just minimalist setup to serve the purpose you want. You can go further to use the entire StackedServices and or StackedApp if you wish.

The other issue about setState not working in main, it's really not possible. It's just natural. main is an entry point for compiled code and not a StatefulWidget. I'm supposing that if the above routing works for you, you might not have the issue of setState again.

But if you still need something, I propose you notifyListeners. Other state management architectures, not only Stacked, use notifyListeners (a method from ChangeNotifier) to update UI from outside StatefulWidget.

I guess you would have your way around this. And you could still go ahead with the View and ViewModel classes of Stacked and call notifyListeners in a viewModel (maybe that of a splash screen) when the globalReferredData is received.

Corporator answered 11/7, 2022 at 23:12 Comment(19)
thanks, I am reading through GetX as we speak so this is helpful. I'm reading yours as well as GetX docs to see if I can do this.Dipnoan
not GetX, GetIt, there is a difference between the two packages. GetX is more complex. I was referring to only GetIt. I think that is what was used in StackedCorporator
I'm unclear how from within main() which is where I put the listener class to capture the globalReferralData how I can pass that based on the example above to then route....Dipnoan
Okay, the navigatorKey is powerful. You can use it to showAlert too. I've updated the 4th step and it's code in the answer above to show what I'm saying. I also included an "Update" section. See if you follow the above steps appropriately this should get solved or at least there should be a different error we will be seeing. And hey your main is calling registerListeners so all that logic inside it will work. Also, the answer is based on the snippet you shared, check to see if you've not discarded the original snippet in your codebase and then you're now confused.Corporator
Thanks let me take a look, this helped me think about how to do it. The challenge (and why I used GetX) is because I need to inject dependencies and the Arguments. It seemed to work....I also pinged you on TwitterDipnoan
@obumeneme -- I'm looking at what you did again. I thought I got it working with GetX (I needed a way to push to a route without context from within listener), but my app everywhere else also uses GoRouter, and I think they collide (although not sure why). Can the Navigator Key be used in the navigator method to push to another route?Dipnoan
I am reading your Update #4 so it looks like it's addressed, just reading and comparing against GetX...and providing that GoRoute is used everywhere else...Dipnoan
Can class NavigationService be defined in main() versus a separate file?Dipnoan
So on /landingPage, I want to access the GlobalReferredData, I do it this way? locator<NavigationService>().navigatorKey.currentContext!, 'Referral Data Received', '$received',Dipnoan
still working through it, got the following error: lib/main.dart:83:6: Error: A function declaration needs an explicit list of parameters. Try adding a parameter list to the function declaration. void setupLocator { I fixed it by void setupLocator()Dipnoan
I get an error. It seems like the named Routes cannot be accessed through the navigator: Unhandled Exception: NoSuchMethodError: The method 'pushNamed' was called on null. Receiver: null Tried calling: pushNamed<Object>("/landingPage") #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:38:5) #1 registerListeners.<anonymous closure> (package:assembly/main.dart:71:10) #2 Invites.setOnReferralDataReceivedListener.<anonymous closure> (package:getsocial_flutter_sdk/invites.dart:74:15)Dipnoan
I am unclear how the locator service would recognize the named routes. It looks like we define the navigatorKey when setting up configs for MaterialApp. The main.dart also imports the nav.dart file where GoRouter named routes are set, but I'm not exactly clear where the named route is defined so it is not 'null'Dipnoan
you have to define the named routes of your projects in the routes field of MaterialAppCorporator
How does the above help pass the arguments from the GlobalReferralData to the landingPage widget? I read that we can pushNamed(:route, arguments:) as one way. It seems that the service locator pattern should also let me pass data through the dependency on a class, but I havent seen an example before.Dipnoan
You can have a different service for keeping track of the GlobalReferralData. So when you obtain the data in main, before navigating, first attach it to this ReferalService. Then you then use navigatorKey to navigate, then access the data in the service in the landingPage widget. Also, the fact that you can use a navigatorKey to navigate doesn't mean that the navigatorKey takes care of onGenerateRoute. The entire above technique is a minimal setup that should work if you follow it up properly.Corporator
In Stacked, onGenerateRoute and routes are usually set up too by some navigationService. This service will end up being auto generated with build_runner package and all. But I feel that might be an overkill to address what you reported. Are you sure you properly followed the steps above? are you sure you are using just get_it package and not any other?Corporator
The routes are set in a nav.dart, which uses GoRouter. This is generated as a class called FFRoute. I use flutterflow to generate the boilerplate code so I cannot change that. I think I see one of the issues -- I have two MaterialApp, one used in RunApp and the other in the Widget. I think I need to remove the one in the RunAppDipnoan
Let us continue this discussion in chat.Dipnoan
Hi Obum I think I see the issue: the app uses GoRouter, so I cannot use the navigationKey and attach to routes defined in the MaterialApp. The locator service works for passing data; are you familiar with the GoRouter? #73059950Dipnoan

© 2022 - 2024 — McMap. All rights reserved.