Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Easily navigate, show dialogs, menus, bottom sheets and scaffold outside the widget tree without BuildContext [New UPDATA] #129

Closed
GIfatahTH opened this issue Sep 8, 2020 · 6 comments

Comments

@GIfatahTH
Copy link
Owner

With states_rebuilder, you can perform side effects that require a BuildContext without being forced to be in the widget tree.

In order for states_rebuilder to navigate and display dialogs without a BuildContext, we need to set the navigatorKey of the MaterialApp widget and assign it to RM.navigate.navigatorKey.

MaterialApp(
  //set the navigator key
  navigatorKey: RM.navigate.navigatorKey,
..
)

Navigation

states_rebuilder follows the naming convention as in Flutter SDK, with one minor change:

  • In Flutter push becomes to in states_rebuilder.
  • In Flutter pop becomes back in states_rebuilder.

1- push a route:

 RM.navigate.to(NextPage()); //Flutter: push

2- push a named route:

 RM.navigate.toName('route-name'); //Flutter: pushNamed

3- push a route and replace the current one:

 RM.navigate.toReplacement(NextPage()); //Flutter: pushReplacement

4- push a route and replace the current one:

 RM.navigate.toReplacementNamed('route-name'); //Flutter: pushReplacementNamed

5- push a route and remove util the route of the given name:

 RM.navigate.toAndRemoveUntil(NextPage(), 'previous-route-name'); //Flutter: pushAndRemoveUntil
 //
 // if 'route-name' is omitted, all previous routes are removed
 RM.navigate.toAndRemoveUntil(NextPage());  //The route stack contains only NextPage.

6- push named route and remove util the route of the given name:

 RM.navigate.toNamedAndRemoveUntil('route-name', 'previous-route-name'); //Flutter: pushNamedAndRemoveUntil
 //
 // if 'route-name' is omitted, all previous routes are removed
 RM.navigate.toNamedAndRemoveUntil(NextPage());//The route stack contains only NextPage.

7- pop a route:

 RM.navigate.back();//Flutter: pop

8- pop all routes until we reach a previous route name:

 RM.navigate.backUntil(''previous-route-name''); //Flutter: popUntil

9- pop the current route and push to a named route:

 RM.navigate.backAndToNamed(''previous-route-name''); //Flutter: popAndPushNamed

10- For any other navigation option, you can use the navigatorState exposed by states_rebuilder:
example:

 RM.navigate.navigatorState.pushNamedAndRemoveUntil<T>(
      'newRouteName',
      (route) => false,
    )

Dialogs and Sheets

Dialogs when displayed are pushed into the route stack. It is for this reason, in states_rebuilder, dialogs are treated as navigation:

In Flutter to show a dialog:

showDialog<T>(
    context: navigatorState.context,
    builder: (_) => Dialog(),
);

In states_rebuilder to show a dialog:

RM.navigate.toDialog(Dialog());

For sure, states_rebuilder is less boilerplate, but it is also more intuitive.
In states_rebuilder we make it clear that we are navigating to the dialog, so to close a dialog, we just pop it from the route stack.

So states_rebuilder follows the naming convention as in Flutter SDK, with the change from show in Flutter to to in states_rebuilder.

1- Show a material dialog:

 RM.navigate.toDialog(DialogWidget());//Flutter: showDialog

2- Show a cupertino dialog:

 RM.navigate.toCupertinoDialog(CupertinoDialogWidget());//Flutter: showCupertinoDialog

3- Show a cupertino dialog:

 RM.navigate.toBottomSheet(BottomSheetWidget());//Flutter: showModalBottomSheet

4- Show a cupertino dialog:

 RM.navigate.toCupertinoModalPopup(CupertinoModalPopupWidget());//Flutter: showCupertinoModalPopup

5- For all other dialogs, menus, bottom sheets, not mentioned here, you can use is as defined by flutter using RM.context:
example:

 showSearch(
     context: RM.context, 
     delegate: MyDelegate(),
 )

Show bottom sheets, snackBars and drawers that depend on the scaffolding

Some side effects require a BuildContext of a scaffold child widget.

In state_states_rebuilder to be able to display them outside the widget tree without explicitly specifying the BuildContext, we need to tell states_rebuild which BuildContext to use first.

This can be done either:

     onPressed: (){
      RM.scaffoldShow.context= context;
      RM.scaffoldShow.bottomSheet(...);
     }

Or

    onPressed: (){
    modelRM.setState(
      (s)=> doSomeThing(),
      context: context,
      onData: (_,__){
        RM.scaffoldShow.bottomSheet(...);
      )
    }
  }

