Why is the BlocBuilder never called when bloc is not set explicitly - flutter

This is an example of a Flutter counter app. I instantiate the Counter with a Bloc like this:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Counter(CounterBloc()),
);
}
}
This code below works fine. Event is dispatched and the "builder" method is called.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: CounterPage(),
);
}
}
class CounterPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment)),
);
}
}
The code below does not work. The event is dispatched but the builder is never called.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment)),
),
);
}
}
I found out that I can set property "bloc" on a "BlocBuilder" but I'd expect it's not necessary.
Why the difference in behavior?

I believe the CounterEvent.increment from not working snippet won't get dispatched and instead will throw an error BlocProvider.of() called with a context ... because you use the same context where you provided the bloc.
This code works because it's a new context after BlocProvider
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Builder(
builder: (context) => Scaffold(
body: BlocBuilder<CounterBloc, int>(
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => BlocProvider.of<CounterBloc>(context)
.add(CounterEvent.increment),
),
),
),
);
}
}
This code also works because we explicitly use the bloc instance from the constructor instead of calling BlocProvider.of() and using the bloc instance provided via BlocProvider.
class Counter extends StatelessWidget {
final Bloc bloc;
const Counter(this.bloc, {Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider<CounterBloc>(
create: (context) => bloc,
child: Scaffold(
body: BlocBuilder<CounterBloc, int>(
bloc: bloc,
builder: (context, count) => CountView(count),
),
floatingActionButton: AddButton(
action: () => bloc.add(CounterEvent.increment),
),
),
);
}
}
Both snippets above will work but it's not exactly the "correct" way.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Counter(
CounterBloc(), // <=() You need a work around to dispose this instance
),
);
}
}

Related

How can I resolve this Flutter Navigator.push() error?

I am trying to move from one page to another via Navigator.push. Here is my code:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int count = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepOrange,
title: const Text("Noice!"),
),
body: ElevatedButton(
child: const Text('About'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AboutScreen(),
),
);
},
),
class AboutScreen extends StatelessWidget {
const AboutScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 0, 255, 8),
title: const Text("Flutter ballert")),
);
}
}
The code compiles and I can access the app, but clicking the button leads to an error:
Exception has occurred.
FlutterError (Navigator operation requested with a context that does not include a Navigator.
The context used to push or pop routes from the Navigator must be that of a widget that is a descendant of a Navigator widget.)
I know that in the offical docs the example for navigation is a tiny bit different. They dont return MaterialApp, instead they return Scaffold. Whats the difference between the two? Why should I return MaterialApp in the first place? And why doesnt it work with returning MaterialApp?
I dont know why this happens. I am new to flutter, so apologies if thats really trivial.
EDIT: Duplicate of:
Navigator operation requested with a context that does not include a Navigator
The problem is with the context. To fix this. Wrap your widget in a Builder() Class:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int count = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(builder: (context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepOrange,
title: const Text("Noice!"),
),
body: ElevatedButton(
child: const Text('About'),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AboutScreen(),
));
},
),
);
}),
);
}
}
class AboutScreen extends StatelessWidget {
const AboutScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 0, 255, 8),
title: const Text("Flutter ballert")),
);
}
}
Here is a YouTube video by the Google team explaining Builder
You need to pass the context:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
int count = 0;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.deepOrange,
title: const Text("Noice!"),
),
body: ElevatedButton(
child: const Text('About'),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AboutScreen(),
),
);
},
),
),
);
}
}
class AboutScreen extends StatelessWidget {
const AboutScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: const Color.fromARGB(255, 0, 255, 8),
title: const Text("Flutter ballert")),
);
}
}
Basically you did:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => AboutScreen(),
),
);
But you need to do this:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => AboutScreen(),
),
);

Could not find the correct Provider above this Test widget

