Flutter Webview with GoRouter (navigation 2.0) - how to handle back button press - flutter

According to this blog, if using Navigator 2.0 and/or (in my case) GoRouter you can no longer override the phone's back button using the "WillPopScope" and onWillPop function call. Navigator 2.0 now uses PopRoute to go back.
This causes an issue when using webview on a flutter page. If the user navigates to another web page within that webview and then clicks the back button on their phone they naturally expect the webview navigate back to the previous web page. But instead it takes the user off that page and back to their previous flutter page.
Is there any way around this? Can I have my back button first check whether there is a controller.canGoBack() like I used to be able to do with the old Navigator system?

I have found a solution. Convoluted, but functional:
I had to create a custom "backButtonDispatcher" and add it to the main.dart MaterialApp.router function
child: Builder(builder: (BuildContext context) {
final router = Provider.of<MainRouter>(context, listen: false).router;
backbuttondispatcher = backButtonDispatcher(router.routerDelegate, settings);
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routeInformationProvider: router.routeInformationProvider,
routerDelegate: router.routerDelegate,
backButtonDispatcher: backbuttondispatcher,
.
.
.
I created the new dispatcher in the router folder and called it "backbuttondispatcher.dart.
import 'package:flutter/material.dart';
class backButtonDispatcher extends RootBackButtonDispatcher {
final RouterDelegate _routerDelegate;
final _settings;
backButtonDispatcher(this._routerDelegate,this._settings)
: super();
Future<bool> didPopRoute() async {
//Can user leave the page?
if (!_settings.canLeavePage) {
//no, as the webview widget has flagged canLeavePage as false
_settings.goBackToPreviousWebsite();
return true;
}else{
//yes, perform standard popRoute call
return _routerDelegate.popRoute();
}
}
}
Using a shared class reference (I used "_settings") I store a flag that says whether or not the user has traversed through more than one web page - if TRUE, the back button dispatcher won't go back to a previous route/page and instead call another function (pointer) that handles going back to a previous web page in the webview widget route. But if FALSE, the dispatcher performs it's standard didPopRoute function.
Additionally, on all other routes/pages with a webview, the pointer function and boolean need to reset to null and false. This is not ideal but fortunately there aren't that many pages in the app.
It annoys me that they changed the back button functionality for main route/page navigation but didn't take in to consideration the fact that the back button can also be used for going back to a previous webpage. I understand that we shouldn't really be showing web pages with apps anyway but we lowly developers don't always have the power to deny app requirements from higher up.

Related

how to force build when ever a page shows up in flutter

Hello hope you guys are ok.
the problem I'm facing is we have a main_page which leads to a page doing some changes on data which are show on the main page.
after some process if the user touches back button and goes to main_page app loads it from stack and the data are not shown because it does not get rebuilt.
I don't want to control back button because there are other pages which lead to data changing page and I also tried using valuelistenablebuilder but I don't know why it goes wild and gets into a screen refresh loop without even changing the valueListenable I used redux to manage the value.
actual reason I want main page to rebuild is to call a method. why do I not call that method in second page is complicated and because i don't want to.
in conclusion I want main page to rebuild whenever it shows up even when it's read from the stack or even is there a way to tamper with stack of the pages without visual changes to the user.
you need to use Shared preferences plugin
https://pub.dev/packages/shared_preferences
If I understood the question correctly
The flow should be:
Screen A
#override
void initState() {
loadSomeDataFromDB();
super.initState();
}
Screen B
#override
void initState() { *//INIT AS EXAMPLE*
changeSomeDataFromDB();
super.initState();
}
You can try this in Screen A
onPressed: () async {
bool? shouldLoadDataAgain = await
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) =>
const ScreenB(),
));
if(shouldLoadDataAgain!=null&&shouldLoadDataAgain ==true){
loadSomeDataFromDB();
}
},
and this in Screen B when user press back button
onPressed: () async {
Navigator.of(context).pop(true)
},
solution for my problem was that used redux store had an object and updating object property or a list does not count as variable change to redux so the widget wouldn't rebuild.

Navigating to the updated page in flutter

