How can I avoid creating another instance of some bloc class?
I have two blocs: LoginBloc and AuthBloc . and LoginBloc accepts an instance of AuthBLoc and here is problem:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final UserRepository repository=UserRepository();
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => LoginBloc(
//******Extra Instance of AuthBloc in being created here because LoginBloc needs it to listen********
authBloc: AuthBloc(SInitialState(),userRepository: repository), userRepository:repository
),
),
BlocProvider(
create: (context) => AuthBloc(SInitialState(),userRepository: repository),
)
],
child: MaterialApp(...);
Thanks in advance.
You can assign AuthBloc to a variable inside build() method.
Or, just nest one BlockProvider inside another one:
BlocProvider<AuthBloc>(
create: (context) => AuthBloc(SInitialState(),userRepository: repository),
child: BlocBuilder<AuthBloc, AuthState>(
builder: (BuildContext context, AuthState authState) {
return BlocProvider(
create: (context) => LoginBloc(
authBloc: context.bloc<AuthBloc>(),
userRepository: repository,
),
child: MaterialApp(...),
)
})
),
But the best solution is Bloc to Bloc communication
if I got you question correctly, you might use GetIt
and here is a sample code
MultiBlocProvider(
providers: [
BlocProvider<SginInBloc>(
create: (BuildContext context) => sl<SginInBloc>(),
child: SginInPage(),
),....
so that you will get access to the instans of that Bloc that will be created
final sl = GetIt.instance;
sl.registerFactory(() => SginInBloc();
here I'm using registerFactory but you can use registerSingleton depends on what you want to achive
void main() {
final UserRepository repository=UserRepository();
runApp(MyApp(authBloc: AuthBloc(),userRepo : repository));
}
class MyApp extends StatelessWidget {
final AuthBloc authBloc;
final UserRepository userRepo;
MyApp({required this.authBloc,required this.userRepo});
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => LoginBloc(
//******Extra Instance of AuthBloc in being created here because LoginBloc needs it to listen********
authBloc: authBloc,userRepository: userRepo), userRepository:userRepo
),
),
BlocProvider(
create: (context) => authBloc,
)
],
child: MaterialApp(...);
Try this
Related
I have two screens in my flutter application Screen1 and Screen2. Screen1 is the home screen. I navigate from Screen1 to Screen2 via
Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2());
and Screen2 to Screen1 via
Navigator.pop(context);
Screen1 is statelesswidget:
class Screen1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(create: (_) => BlocA()),
BlocProvider<BlocB>(create: (_) => BlocB()),
]
child: RaisedButton(
child: Text('Goto Screen 2'),
onPressed: Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2());
),
)
}
}
I would appreciate anyone can provide an answer that will satisfy the following :
Want to access the two bloc initialised in the Screen1 from Screen2 using
BlocProvider.value(value: BlocProvider.of(context), child: ...)
without bringing the initialisation of blocs upto the MaterialApp widget. Cannot make the MultiBlocProvider the parent of MaterialApp. I want the blocs only accessed in Screen1 and Screen2. It should not be accessed by other screens.
Also when popped from Screen2 to Screen1, the blocs should not be disposed. Hence, continue to maintain state when popped from Screen2
Should not pass the bloc via constructor or as arguments in Navigator
Currently getting following error:
flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building Screen2(dirty):
flutter: BlocProvider.of() called with a context that does not contain a BlocA.
flutter: No ancestor could be found starting from the context that was passed to
flutter: BlocProvider.of<BlocA>().
flutter:
flutter: This can happen if the context you used comes from a widget above the BlocProvider.
flutter:
flutter: The context used was: Screen2(dirty)
The use the already created bloc instance on new page, you can use BlocProvider.value.
Like passing BlocX to next route will be like
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<BlocX>(context),
child: Screen2(),
),
),
);
I might go for repository provider on your case. But to pass multiple instance, you can wrap BlocProvider two times on route.
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: BlocProvider.value(
value: BlocProvider.of<BlocB>(context),
child: Screen2(),
),
),
),
);
Currently, I cannot remember any better option, let me know if you've got any.
Now, your second route Screen2 can access both BlocB and BlocB instance.
You can get the instance it like, depend on your code structure.
BlocConsumer<BlocA, BlocAState>(
builder: (context, state) {
if (state is BlocAInitial) {
return Text(state.name);
}
return Text("un impleneted");
},
listener: (context, state) {},
),
When you create bloc, and like to pass it with BlocProvider.value(value: BlocProvider.of<BlocA>(context),, you need to use separate context.
More about blocprovider.
Check the demo, It will clarify, I am using Builder instead of creating new widget for context.
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Screen1(),
);
}
}
class Screen1 extends StatelessWidget {
const Screen1({super.key});
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<BlocA>(create: (_) => BlocA()),
BlocProvider<BlocB>(create: (_) => BlocB()),
],
child: Builder(builder: (context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(
builder: (_) => BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: BlocProvider.value(
value: BlocProvider.of<BlocB>(context),
child: Screen2(),
),
),
),
);
},
),
);
}),
);
}
}
class Screen2 extends StatelessWidget {
const Screen2({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
BlocConsumer<BlocA, BlocAState>(
builder: (context, state) {
if (state is BlocAInitial) {
return Text(state.name);
}
return Text("un impleneted");
},
listener: (context, state) {},
),
BlocConsumer<BlocB, BlocBState>(
builder: (context, state) {
if (state is BlocBInitial) {
return Text(state.name);
}
return Text("un impleneted");
},
listener: (context, state) {},
),
],
),
);
}
}
Find more about flutterbloccoreconcepts
you have to elevate MultiBlocProvider in the widget tree so that it wraps both screens, e.g. make it a parent of MaterialApp
You can pass bloc elements as a parameter to Screen2
final blocAObject = BlocProvider.of<BlocA>(context);
Navigator.of(context).push(PageRouteBuilder<void>(pageBuilder: (context, animation, secondaryAnimation) => Screen2(bloca:blocAObject));
If you're ok with initializing in MaterialApp while only having the blocs accessible from the two screens, try the following:
final blocA = BlocA(); // shared bloc instance
final blocB = BlocB(); // shared bloc instance
#override
Widget build(BuildContext context) {
return MaterialApp(
routes: {
'screen1': (_) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => blocA,
),
BlocProvider(
create: (context) => blocB,
),
],
child: Screen1(),
),
'screen2': (_) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => blocA,
),
BlocProvider(
create: (context) => blocB,
),
],
child: Screen2(),
),
},
);
}
Problem: Some of my providers cannot be found in the context above the modal bottom sheet. Example:
Error: Could not find the correct Provider above
this ModalEnterTask Widget
This happens because you used a BuildContext that does not include
the provider of your choice.
All Providers are definetly above the widget opening the modal sheet. One provider is actually working. That one is created above the material app. The ones not working are created in the build method of my tabs screen. I've been using them sucesfuly all throughout the app. Why can they not be found in the modal sheet?
My theory: The context used for the modal sheet is dependend on the Material app context; not on the context from where it is opened in the widget tree. Correct?
Solution: I don't know. I can't move the providers up to where the working Provider sits. They need context information (edit: MediaQuery information, not context), so they cannot be initialized before the material app.
Code:
MyApp State...this is where I initialize the provider that works:
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => MainElementList(widget.mainElementList),
),
ChangeNotifierProvider(
create: (context) => ScrollControllerToggles(),
)
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Three Things',
initialRoute: '/',
routes: {
'/': (ctx) => TabsScreen(),
},
),
);
}
}
The TabsScreen(), here I initialize the Providers that do not work in the modal sheet:
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (ctx) => CustomColors(customColorScheme),
),
//
ChangeNotifierProvider(
create: (ctx) => DimensionsProvider(
appBarHeight: appBarHeight,
availableHeight: availableHeight,
availableWidth: availableWidth),
),
//
ChangeNotifierProvider(
create: (ctx) => CustomTextStyle(availableHeight, customTextTheme),
),
],
child: Scaffold(//body: function picking pages)
Calling the modal sheet:
return GestureDetector(
onTap: () => showModalBottomSheet(
context: context,
builder: (bctx) => ModalEnterTask(positionTask: positionTask),
),
//
child: Center(//container with an icon)
The widget called in the builder of the the modal sheet:
class ModalEnterTask extends StatelessWidget {
late String positionTask;
ModalEnterTask({required String this.positionTask, Key? key})
: super(key: key);
#override
Widget build(BuildContext context) {
//RESPONSIVENESS
final availableHeight =
Provider.of<DimensionsProvider>(context).availableHeight;
return Column(
children: [
SizedBox(
height: 10,
),
//
IconButton(
icon: Icon(Icons.plus_one),
onPressed: () {
Provider.of<MainElementList>(context, listen: false)
.changeSingleTaskPhase(0, positionTask);
Navigator.of(context).pop();
},
),
],
);
}
}
DimensionProvider > doesn't work
MainElementList > works
As I can see you are getting error because your provider is not in the tree which you are calling so its better to include all providers in the main and you will be able to resolve this issue. Here is how you do that
void main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => CustomColors()),
ChangeNotifierProvider(create: (context) => DimensionsProvider()),
ChangeNotifierProvider(create: (context) => MainElementList()),
ChangeNotifierProvider(create: (context) => ScrollControllerToggles()),
],
child: MyApp(),
),
);
}
I think this solution will work in your case... Have a try and let me know
Note: I can see that you are passing some parameters in providers so just change a structure little bit and assign values to providers where you are initializing them
I got same error when using method showModalBottomSheet.
I try to use BlocBuilder in the widget that was open as a modal bottom sheet, then i got
BlocProvider.of() called with a context that does not contain a MyBloc
My solutions is:
pass the value as a parameter of widget. In my case is:
showModalBottomSheet(
context: context,
builder: (BuildContext btsContext) {
return CartBottomSheetPage(
cartBloc: BlocProvider.of<MyBloc>(context),
);
},
);
NOTE: you must get the value from parent context like this:
BlocProvider.of<MyBloc>(context)
not like this:
BlocProvider.of<MyBloc>(btsContext)
How to use Bloc in flutter. What is the best way to use it? to wrap the whole app with blocprovider?
runApp(
RepositoryProvider(
create: (context) => API(),
child: MultiBlocProvider(
providers: [
BlocProvider<GlobalViewBloc>(
lazy: false,
create: (BuildContext context) =>
GlobalViewBloc(context.read<API>()),
),
BlocProvider<CountryDetailViewBloc>(
lazy: false,
create: (BuildContext context) =>
CountryDetailViewBloc(context.read<API>()),
),
],
child: MaterialApp(
home:MyApp(),
),
),
));
there are two ways of accessing bloc:
1 - global declaration
you declare a bloc variable inside your bloc and all widgets and whole app will have access to that variable. like this:
final bloc = YourBloc();
2 - using provider
in this declaration you have to define the provider in the highest widget which you want having the access to bloc and all of its children will have access to that bloc:
class WD extends StatelessWidget {
#override
Widget build(BuildContext context) {
final bloc = YourBlocProvider.of(context);
/....
);
}
}
I'm fetching data from the local database using SQflite in my main.dart and passing it to ProvideRecords widget using FutureProvider :
Future<List<WeightRecord>> _getRecords() async {
List<WeightRecord> records = await RecordsDatabase.instance.getRecords();
return records;
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: FutureProvider<List<WeightRecord>?>(
create: (context) {
return _getRecords();
},
initialData: [],
catchError: (_, error) => [
WeightRecord(
date: DateTime.now(), weight: 00.0, note: 'hasError: $error')
],
child: ProvideRecords(),
),
);
}
}
then in the ProvideRecords widget I pass the data again to another provider :
class ProvideRecords extends StatelessWidget {
const ProvideRecords({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Consumer<List<WeightRecord>?>(builder: (context, list, child) {
return MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => RecordsListModel()),
ChangeNotifierProvider(create: (context) => ButtonMode())
],
builder: (context, child) {
Provider.of<RecordsListModel>(context, listen: true)
.updateRecordsList(list);
return Home(list: list);
});
}
});
}
}
the code works but I'm getting setState() or markNeedsBuild() called during build. because I'm using Provider.of<RecordsListModel>(context, listen: true).updateRecordsList(list); in the builder function. However I couldn't find another way of passing the data from FutureProvider to the RecordListModel, what can I do?
Its mainly an issue of how you have structured your code
Its recommended to have your multiproviders at the apps entry point..
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
//my providers here
ChangeNotifierProvider(create: (context) => ButtonMode())
... material app
then when adding data no need to set listen to true
Provider.of<RecordsListModel>(context, listen: false)
.updateRecordsList(list);
since home depends on data from the above provider use a consumer
Home(list: list);
// consume your provider
Consumer<RecordsListModel>(
builder:
(context, RecordsListModel recordsP, child) {
return Home(list:recordsP.list);
},
)
I'm using both BlocProvider & ChangeNotifierProvider in my app. The flow of the app goes here:-
first time user opens the app: InstructionPage() -> WelcomePage() -> HomePage() //getting error
second time user opens the app: HomePage() //working fine
I'm using sharedPreference to store the value of isInstructionPageLoaded.
But navigating from WelcomePage() to HomePage() getting error Could not find the correct Provider above this ChangeLocation Widget
here is my code:-
//main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await StorageUtil.getInstance();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: Theme.of(context).copyWith(primaryColor: kBgColorGreen),
home: MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
RestaurantBloc()..add(RestaurantPageFetched())),
],
child: MultiProvider(
providers: [
ChangeNotifierProvider(
create: (context) => LocationServiceProvider()),
],
child: StorageUtil.getBoolValue(
SharedPrefsKeys.isInstructionPageLoaded)
? HomePage()
: InstructionScreen(),
)),
routes: Routes.getRoutes(),
);
}
}
//routes.dart
class Routes {
static const String instruction = '/instruction';
static const String welcome = '/welcome';
static const String home = '/home';
static const String change_location = '/change_location';
static Map<String, WidgetBuilder> getRoutes() {
return {
Routes.instruction: (context) => InstructionScreen(),
Routes.welcome: (context) => WelcomePage(),
Routes.home: (context) => HomePage(),
Routes.change_location: (context) => ChangeLocation(),
};
}
}
//location_service.dart
class LocationServiceProvider extends ChangeNotifier {
void toogleLocation(LocationService location) {
location.isLocationUpdated = !location.isLocationUpdated;
notifyListeners();
}
}
class LocationService {
bool isLocationUpdated = false;
}
//welcome_page.dart -
on button pressed calling below method
void _navigateToHomePage() async {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return BlocProvider(
create: (context) => RestaurantBloc()..add(RestaurantPageFetched()),
child: ChangeNotifierProvider(create: (context) => LocationServiceProvider(),
child: HomePage(),),
);
}));
}
I have added BlocProvider in above method becoz before it was giving me error
blocprovider.of() called with a context that does not contain a bloc navigating from other screen from navigating from WelcomePage() to HomePage().
Thanks in advance!!!
To make sure the blocs are exposed to new routes, you need to follow the documentation and add BlocProvider.value() to provide the value of the bloc to new routes. This will carry the bloc's state and make your life easier.
Check the Official Documentations for a clear step-by-step guide ;).