======== Exception caught by gesture ===============================================================
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider above this Test Widget
This happens because you used a BuildContext that does not include the provider
of your choice. There are a few common scenarios:
You added a new provider in your main.dart and performed a hot-reload.
To fix, perform a hot-restart.
The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
You used a BuildContext that is an ancestor of the provider you are trying to read.
Make sure that Test is under your MultiProvider/Provider.
This usually happens when you are creating a provider and trying to read it immediately.
For example, instead of:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// Will throw a ProviderNotFoundError, because `context` is associated
// to the widget that is the parent of `Provider<Example>`
child: Text(context.watch<Example>()),
),
}
consider using builder like so:
Widget build(BuildContext context) {
return Provider<Example>(
create: (_) => Example(),
// we use `builder` to obtain a new `BuildContext` that has access to the provider
builder: (context) {
// No longer throws
return Text(context.watch<Example>()),
}
),
}
If none of these solutions work, consider asking for help on StackOverflow:
https://stackoverflow.com/questions/tagged/flutter
I am building an Widget "Test" to search users by their username. This is the widget Test with Bloc.
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: BlocListener<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
child: Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocBuilder<DonorsCubit, DonorsState>(
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState) return const Icon(Icons.circle);
return const SizedBox();
},
),
),
),
),
);
}
}
I used this cubit to manage states.
class DonorsCubit extends Cubit<DonorsState> {
List<MyUser> users = <MyUser>[];
final FirebaseDBRepo _firebaseDBRepo = FirebaseDBRepo();
late StreamSubscription _streamSubscription;
DonorsCubit() : super(DonorsInitialState()) {
_streamSubscription =
_firebaseDBRepo.usersStream().listen((List<MyUser> users) {
this.users = users;
});
}
void searchDonors({required String? searchKey}) {
emit(DonorsLoadingState());
List<MyUser> searchedUser = <MyUser>[];
searchedUser.clear();
if (searchKey == null) {
emit(DonorsLoadedState(users: users));
} else {
for (MyUser user in users) {
if (user.username!.toLowerCase().contains(searchKey.toLowerCase())) {
searchedUser.add(user);
}
}
emit(DonorsLoadedState(users: searchedUser));
}
}
#override
Future<void> close() {
_streamSubscription.cancel();
return super.close();
}
}
abstract class DonorsState extends Equatable {
const DonorsState();
}
class DonorsLoadingState extends DonorsState {
#override
List<Object> get props => [];
}
class DonorsInitialState extends DonorsState {
#override
List<Object> get props => [];
}
class DonorsLoadedState extends DonorsState {
final List<MyUser> users;
const DonorsLoadedState({required this.users});
#override
List<Object?> get props => [users];
}
The problem you get is related to how the provider package works. In order to access the cubit, you should provide it above in the widget tree. Now, you provide and listen to the cubit in the same context. There are several ways how you could handle it.
Use the Builder widget.
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: Builder(
builder: (context) => BlocListener<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
child: Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocBuilder<DonorsCubit, DonorsState>(
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState)
return const Icon(Icons.circle);
return const SizedBox();
},
),
),
),
),
),
);
}
}
Split your widget into two and provide your cubit in the parent widget:
class TestWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: const Test(),
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocListener<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
child: Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocBuilder<DonorsCubit, DonorsState>(
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState) return const Icon(Icons.circle);
return const SizedBox();
},
),
),
),
);
}
}
I am a fan of option 2 since it is more clear that you are splitting your code and working in separate contexts.
BONUS
Instead of using BlocListener and BlocBuilder separately, you could use the BlocConsumer widget:
class TestWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => DonorsCubit(),
child: const Test(),
);
}
}
class Test extends StatelessWidget {
const Test({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: IconButton(
onPressed: () {
context.read<DonorsCubit>().searchDonors(searchKey: "masum");
},
icon: BlocConsumer<DonorsCubit, DonorsState>(
listener: (context, state) {
print(state);
},
builder: (context, state) {
if (state is DonorsInitialState) return const Icon(Icons.add);
if (state is DonorsLoadedState) return const Icon(Icons.done);
if (state is DonorsLoadingState) return const Icon(Icons.circle);
return const SizedBox();
},
),
),
);
}
}
I have the same problem, I use the MultiProvider to list my providers like this:
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => Example()),
],
child: MaterialApp(
title: 'Example',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark().copyWith(
textTheme: GoogleFonts.poppinsTextTheme(Theme.of(context).textTheme)
),
// here I set my first screen...
home: HomePage(),
),
);
}

Passing Arguments between pages but show this error