When I insert data from other page and pop this page to go back on the listing page, I have to refresh the listing page to get the updated list of data. I want to go back to the updated version of page without pressing refresh button. I am using pushNamed and also I want to have a back arrow button on the input page.
Using Navigator.push or Navigator.pushNamed returns a Future object to you.
This Future resolves when you use Navigator.pop in the your second screen.
Here is a basic example. Assuming you have function goToNewPage which you are calling to push a new Screen, you can do it like this.
class Screen1State extends State<Screen1> {
void goToNewPage (context) {
Navigator.of(context).pushNamed('screen2').then((_) {
// The screen2 has popped. so you can just call setState here to just rebuild
// You can do anything you want here, that needs to happen after the new page has been popped.
setState(() {});
});
}
... rest of your code.
Note, the actual implementation of what you should do inside the callback depends on how your widget is getting data. But you can do anything you want to do after your new page is popped inside that callback.

Flutter detect when Navigator is ready

Working on a Flutter app, I am handling deeplinks with a SDK for which I am given a listener that I must handle as soon as the app starts, therefore in the main().
Once a deeplink is received, I must navigate to the proper screen, based on the parameters passed along the deeplink data.
Since I receive the deeplink in the main function, I am detached from the Context of the app, therefore to access the NavigatorState I used a navigation singleton with a GlobalKey passed to my CupertinoApp's navigatorKey. I later use this key to retrieve the NavigatorState and call push. (instead of Navigator.of(context)....
However, if opening the app from a deeplink, it is very likely that the navigatorKey does not contain anything (yet).
How can I detect/wait until the Navigator is ready ?
As of right now my approach is to add a WidgetsBinding.instance.addPostFrameCallback in my App's initState, that resolves a promise to indicate when the GlobalKey pointing to the NavigatorState can be used... I'm sure there is a beter way to achieve this.
For this i make the initialRoute as a Loading screen and execute all my logic inside it , that way i can have different initialRoutes and i don't have to worry about not having MediaQuery, or Navigator, or even ThemeData.
However the question specifies a way to know if the navigator is available, for this i would use the builder function of MaterialApp/WidgetsApp/CupertinoApp, it's used to override the navigator altought i use it to add pading and a global background color and even override the navigator widget with a custom one,
CupertinoApp(
...
builder: (context, child){
//The navigator is ready
Future.delayed(Duration.zero,
() => print(navigatorKey.currentContext)); //We use a delayed since the child needs to be returned first. It's kinda like a hack 7u7
return child;
}

Screen does not refresh after a Stream has changed in Flutter

In my Flutter application my '/' route is set to Wrapper(),
In that Wrapper, I have the following:
FirebaseUser user = context.watch<FirebaseUser>();
if (user == null) {
return Authentication();
} else {
return Home();
}
The problem I a having with this code is that if I update the Navigation stack with, for example, Navigator.pushNamed(context, "/login"), the return statement from above does not update the screen.
For instance, after the Authentication() page I redirect to either Login or Register page with Flutter's Navigator. And if the state of the FirebaseUser changes from one of those pages, I do not get redirected to Home() from my Wrapper(). I need to update the screen manually (press back on Android Emulator) and only then will I be redirected to Home().
I am also open to complete 'redesigns' of the structure of my app if needed.
EDIT: Also, do I even need the wrapper? I implemented it to make sure the user get to the right screen, since I did not know if there are any workarounds with the state of the app. Can I get by with just using the navigation without all of the Wrapper nonsense?

Calling Navigator.of anywhere without context?

I've been setting up a tabbed navigation app based on this wonderful tutorial (https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf).
Now I would like to display a modal overlay login route that covers the whole screen. My login controller checks if the user is logged in and I would like to fire an event on which the modal login route appears. The problem I have is now, that I don't have a context object where I receive the signal to display the login route:
Navigator.of(context).pushReplacementNamed('/');
How can I solve this or is this the wrong approach?
My User controller is a singleton object that gets initiated at app start. It checks then the user data model and if that is not set, it wants to invoke the login screen / route.
Thanks for any pointer in the right direction.
Martin
Define a navigator key that accesses from everywhere in-app (e.g in main class global space ) and pass it to root MaterialApp navigator key property in the build method
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
then :
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: navigatorKey,
//...
);
}
then you can access context everywhere with doing like this:
navigatorKey.currentContext
example of navigating:
Navigator.of(navigatorKey.currentContext).pushReplacement(
MaterialPageRoute(builder: (_) => SecondScreen()));
You could use a globalKey to access the context of the widget u like (you have to pass it a key in the constructor) , and leave it in the global space, or static in any class....
although not the most elegant approach, should work
you can use this package to skip the required context.
https://pub.dev/packages/one_context
// go to second page using named route
OneContext().pushNamed('/second');
// go to second page using MaterialPageRoute
OneContext().push(MaterialPageRoute(builder: (_) => SecondPage()));