Closed abibiano closed 3 years ago
@abibiano Hi. In your situation it would look like this.
//MaterialApp...
child: Consumer(
builder: (context, watch, child) {
final state = watch(weatherStateNotifierProvider.state);
if (state is WeatherLoading) {
return buildLoading();
} else if (state is WeatherLoaded) {
return buildWeatherPage(context, state.weather);
}
},
),
//.....
And if you want only read data you can use StatelessWidget
and use context.read(myProvider).value;
. You don't need use Consumer
to read data.
@iamarnas thanks for your answer but I don't know where to put your code. In my current code I don't have a child property:
class AppWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer(
builder: (context, watch, child) {
final state = watch(authenticationStateNotifierProvider.state);
print(state);
return MaterialApp(
title: 'Test',
home: (state is Authenticated) ? SalesOrderListPage() : LoginPage(),
);
},
);
}
}
and I really don't know why this code is not working, because the state is updated and code inside build method is executed but home change is not displayed.
@abibiano home:
== child:
@iamarnas OK, I understant. I already tried this solution before and it works until I do a Navigator.pushReplacementNamed(context, ...) from my drawer for example to display a new page.
When I do the pushReplacementNamed then Consumer widget below my MaterialApp is deleted and updates to the state are no longer watched.
Do you have any suggestion how to avoid this problem?
@abibiano If you want return MaterialApp
use then ConsumerWidget
instead StatelessWidget
. Consumer
builder uses inside a widget tree. I'm bad at explaining but I hope you understood what I mean.
yes I understand, but if I replace the complete app StatelessWidget with a ConsumerWidget I have the same problema as with my initial code. Changes on the state don't force to rebuild the APP
yes I understand, but if I replace the complete app StatelessWidget with a ConsumerWidget I have the same problema as with my initial code. Changes on the state don't force to rebuild the APP
And you return as in the example it is the same problem?
return watch(myProvider).didComplete ? SignInPage() : HomePage();
When I do the pushReplacementNamed then Consumer widget below my MaterialApp is deleted and updates to the state are no longer watched.
Do you have any suggestion how to avoid this problem?
If you destroy provider when you navigate to another page. You can override them with ProvidedScope
. You can find example in the Marvel source in the documentation.
This is my code with ConsumerWidget. I think It's Ok. In fact if I print the state too debug I can see it gets updated inside the builder but the widget tree is not rebuild.
class AppWidget extends ConsumerWidget {
@override
Widget build(context, watch) {
final state = watch(authenticationStateNotifierProvider.state);
return MaterialApp(
title: 'TEST',
home: (state is Authenticated) ? SalesOrderListPage() : LoginPage(),
onGenerateRoute: AppRouteGenerator.generateRoute,
);
}
}
I think the problem is not with the provider because everything is fine. It seems the problem is with the MateriaApp that the home parameters update is not forcing a widget rebuild.
@abibiano I'm not sure but my first example should work.
@abibiano look at this example. Here is little advanced but in your situation you need only Consumer
Yes. Your first example works until I do a pushReplacementNamed on the app. After the pushReplacementNamed the consumer disapear on the widget tree. I will investigate more about this.
De: Arnas notifications@github.com Enviado: Wednesday, December 16, 2020 6:15:06 PM Para: rrousselGit/river_pod river_pod@noreply.github.com Cc: Alex Bibiano alex@bibiano.es; Mention mention@noreply.github.com Asunto: Re: [rrousselGit/river_pod] Consumer not updating MaterialApp home at widget rebuild (#250)
@abibianohttps://github.com/abibiano I'm not sure but my first example should work.
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHubhttps://github.com/rrousselGit/river_pod/issues/250#issuecomment-746660714, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAMQNRECDASGQ2PLO73ADXLSVDTJVANCNFSM4U6BE7OA.
Could you provide a full example?
The snippet you gave cannot be executed and there is no obvious issue with it
@abibiano I can accept that here is not issue with Riverpod. I have tested you StateNotifier
and I have no issue and work very well. Here's what my little app looks like:
// View.
class App extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
final state = watch(authenticationNotifier.state);
return MaterialApp(
home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
);
}
}
// Controller.
final authenticationNotifier = StateNotifierProvider((ref) => AuthenticationNotifier());
class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
AuthenticationNotifier() : super(Unauthenticated());
void toggle() {
state = state is Unauthenticated ? Authenticated() : Unauthenticated();
}
}
// State.
abstract class AuthenticationState {}
class Authenticated extends AuthenticationState {}
class Unauthenticated extends AuthenticationState {}
@iamarnas I have created a very simple example to demostrate the issue on abibiano/riverpod_test
If you test the app you will see state management works between login and home page as expected with your code, but as soon as you navigate to the second screen form the home page and press the logout button, state is changed on the App widged but widget tree is not updated.
Here the code for reference:
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/all.dart';
void main() {
runApp(ProviderScope(child: MyApp()));
}
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, ScopedReader watch) {
final state = watch(authenticationNotifier.state);
print(state);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: state is Unauthenticated ? LoginScreen() : HomeScreen(),
onGenerateRoute: (RouteSettings settings) {
if (settings.name == '/second')
return MaterialPageRoute(builder: (_) => SecondScreen());
else
return MaterialPageRoute(builder: (_) => HomeScreen());
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('HomeScreen'),
),
body: Column(
children: [
MaterialButton(
child: Text('Logout'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
MaterialButton(
child: Text('Second'),
onPressed: () => Navigator.pushReplacementNamed(
context,
'/second',
),
),
],
),
);
}
}
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: MaterialButton(
child: Text('Logout'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
);
}
}
class LoginScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('LoginScreen'),
),
body: MaterialButton(
child: Text('Login'),
onPressed: () => context.read(authenticationNotifier).toggle(),
),
);
}
}
// Controller.
final authenticationNotifier =
StateNotifierProvider((ref) => AuthenticationNotifier());
class AuthenticationNotifier extends StateNotifier<AuthenticationState> {
AuthenticationNotifier() : super(Unauthenticated());
void toggle() {
state = state is Unauthenticated ? Authenticated() : Unauthenticated();
}
}
// State.
abstract class AuthenticationState {}
class Authenticated extends AuthenticationState {}
class Unauthenticated extends AuthenticationState {}
@abibiano Hi,
It's nothing to do with providers. You replace your HomeScreen
with SecondScreen
and you provider work in the background of SecondScreen
. You need just navigate back to the LoginScreen
when user click logout button and of course update state of AuthenticationNotifier
Your SecondScreen
would look like that:
class SecondScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: Center(
child: MaterialButton(
child: Text('Logout'),
onPressed: () {
Navigator.pushReplacementNamed(context, '/'); // <- Just add this.
context.read(authenticationNotifier).logout(); // <- Update state.
},
),
),
);
}
}
Lot of thanks for your time. You are right!
@iamarnas Excellent answer. I was struggling for a few hours with almost the same problem where consumer was not rebuilding the MaterialApp when the user logged in again and the MaterialApp.home definition was not respected. The problem was caused in logout function where I pushed a route to loginScreen what was completely unnecessary and causing the problem. The definition of home in MaterialApp was such that loginScreen is displayed whenever the user is not authenticated, otherwise homeScreen. When I read your answer the problem was immediately solved. Thanks for pointing that out.
MultiProvider(
[
....
ChangeNotifierProvider.value(value: Auth()),
]
),
child: Consumer
I have an authentication StateNotifier class and StateNotifierProvider and have wrapped the MaterialApp widget into a Consumer to rebuild the complete app/widget tree when user is no longer authenticated.
When I update the state of the authenticationStateNotifierProvider from another page/widget and I debug, I can see the build method of the consumer wrapping the App is triggered and the state is updated (print(state)) but the home page and widget tree is not rebuilt.
I’m migrating this app from using provider package and with the provider package it works.
Sample code using Riverpot + StateNotifier
Sample code using provider