I'm using flutter's StreamBuilder
to decide which "root" page to show a user. Right now there are basically 2 possibilities - LoginPage
or HomePage
. My app's main build method looks like this:
Widget build(BuildContext context) => StreamProvider.value(
initialData: CurrentUser.initial,
value: AuthService().user,
child: Consumer<CurrentUser>(
builder: (context, currentUser, _) => MaterialApp(
home: currentUser.isInitialValue
? Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
)
: currentUser.user != null
? MultiProvider(providers: [
Provider<User>.value(value: currentUser.user),
// NOTE: Any other user-bound providers here can be added here
], child: HomePage())
: LoginPage())));
The relevant part of the login page is that it gives you two options:
When you click one of those buttons, you go to the form in a certain mode via the navigator:
Navigator.push(
context,
CupertinoPageRoute(
builder: (context) => LoginPage(
mode: LoginPageMode.signUp,
),
))
After successfully logging in or signing up, the stream gets updated and the HomePage
renders. I know this because I have a print statement in the build method ("Building HomePage"
):
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
print('Building HomePage');
return Scaffold(
body: Container(
alignment: Alignment.center,
child: Text('HOME'),
));
}
}
However, the screen does not actually change. The screen remains on the login page. I think I'm managing the stream fine because if I wasn't the render method would never be hit. I saw a similar question here but it seems they are not managing the stream properly. How can I see the render method hit, yet the screen stay the same? I've been working with Flutter for a while and I've never seen this before.
I think it has something to do with the navigation because if I remove the step where the user chooses "sign in" or "sign up", and I just send them to the sign in page, the issue disappears. It's like the HomePage
is being built under the page that was navigated to.
The reason of such behavior is that you use root Navigator that pushes LoginPage
route on the apps root level. Here is widgets tree reveals widgets relations after push:
App
|
|-- StreamBuilder
| |--LoginPage()
|
|-- LoginPage(mode: LoginPageMode.signUp)
So when your StreamBuilder changes data the tree becomes this:
App
|
|-- StreamBuilder
| |--HomePage() // <--- CHANGED
|
|-- LoginPage(mode: LoginPageMode.signUp)
Thats why you still see LoginPage
and at the same time HomePage
rendered too. HomePage
just lays "under" LoginPage
.
The solution is to use nested Navigator:
Widget build(BuildContext context) => StreamProvider.value(
initialData: CurrentUser.initial,
value: AuthService().user,
child: Consumer<CurrentUser>(
builder: (context, currentUser, _) => MaterialApp(
home: currentUser.isInitialValue
? Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
)
: currentUser.user != null
? MultiProvider(providers: [
Provider<User>.value(value: currentUser.user),
// NOTE: Any other user-bound providers here can be added here
], child: HomePage())
: Navigator( // <--- HERE
onGenerateRoute: (settings) {
return CupertinoPageRoute(
builder: (context) => LoginPage(),
);
},
)));
...
In that case when you will call Navigator.push(context, ...)
inside LoginPage
your widget tree will look like this:
App
|
|-- StreamBuilder
|-- Navigator()
|-- LoginPage()
|-- LoginPage(mode: LoginPageMode.signUp)
Please try this approach, it should work.