If you have one of the states_rebuilder widgets that is a child of the Scaffold, you no longer need to specify a BuildContext. The BuildContext of this widget will be used.

Since SnackBars, for example, depend on ScaffoldState and aren't pushed to the route stack, we don't treat them as navigation like we did with dialogs.

To distinguish them from Dialogs and to emphasize that they need a Scaffold-related BuildContext, we use RM.scaffoldShow instead of RM.navigate.

1- Show a persistent bottom sheet:

 RM.scaffoldShow.bottomSheet(BottomSheetWidget());//Flutter: Scaffold.of(context).showBottomSheet

2- Show a snackBar:

 RM.scaffoldShow.snackBar(SnackBarWidget());//Flutter: Scaffold.of(context).showSnackBar

3- Open a drawer:

 RM.scaffoldShow.openDrawer();//Flutter: Scaffold.of(context).openDrawer

4- Open a end drawer:

 RM.scaffoldShow.openEndDrawer();//Flutter: Scaffold.of(context).openEndDrawer

5- For anything, not mentioned here, you can use the scaffoldState exposed by states_rebuilder.

@GIfatahTH GIfatahTH mentioned this issue Sep 8, 2020
@webskydavid
Copy link

webskydavid commented Oct 25, 2020

Hi, I started using RM.navigate.toReplacement() and figured out that after navigating to another page the registered models are gone.

Exception: |

***The model [CameraService] is not registered yet***
| You have to register the model before calling it.
| 
| To register the model use the `Injector` widget.
| You can set the silent parameter to true to silent the error.
| 
| This is the list of registered models: ().

Main widget

MaterialApp(
      title: 'Lumber-dev',
      theme: ThemeData(
        primarySwatch: Colors.grey,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      navigatorKey: RM.navigate.navigatorKey,
      home: Injector(
        inject: [
          Inject.future(() => Firebase.initializeApp()),
          Inject<CameraService>(() => CameraService()),
          Inject<LumberService>(() => LumberService()),
        ],
        disposeModels: true,
        builder: (context) {
          return WhenRebuilder<FirebaseApp>(
            key: Key('MAIN_KEY'),
            observe: () => RM.get<FirebaseApp>(),
            onWaiting: () => LinearProgressIndicator(),
            onIdle: () => Text('Idle'),
            onError: (error) => Text('Error'),
            onData: (data) {
              //freturn AddLumberScreen();
              return LumberListScreen();
              //return Text('fefefe');
            },
          );
        },
      ),
    );,

I Inject the models at the beginning of the main widget. The other widgets use WhenRebuilderOr class to get data.

With RM.navigate.to() the models are stil registered but it seems that the models are not disposed of, and after coming back it reruns my initState multiple times.

[ReactiveModel Notification] RM <CameraService>. Tag: no tags
[ReactiveModel Notification] hasData : Instance of 'CameraService'
[states_rebuilder Widget Rebuild] WhenRebuilderOr(11376091) |[<'LIST_KEY'>] | <CameraService>(hasData)
[states_rebuilder Widget Rebuild] |# 2 #| rebuild times
[states_rebuilder Widget Rebuild] WhenRebuilderOr(862795830) |[<'INIT_KEY'>] | <CameraService>(hasData)
[states_rebuilder Widget Rebuild] |# 2 #| rebuild times
[states_rebuilder Widget Rebuild] WhenRebuilderOr(139288740) |[<'LIST_KEY'>] | <CameraService>(hasData)
[states_rebuilder Widget Rebuild] |# 4 #| rebuild times
[states_rebuilder Widget Rebuild] WhenRebuilderOr(490457391) |[<'INIT_KEY'>] | <CameraService>(hasData)
[states_rebuilder Widget Rebuild] |# 4 #| rebuild times
...

Am I doing something wrong?
I do not fully understand why after using .toReplacement() the registration of models disappears. And after using .to() it rebuilds the model multiple times.
Thanks in advance ;)

@GIfatahTH
Copy link
Owner Author

Models that you do not want them to dispose of after navigation, inject them above the MaterialApp widget. Try to inject CameraService abovethe MaterialApp and see!

@2shrestha22
Copy link

I am using FCM for push notification. onResume and onMessage (while app is open or in background) handler does proper navigation to a page but onLaunch (after completely exiting the app) shows homepage instead of the desired page.
Inside my service model I have,

 _fcm.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
        _serialiseAndNavigate(message);
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
        _serialiseAndNavigate(message);
      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume is called with message: $message");
        _serialiseAndNavigate(message);
      },
    );

