Screen does not refresh after a Stream has changed in Flutter - 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?

Related

When opening app via Asset Links the navigation stack becomes wrong

I am using flutter_inappwebview 5.7.2+3 to open Chrome custom tabs on Android like this:
class EnrollBrowser extends ChromeSafariBrowser {
#override
void onOpened() {
logDebug("ChromeSafari browser opened");
}
#override
void onCompletedInitialLoad() {
logDebug("ChromeSafari browser initial load completed");
}
#override
void onClosed() {
logDebug("ChromeSafari browser closed");
}
}
Future openELoanBrowser() async {
final theUrl = await buildUrl();
try {
await EnrollBrowser().open(
url: Uri.parse(theUrl),
options: ChromeSafariBrowserClassOptions(
android: AndroidChromeCustomTabsOptions(
packageName: 'com.android.chrome',
shareState: CustomTabsShareState.SHARE_STATE_OFF,
keepAliveEnabled: true),
ios: IOSSafariOptions(barCollapsingEnabled: true)));
} catch (e) {
logError(StackTrace.current, 'Error opening browser: ${e.toString()}');
return false;
}
return true;
}
When ever I navigate, I use (it is a old project):
Navigator.of(context).push()
I have a strange problem.
If I have opened the Chrome custom tab and I then put the app in the background and bring it back to foreground, everything works as I expect, the complete navigation stack is preserved and my Chrome custom tab is on top.
I have a flow where the page opened in the custom tab opens a third party app than then in return opens my app again via Asset Links.
If I have "android:launchMode" set to "singleTop" or "singleTask" or "singleInstance" then my app, when opened with Asset Links, now has a very strange navigation stack. The custom tab that used to be and should be on top, now is on bottom and the rest of the navigation stack from before are now on top of my custom tab and I have to navigate back and back and back... until I get to it.
If I have "android:launchMode" set to "standard" then I am almost there. When my app is opened with Asset Links, the whole navigation stack is preserved, but one problem remains, my default "/" route is pushed on top of the custom tab.
I have tried calling:
Navigator.of(context, rootNavigator: true).pop();
when I detect that my "/" route has been pushed on top of my custom tab, but then I get a black screen. This is strange, because if acuallt manually navigate back on the device, it works and I get back to the custom tab.
So, with option 1. I do not get any routes added, but my custom tab that was at the top of the navigation stack now is at the bottom. Can I prevent this somehow? Then option 1. would probably be best.
With option 2. the correct navigation stack i preserved, but the "/" route is pushed on top of everything, and from code I can not pop this (even though I can manually).
How can this be? Any ideas to fix this?
Best regards and thank you
Søren

Flutter - How to check if dialog is shown when using go_router

I was using Navigator 1 then I migrated to go_router to support deep links and web.
Sometimes when I send HTTP requests, I show a loading dialog using showDialog() until the response is processed, after processing I need to check if a dialog is shown or not, if shown I dismiss it using Navigator.of(context).pop().
When I was using Navigator 1, I used to do that in this way:
if (ModalRoute.of(context)?.isCurrent == false) Navigator.of(context).pop();
But now after migrating to go_router this doesn't work as I found that ModalRoute.of(context) always equals to null whether there is a dialog shown or not.
I tried using if (Navigator.of(context).canPop()) Navigator.of(context).pop();, but this doesn't differentiate between dialogs and screens, if there is no dialog shown it pops the current screen.
You can do this by checking the location of GoRouter
if (GoRouter.of(context).location == '/dialog'){ 👈 Check here
context.pop();
}
Assuming your dialog route has path /dialog

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

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.

StreamBuilder is not able to re-route even though condition is true

I am building Flutter authentication app using firebase. Upon successful login, StreamBuilder gets new data but not redirecting to desired component, instead it stays on login page. Here is the code snippet:
StreamBuilder<UserModel>(
stream: _authService.user,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return DashboardScreen(snapshot.data);
}
return HomeScreen();
// return snapshot.hasData
// ? DashboardScreen(snapshot.data)
// : HomeScreen();
},
)
The only thing You should know that from HomeScreen user can choose Login or Register option. That will open up new screen(AuthScreen) and on that screen I've Login/Register form. after successful login/Register, it stays on the same page even though snapshot.hasData is true. Is that an issue? Is flutter not able to figure out previous route? I really appreciate some help here.
Imagine routes as a deck of cards, if you route to another screen, that's a new card on top of your current cards.
The StreamBuilder is probably updating just fine, you can check this by refreshing the Flutter Inspector widget tree, seeing there the DashboardScreen. But you won't see it on the screen because the route is on top of it.
After a successful login pop your route and see if works.

How to make my flutter app return the user to the OS home screen?

I'm working on an app that will have a lock screen, but I'm having some issues getting it to behave correctly. Right now I'm using the didChangeAppLifecycleState to navigate to the lock screen when the user suspends/resumes the app. I'm also using the WillPopScope to intercept and deny the back button. The problem with this approach is that pressing the back button and having nothing happen doesn't feel super intuitive. Ideally, I'd like the back button to take the user out of the app and back to their OS home screen when they're on the lock screen. I also want to do this in a way that the user's route history is maintained for when they successfully unlock the app.
Is there any way to accomplish what I'm trying to do, or should I just accept that the back button won't do anything on the lock screen?
You can create an identifier in your LockScreen state and check for the identifier in onWillPop and if the user is pressing the back button from the lock screen, exit the app.
String identifier = "lockscreen";
bool onWillPop() {
if (identifier == "lockscreen") {
SystemNavigator.pop();
SystemChannels.platform.invokeMethod('SystemNavigator.pop'); //preferred.*
return true;
}
}
SystemNavigator.pop(): On iOS, calls to this method are ignored because Apple's human interface guidelines state that applications should not exit themselves.