I m trying to pass arguments between pages but I m getting this error:
FlutterError (Could not find a generator for route RouteSettings("detail", Instance of 'Commodity') in the _WidgetsAppState.
Make sure your root app widget has provided a way to generate
this route.
Generators for routes are searched for in the following order:
For the "/" route, the "home" property, if non-null, is used.
Otherwise, the "routes" table is used, if it has an entry for the route.
Otherwise, onGenerateRoute is called. It should return a non-null value for any valid route not handled by "home" and "routes".
Finally if all else fails onUnknownRoute is called.
Unfortunately, onUnknownRoute was not set.)
I don't know why because 'detail' page already setted
This is my Main App
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: WelcomePage.routeName,
routes: {
'/welcome': (_) => const WelcomePage(),
'detail': (_) => SubCommoditiePage(),
},
);
}
}
This is my From Page
class DisplayOptions extends StatelessWidget {
final List<Commodity> _optionsToDisplay;
const DisplayOptions(this._optionsToDisplay);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _optionsToDisplay.length,
itemBuilder: (context, index) {
final opt = _optionsToDisplay[index];
return ListTile(
title: Text(opt.label),
subtitle: Text(opt.shortLabel),
onTap: () {
Navigator.pushNamed(context, 'detail', arguments: _optionsToDisplay[index]);
});
});
}
}
This is my Destination Page
class SubCommoditiePage extends StatefulWidget {
const SubCommoditiePage({Key? key}) : super(key: key);
static const String routenName = 'detail';
static Route route() {
return MaterialPageRoute(
builder: (_) => const SubCommoditiePage(),
settings: const RouteSettings(name: routenName));
}
#override
_SubCommoditiePageState createState() => _SubCommoditiePageState();
}
class _SubCommoditiePageState extends State<SubCommoditiePage> {
#override
Widget build(BuildContext context) {
final Commodity commoditySeleted =
ModalRoute.of(context)?.settings.arguments as Commodity;
return Scaffold(
appBar: AppBar(
title: Text(commoditySeleted.label),
),
);
}
}
For initial/homepage, use /. In this case your WelcomePage.routeName will be /;
and route
routes: {
WelcomePage.routeName: (_) => WelcomePage(),
..........
},
You don't need to pass initialRoute while the root '/' will be selected.
For more about named-routes
The way I was able to solve it was:
I evidently didn't know why flutter didn't recognize my 'detail' page.
And I had to do it this way:
This is my From Page
in my ontap, I used
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SubCommoditiePage(
seleted: _optionsToDisplay[index],
)));
like that:
class DisplayOptions extends StatelessWidget {
final List<Commodity> _optionsToDisplay;
const DisplayOptions(this._optionsToDisplay);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: _optionsToDisplay.length,
itemBuilder: (context, index) {
final opt = _optionsToDisplay[index];
return ListTile(
title: Text(opt.label),
subtitle: Text(opt.shortLabel),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SubCommoditiePage(
seleted: _optionsToDisplay[index],
)));
});
});
}
}
adding to my page: final Commodity seleted; and with widget.selected... I was able to use it perfectly!
class SubCommoditiePage extends StatefulWidget {
final Commodity seleted;
const SubCommoditiePage({required this.seleted, Key? key}) : super(key: key);
static const String routenName = 'detail';
#override
_SubCommoditiePageState createState() => _SubCommoditiePageState();
}
class _SubCommoditiePageState extends State<SubCommoditiePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.seleted.label),
backgroundColor: const Color.fromRGBO(37, 59, 128, 5),
),
body: ListView.builder(
itemCount: widget.seleted.subCommodities.length,
itemBuilder: (context, index) {
final opt = widget.seleted.subCommodities[index];
return ListTile(
title: Text(opt.label),
onTap: () {},
);
}));
}
}

Could not find the correct Provider<T> above this BlocConsumer<T, S> Widget

First of all, I read almost all question related to this error and I couldn't find any answer to question.
I'm trying to use BlocProvider to provide a child widget with BlocProvider in SignInPage:
class SignInPage extends StatelessWidget {
const SignInPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Sign In'),
),
body: BlocProvider(
create: (context) => getIt<SignInFormBloc>(),
child: const SignInForm(),
),
);
}
}
And the SignInForm is:
class SignInForm extends StatelessWidget {
const SignInForm({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocConsumer<SignInFormBloc, SignInFormState>(
listener: (context, state) {
},
builder: (context, state) {
},
);
}
}
As I know, the BlocConsumer should search for the SignInFormBloc in parent widget, and the parent widget is BlocProvider which I provide it with SignInFormBloc I don't know why it couldn't find it
I try to wrap it in Builder(builder: (context) {} ) in case it searchs in wrong context but doesn't work too.
I made a mistake in AppRouter instated of using SignInPage I used SignInForm
#MaterialAutoRouter(
routes: [
AutoRoute(page: SplashPage, initial: false),
// It should be SignInPage -_-
AutoRoute(page: SignInForm, initial: true),
],
)
class $AppRouter {}

Flutter Provider nested navigation