and

 void _serialiseAndNavigate(Map<String, dynamic> message) {
    var notificationData = message['data'];
    var postIdString = notificationData['post_id'];

    if (postIdString != null) {
      // Navigate to the showPostById
      RM.navigate.to(FromNotificationPage(
        postId: int.parse(postIdString),
      ));
    }
  }

Am I doing it wrong? Is there other way to do this?

@2shrestha22
Copy link

2shrestha22 commented Nov 29, 2020

I checked log and onLaunch is triggering _serialiseAndNavigate() with correct data, but is not navigating to correct destination. Other ways to achieve this?

@2shrestha22
Copy link

2shrestha22 commented Nov 29, 2020

Finally I got some log,
Here is the log,

11-29 08:19:08.660 32493 32549 I flutter : flutter should navigate to: 25529
11-29 08:19:08.667 32493 32549 E flutter : [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: 'package:states_rebuilder/src/reactive_model/rm.dart': Failed assertion: line 541 pos 12: 'navigatorState != null': The MaterialApp has no defined navigatorKey.
11-29 08:19:08.667 32493 32549 E flutter : 
11-29 08:19:08.667 32493 32549 E flutter : To fix:
11-29 08:19:08.667 32493 32549 E flutter : MaterialApp(
11-29 08:19:08.667 32493 32549 E flutter :    navigatorKey: RM.navigate.navigatorKey,
11-29 08:19:08.667 32493 32549 E flutter :    //
11-29 08:19:08.667 32493 32549 E flutter :    //
11-29 08:19:08.667 32493 32549 E flutter : )
11-29 08:19:08.667 32493 32549 E flutter : 
11-29 08:19:08.667 32493 32549 E flutter : #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:46:39)
11-29 08:19:08.667 32493 32549 E flutter : #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:36:5)
11-29 08:19:08.667 32493 32549 E flutter : #2      _Navigate.navigatorState (package:states_rebuilder/src/reactive_model/rm.dart:541:12)
11-29 08:19:08.667 32493 32549 E flutter : #3      PushNotificationService._serialiseAndNavigate (package:wordpress_app/data_source/fcm_push_notification.dart:58:25)
11-29 08:19:08.667 32493 32549 E flutter : #4      PushNotificationService.initialise.<anonymous closure> (package:wordpress_app/data_source/fcm_push_notification.dart:37:9)
11-29 08:19:08.667 32493 32549 E flutter : #5      FirebaseMessaging._handleMethod (package:firebase_messaging/firebase_messaging.dart:194:25)
11-29 08:19:08.667 32493 32549 E flutter : #6      MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:430:55)
11-29 08:19:08.667 32493 32549 E flutter : #7      MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:383:34)
11-29 08:19:08.667 32493 32549 E flutter : #8      _DefaultBinaryMessenger.handlePlatformMessage (package:flutter/src/services/binding.dart:283:33)
11-29 08:19:08.667 32493 32549 E flutter : #9      _invoke3.<anonymous closure> (dart:ui/hooks.dart:280:15)
11-29 08:19:08.667 32493 32549 E flutter : #10     _rootRun (dart:async/zone.dart:1190:13)
11-29 08:19:08.667 32493 32549 E flutter : #11     _CustomZone.run (dart:async/zone.dart:1093:19)
11-29 08:19:08.667 32493 32549 E flutter : #12     _CustomZone.runGuarded (dart:async/zone.dart:997:7)
11-29 08:19:08.667 32493 32549 E flutter : #13     _invoke3 (dart:ui/hooks.dart:279:10)
11-29 08:19:08.667 32493 32549 E flutter : #14     _dispatchPlatformMessage (dart:ui/hooks.dart:154:5)

But I already have navigatorKey: RM.navigate.navigatorKey, in my MaterialApp(),

return isDarkMode.rebuilder(() => MaterialApp(
          title: 'Material App',
          theme: isDarkMode.state ? AppTheme.dark : AppTheme.light,
          initialRoute: '/',
          onGenerateRoute: router.Router.generateRoute,
          debugShowCheckedModeBanner: false,
          navigatorKey: RM.navigate.navigatorKey,
        ));

@2shrestha22
Copy link

2shrestha22 commented Nov 29, 2020

It worked after using

RM.navigate.to(FromNotificationPage(
        postId: int.parse(postIdString),
      ));

directly inside onLaunch. Problem is solved. State Rebuilder is awesome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants