After I added the dependency of ProfileLogic to LocationLogic I get the following error when the app starts:
I/flutter (14674): A LocationLogic was used after being disposed.
I/flutter (14674): Once you have called dispose() on a LocationLogic, it can no longer be used.
These are my providers:
providers: [
ChangeNotifierProvider(builder: (_) => ConnectivityLogic()),
ChangeNotifierProxyProvider<ConnectivityLogic, ProfileLogic>(
builder: (context, connectivity, previousMessages) =>
ProfileLogic(connectivity.isOnline),
initialBuilder: (BuildContext context) => ProfileLogic(false),
),
ChangeNotifierProxyProvider<ProfileLogic, LocationLogic>(
builder: (context, profileLogic, previousMessages) =>
LocationLogic(profileLogic.profile),
initialBuilder: (BuildContext context) => LocationLogic(null),
),
ChangeNotifierProvider(builder: (_) => SignUpModel()),
ChangeNotifierProxyProvider<ConnectivityLogic, WorkLogic>(
builder: (context, connectivity, previousMessages) =>
WorkLogic(connectivity.isOnline),
initialBuilder: (BuildContext context) => WorkLogic(false),
),
ChangeNotifierProvider(builder: (_) => OrderLogic()),
]
The strange thing is that everything works properly, even with that error.
I think you disposed of a widget that holds those providers. Try to move desired providers higher in the tree. So if you have:
MaterialApp(
home: MultiProvider(
providers: [...],
child: child,
)
)
Do something like:
MultiProvider(
providers: [...],
child: MaterialApp(
home: child,
)
)
If this won't help you need to provide more context. eg. Whats widget tree like.
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(),
),
},
);
}
I am getting the following error when using Provider in a WebApp. I've seen this before when creating none webapps and can usually fix it. However I can't see where the error is within the webapp. I am creating the MultiProvider in Main.dart and above the MaterialApp widget.
Here is the error:
Error: Could not find the correct Provider<List> above this CategoryWidget Widget
This happens because you used a BuildContext that does not include the provider
of your choice.
And my MultiProvider code:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Firebase.initializeApp(),
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return MaterialApp(
home: Scaffold(
body: Container(
alignment: Alignment.center,
child: const Text('ERROR: DID NOT INITIALIZE FIREBASE!')),
),
);
}
if (snapshot.connectionState == ConnectionState.done) {
print("FIREBASE INITIALIZED");
final catCollection = FirebaseFirestore.instance
.collection('categories')
.orderBy("active", descending: true);
final categories = catCollection.snapshots().map((snapShot) =>
snapShot.docs
.map((catDoc) => Category.fromMap(catDoc.data(), catDoc.id))
.toList());
return MultiProvider(
providers: [
StreamProvider<User?>(
create: (_) => FirebaseAuth.instance.authStateChanges(),
initialData: null,
),
StreamProvider<List<Category?>>(
create: (_) => categories,
initialData: [],
),
ChangeNotifierProvider.value(
value: Categories(),
),
ChangeNotifierProvider.value(
value: Auth(),
),
],
child: MaterialApp(
title: "Qi Gang's Dashboard",
debugShowCheckedModeBanner: false,
// home: LoginSignupScreen(isLoginForm: true),
initialRoute: LoginSignupScreen.id,
routes: {
LoginSignupScreen.id: (context) =>
LoginSignupScreen(isLoginForm: true),
MainScaffold.id: (context) => MainScaffold(),
And lower down the widget tree, called from MainScaffold is my CategoryWidget, where the error is occurring:
Widget categoryGrid(BuildContext context) {
final categories = Provider.of<List<Category>>(context);
Been trying to solve this for some time now and am blocked so would really appreciate it if you could point out where I am going wrong here.
Many thanks
As you can read in providers official documentation. here
context.watch<T>(), which makes the widget listen to changes on T
context.read<T>(), which returns T without listening to it
context.select<T, R>(R cb(T value)), which allows a widget to listen to only a small part of T.
These methods will look up in the widget tree starting from the widget associated with the BuildContext passed and will return the nearest variable of type T found (or throw if nothing is found).
and in your code you are defining <List<Category?>> in provider and you are Looking for <List<Category>> down the widget tree thus this is giving you error.
You need to add provider (or initialize) to main.dart and then try to use it.
It will work surely.
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)
I have a Landing UI that doesn't have any Blocs, a Register UI with it's Bloc, a Verification UI with it's Bloc, and a Home UI with it's Bloc.
In each one I defined the BlocProvider.of.
In the main I defined at the Home of Material App a Multiple Bloc Provider with each has it's child and the main child of the provider is the landing Page like this :
home: MultiBlocProvider(
providers: [
BlocProvider<UserBloc>(
create: (context) => UserBloc(UsRepoImp()),
child: RegisterUi(),
),
BlocProvider<VerificationBloc>(
create: (context) => VerificationBloc(VerRepoImp()),
child: VerificationUi(),
),
BlocProvider<HomeBloc>(
create: (context) => HomeBloc(HomeRepoImp()),
child: HomeUi(),
),
],
child: LandingUi(),
),
and one more thing the Verification UI is returned from a Register Bloc state like so :
BlocBuilder<UserBloc, UserState>(
builder: (context, state) {
if (state is UserLoading) {
return CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Color(0xff7b68ee)),
);
} else if (state is UserRegistered) {
return VerifyAccount();
} else if (state is UserError) {
return Text('Error');
}
return SizedBox(
height: 10.0,
);
},
),
But when I run I have an error that the Bloc shouldn't have an ancestor.
How am I supposed to make these Blocs to communicate with UI changings correctly?
I think you are using MultiBlocProvider in a wrong way. you should not provide child there, instead only provide the argument of the create function there, and then in your widget tree below this MultiBlocProvider you can use BlocBuilder to listen to any of the provided blocs above in the tree, and if you need to listen to multiple blocs in the same widget, you need to nest BlocBuilders.
example:
#override
Widget build(BuildContext context) => MultiBlocProvider(
providers: [
BlocProvider<RecorderBloc>(
create: (context) => myFirstBloc(),
),
BlocProvider<PermissionBloc>(
create: (context) => mySecondBloc(),
)
],
child:myChild()
);
Then inside my_child.dart :
#override
Widget build(BuildContext context) {
return BlocBuilder<MyFirstBloc, MyFirstBlocState>(
builder: (context, myFirstBlocState) =>
BlocBuilder<MySecondBloc, MySecondBlocState>(
builder: (context, secondBlocState) {
//return widget based on the states of both blocs...
},
),
);
}
I recently started using provider for my state management and I know how to use one at a time.
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChangeNotifierProvider<Reader>(
create: (context) => new Reader(),
child: _HomeBody(),
),
);
}
}
But now I have two different classes and I want to add both of them and be able to access in my widget tree.
How can I add more than one ChangeNotifierProvider in Flutter?
One option (not recommended) is to nest 2 Providers:
ChangeNotifierProvider<Reader>(
create: (_) => Reader(),
child: ChangeNotifierProvider<SomethingElse>(
create: (_) => SomethingElse(),
child: ChangeNotifierProvider<AnotherThing>(
create: (_) => AnotherThing(),
child: someWidget,
),
),
),
This is not recommended because, as the documentation states:
When injecting many values in big applications, Provider can rapidly become pretty nested:
But, another suggestion from the Provider package itself is to use the MultiProvider:
MultiProvider(
providers: [
ChangeNotifierProvider<Reader>(create: (_) => Reader()),
ChangeNotifierProvider<SomethingElse>(create: (_) => SomethingElse()),
ChangeNotifierProvider<AnotherThing>(create: (_) => AnotherThing()),
],
child: _HomeBody(),
)
Both approaches work the same but the second one is more readable. As the documentation words:
The behavior of both examples is strictly the same. MultiProvider only changes the appearance of the code.
Example adapted from the provider flutter package page and adapted to your case.
You could use MultiProvider
This guide helped me, hope it will help you too...
MultiProvider(
providers: [
ChangeNotifierProvider<Counter>(builder: (context) => Counter(0)),
ProxyProvider<Counter, ThemeSwitch>.custom(
builder: (context, counter, previous) {
final theme = previous ?? ThemeSwitch(ThemeState.light);
theme.themeState =
(counter.value > 5) ? ThemeState.dark : ThemeState.light;
return theme;
},
dispose: (context, value) => value.dispose(),
providerBuilder: (_, value, child) =>
ChangeNotifierProvider.value(notifier: value, child: child),
),
],
)