Flutter: Custom Navigator ModalRoute.of does not work properly - flutter

I want to change my selected tabIndex when user uses back button.
So, I edited my willPopScope to get current Route name. I am using
nested navigation. So my application has two navigators.
When I want to get current route name after pop action, It returning to me mainNavigatorKey's routeName.But I give it the coachNavigatorKey context..
Also I can not get the arguments from previous page data is always null.
ModalRoute.of(NavigatorKeys.coachNavigator.currentState!.context)!.settings.arguments;
WillPopScope(
onWillPop: () async {
await NavigatorKeys.coachNavigator.currentState!.maybePop();
var s = ModalRoute.of(NavigatorKeys.coachNavigator.currentState!.context)!.settings.name;
context.read<CoachMainCubit>().setTabIndexByName(s!);
return false;
},
Here is my Custom Navigator..
class CustomNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
final List<RouteObserver>? routeObserverList;
final Map<String, WidgetBuilder> routeList;
const CustomNavigator({Key? key, required this.navigatorKey, required this.routeList, this.routeObserverList}) : super(key: key);
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: "/",
observers: routeObserverList ?? [],
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (BuildContext context) => routeList[settings.name]!(context),
settings: RouteSettings(name: settings.name),
);
},
);
}
}
Here is how I call my nested navigator:
class CoachMainView extends StatelessWidget {
const CoachMainView({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (CoolAlertHelper().isLoadingDialogExists()) {
return false;
}
await NavigatorKeys.coachNavigator.currentState!.maybePop();
return false;
},
child: BlocProvider(
create: (context) => CoachMainCubit(context),
child: const CoachMainScreen(),
),
);
}
}
class CoachMainScreen extends StatelessWidget {
const CoachMainScreen({
Key? key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: buildCustomNavigator(context),
bottomNavigationBar: buildBottomNavigationBar(context),
);
}
Widget buildBottomNavigationBar(BuildContext context) {
return SafeArea(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: context.dynamicWidth(0.002), vertical: context.dynamicHeight(0.000)),
child: BlocSelector<CoachMainCubit, CoachMainState, int>(
selector: (state) {
return context.read<CoachMainCubit>().currentTabIndex;
},
builder: (context, state) {
return buildBottomNavBar(context);
},
),
),
);
}
CustomNavigator buildCustomNavigator(BuildContext context) {
return CustomNavigator(
navigatorKey: NavigatorKeys.coachNavigator,
routeObserverList: [CoachRouteObserver(context)],
routeList: CoachScreenConstants().getCoachRoutes(context),
);
}

Related

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(),
),
);
}

How to call a function in cubit class in initState?

So, I have made a function in Cubit Class it is to get data from API. For now I just can get the data if I pressed a button. I wanna make the function automatically called when the page/screen is open. For your information, this page is the first page that will be launched when user open the app. Here is some of my codes.
class UsersCubit extends Cubit<UsersState> {
UsersCubit() : super(UsersInitial());
UserRepository _userRepository = UserRepository();
void getAllUsers() async{
emit(UsersLoading());
try{
ResponseUsers _data = await _userRepository.getUsers();
emit(UsersSuccess(_data));
} catch(e){
emit(UsersError(e.toString()));
}
}
}
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocProvider(
create: (context) => UsersCubit(),
child: BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return Stack(
children: [
(state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar(),
ElevatedButton(
onPressed: (){
context.read<UsersCubit>().getAllUsers();
},
child: Text("GET USERS"),
)
],
);
},
),
),
);
}
}
I have tried to call the function directly in initState but when I run the app it returns an error.
#override
void initState() {
context.read<UsersCubit>().getAllUsers();
super.initState();
}
error:
Error: Could not find the correct Provider<UsersCubit> above this UsersPage 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 UsersPage is under your MultiProvider/Provider<UsersCubit>.
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>()),
}
),
}
Is there any way to solve this case?
Can you try wrapp BlocConsumer inside a Builder ?
import 'package:flutter/material.dart';
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocProvider(
create: (context) {
final cubit = UsersCubit();
cubit.getAllUsers();
return cubit;
},
child: Builder(builder: (context) {
return BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return Stack(
children: [
(state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar(),
ElevatedButton(
onPressed: (){
context.read<UsersCubit>().getAllUsers();
},
child: Text("GET USERS"),
)
],
);
},
);
}),
),
);
}
}
I have solved this case with help from #Ehsan Askari. He suggests me to provide the cubit above the MaterialApp, then I did it. Here is my code now
class AppWidget extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => UsersCubit(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: UsersPage(),
),
);
}
}
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
void initState() {
context.read<UsersCubit>().getAllUsers();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return (state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar();
},
),
);
}
}
you can call it from your Blocprovider by accessing your class like this :-
BlocProvider(
create: (context) => Usercubit()..getAllUsers(),
build : (context) => Scaffold() ........
You don't need to call a function inside initState when using Bloc or cubit, just call it when creating the Cubit inside BlocProvider like this >>.
class UsersPage extends StatefulWidget {
const UsersPage({Key? key}) : super(key: key);
#override
_UsersPageState createState() => _UsersPageState();
}
class _UsersPageState extends State<UsersPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Users")),
body: BlocProvider(
/// after create the cubit you can call the method.
create: (context) => UsersCubit()..getAllUsers();,
child: BlocConsumer<UsersCubit, UsersState>(
listener: (context, state) {
if(state is UsersLoading){
print("getting users ...");
} else if (state is UsersSuccess){
print(state.data.users[1].identity!.name);
} else if (state is UsersError){
print(state.errorMessage);
}
},
builder: (context, state) {
return Stack(
children: [
(state is UsersSuccess) ? listViewUsers(state.data.users) : progressBar(),
ElevatedButton(
onPressed: (){
context.read<UsersCubit>().getAllUsers();
},
child: Text("GET USERS"),
)
],
);
},
),
),
);
}
}

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: () {},
);
}));
}
}

How can i pass parameters in flutter if the user goes back with the arrow?

I know that if you had a raiseButton you can do
Navigation .... .pop(value);
But what happens if the user goes back and i want to update the value, because result will be null
Navigator.push(context, MaterialPageRoute(builder: (context) {
return GalleryClassOne();
})).then((result) {
if (result != null) {
setState(() {
imagesClas1 = result;
});
}
});
You can override the back button behavior with WillPopScope widget. And manually pop with the data you need. Here is the code:
import 'package:flutter/material.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _onButtonPressed() {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => OtherPage()))
.then((value) {
print("returned: $value");
if (value != null) {
setState(() {
// ...
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Demo")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text("Open another screen"),
onPressed: _onButtonPressed),
],
),
),
);
}
}
class OtherPage extends StatelessWidget {
OtherPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// here you can return anything you need ...
Navigator.of(context).pop("my value");
// cancel default behaviour
return false;
},
child: Scaffold(
appBar: AppBar(title: Text("Other page")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Click on return button'),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}
You should return your data at a variable like this
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
The result variable has your data.
for more info, have a look at the docs

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

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
),
);
}
}