I have a problem with provider and navigation.
I have a HomeScreen with a list of objects. When you click on one object I navigate to a DetailScreen with tab navigation. This DetailScreen is wrapped with a ChangenotifierProvider which provides a ViewModel
Now, when I navigate to another screen with Navigator.of(context).push(EditScreen) I can't access the ViewModel within the EditScreen
The following error is thrown
════════ Exception caught by gesture ═══════════════════════════════════════════
The following ProviderNotFoundException was thrown while handling a gesture:
Error: Could not find the correct Provider<ViewModel> above this EditScreen Widget
This is a simple overview of what I try to achieve
Home Screen
- Detail Screen (wrapped with ChangeNotifierProvider)
- Edit Screen
- access provider from here
I know what the problem is. I'm pushing a new screen on the stack and the change notifier is not available anymore.
I thought about creating a Detail Repository on top of my App which holds all of the ViewModels for the DetailView.
I know I could wrap the ChangeNotifier around my MaterialApp, but I don't want that, or can't do it because I don't know which Detail-ViewModel I need. I want a ViewModel for every item in the list
I really don't know what's the best way to solve this. Thanks everyone for the help
Here is a quick example app:
This is a picture of the image tree
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("DetailView"),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(
builder: (context) => ChangeNotifierProvider(
create: (_) => ViewModel(), child: DetailScreen()))),
)));
}
}
class DetailScreen extends StatelessWidget {
const DetailScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("EditScreen"),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => EditScreen())),
),
));
}
}
class EditScreen extends StatelessWidget {
const EditScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("Print"),
onPressed: () =>
Provider.of<ViewModel>(context, listen: false).printNumber()),
),
);
}
}
class ViewModel extends ChangeNotifier {
printNumber() {
print(2);
}
}
To be able to access providers accross navigations, you need to provide it before MaterialApp as follows
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => ViewModel(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
),
);
}
}
class MyHomePage extends StatelessWidget {
const MyHomePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("DetailView"),
onPressed: () => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => DetailScreen(),
),
),
)));
}
}
class DetailScreen extends StatelessWidget {
const DetailScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("EditScreen"),
onPressed: () => Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => EditScreen())),
),
));
}
}
class EditScreen extends StatelessWidget {
const EditScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("Print"),
onPressed: () =>
Provider.of<ViewModel>(context, listen: false).printNumber()),
),
);
}
}
class ViewModel extends ChangeNotifier {
printNumber() {
print(2);
}
}
A bit late to the party, but I think this is the answer the question was looking for:
(Basically passing the ViewModel down to the next Navigator page.)
class DetailScreen extends StatelessWidget {
const DetailScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final viewModel = Provider.of<ViewModel>(context); // Get current ViewModel
return Scaffold(
body: Center(
child: RaisedButton(
child: Text("EditScreen"),
onPressed: () => Navigator.of(context).push(
// Pass ViewModel down to EditScreen
MaterialPageRoute(builder: (context) {
return ChangeNotifierProvider.value(value: viewModel, child: EditScreen());
}),
),
),
));
}
}
I am a bit late but I found a solution on how to keep the value of a Provider alive after a Navigator.push() without having to put the Provider above the MaterialApp.
To do so, I have used the library custom_navigator. It allows you to create a Navigator wherever you want in the tree.
You will have to create 2 different GlobalKey<NavigatorState> that you will give to the MaterialApp and CustomNavigator widgets. These keys will allow you to control what Navigator you want to use.
Here is a small snippet to illustrate how to do
class App extends StatelessWidget {
GlobalKey<NavigatorState> _mainNavigatorKey = GlobalKey<NavigatorState>(); // You need to create this key for the MaterialApp too
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _mainNavigatorKey; // Give the main key to the MaterialApp
home: Provider<bool>.value(
value: myProviderFunction(),
child: Home(),
),
);
}
}
class Home extends StatelessWidget {
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>(); // You need to create this key to control what navigator you want to use
#override
Widget build(BuildContext context) {
final bool myBool = Provider.of<bool>(context);
return CustomNavigator (
// CustomNavigator is from the library 'custom_navigator'
navigatorKey: _navigatorKey, // Give the second key to your CustomNavigator
pageRoute: PageRoutes.materialPageRoute,
home: Scaffold(
body: FlatButton(
child: Text('Push'),
onPressed: () {
_navigatorKey.currentState.push( // <- Where the magic happens
MaterialPageRoute(
builder: (context) => SecondHome(),
),
},
),
),
),
);
}
}
class SecondHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bool myBool = Provider.of<bool>(context);
return Scaffold(
body: FlatButton(
child: Text('Pop'),
onPressed: () {
Novigator.pop(context);
},
),
);
}
}
Here you can read the value myBool from the Provider in the Home widget but also ine the SecondHome widget even after a Navigator.push().
However, the Android back button will trigger a Navigator.pop() from the Navigator of the MaterialApp. If you want to use the CustomNavigator's one, you can do this:
// In the Home Widget insert this
...
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_navigatorKey.currentState.canPop()) {
_navigatorKey.currentState.pop(); // Use the custom navigator when available
return false; // Don't pop the main navigator
} else {
return true; // There is nothing to pop in the custom navigator anymore, use the main one
}
},
child: CustomNavigator(...),
);
}
...