Imagine in the TodoScreen of the application, I have a TodoObjectList (list of todos obtained from some API) and I want to show them inside a list. So I created a list of TodoWidgets (StatelessWidget), each one having its own TodoObject as its property. Now I want TodoWidgets to be bound with its TodoObject, so I used the Provider package. The code is something like below (inside TodoScreen):
SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
return ChangeNotifierProvider<TodoObject>(
builder: (_) => TodoObjectList[index],
child: Consumer<TodoObject>(
builder: (context, TodoObject, child) => TodoWidget(todo: TodoObject)
),
);
},
childCount: TodoObjectList.length,
),
)
This code works fine for the first time. But when I go back and navigate to TodoScreen for the second time (I won’t call the API again, TodoObjectList is already cached), Provider throws an error:
“A TodoObject was used after being disposed.”
I Know why this error is being thrown, but my question is how should I bind TodoWidget with TodoObject using provider, without facing this error when I have the API data stored somewhere.
You should use ChangeNotifierProvider.value instead. This link would probably help you if you had any follow up questions.
Related
I wanted to upload a picture inside Firebase Storage when I saw this error :
The provider you are trying to read is in a different route.
So I started study what is this Provider and now I understood that I need to include the page that I'm using to upload the picture inside the same route of the ancestor Provider of type Database.
So I had the issue inside "Registration_form" that is created by "LogInPage" through a Navigator like this :
void navigateToRegistrationForm(context) {
Navigator.push(context,
MaterialPageRoute<void>(builder: (BuildContext context) {
return Reg_Form();
}));
return;
}
Now I'm changing the code that is something like :
void navigateToRegistrationForm(context) {
Navigator.push(
context,
MaterialPageRoute<void>(
builder: (BuildContext context) => Provider<Database>(
create: (_) => FirestoreDatabase(),
builder: (context, child) => Reg_Form(),
)));
return;
}
But I don't understand what I should pass to the create method...I just want to create Reg_Formin the right route to call Firebase functions to put the file inside firebase storage so I think that FirebaseDatabase isn't what suits my needs.
If I understand your issue correctly, you can either move your ChangeNotifierProvider for your Database class higher up in the widget tree by wrapping your MaterialApp like so:
ChangeNotifierProvider<Database> (
builder: (context) => Database(),
child: MaterialApp(...
Or just change the create in what you provided to
create: (_) => Database(),
Either way should give you access to the correct Provider/BuildContext in your Registration form with standard context.read<Database>()...
Currently my app loads the information (including pictures) into cards from Cloud Firestore, but it only starts loading the information when it appears on the user's screen. I used FadeInImage in _makeCard to make it a bit more usable while loading, but it's still too slow. Is there a way to make StreamBuilder load, for example, the nearest 10 cards that are off the screen? If not, is there a better way to create a list that is loaded from Firestore? This is my code currently:
StreamBuilder(
stream: Firestore.instance.collection('Cards').snapshots(),
builder: (context, snapshot) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
return _makeCard(context, snapshot.data.documents[index]);
},
childCount:
snapshot.hasData ? snapshot.data.documents.length : 0,
),
);
})
I don't have a lot of experience with coding, so let me know if there is anything I can clarify.
I am a bit confused about the implementation of Provider with Firebase Auth. Every tutorial seems to use different methods. I have a service named AuthService which contains a variable final FirebaseAuth auth = FirebaseAuth.instance. So, I want to use the following stream: AuthService.auth.onAuthStateChanged in a widged. The question is should I use something like:
StreamProvider<FirebaseUser>.value(
value: AuthService().auth.onAuthStateChanged,
child: ...
or create a constructor like this:
Provider<AuthService>(
create: (_) => AuthService(),
child: ...
)
and access it somewhere in the widget:
final authService = Provider.of<AuthService>(context);<br>
final user = authService.auth.onAuthStateChanged;
You can use similar to this thing, If user is logged in he will lead to MainScreen() otherwise it will lead you to LoginPage(), you don't need to use
provider
Widget _handleAuth() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
return (!snapshot.hasData)
? LoginPage()
: MainScreen();
},
);
The provider package exposes different types of providers, you can check all types in their page here (At the bottom of their page).
Using a provider instead of another it depends on your needs, as far I'm concerned the Provider(the first one in the list) is fine.
I am trying to "read a provider immediately when creating it" in context.watch<FooModel>().foo, which would trigger an error.
How to obtain a new BuildContext and generate a value from it?
Consumer<FooModel>(
key: ValueKey( context.watch<FooModel>().foo ),
builder: (context, value, child) {
return fooWidget;
},
)
I simply wrap the child part of the provider into another class, to prevent reading a provider immediately. It worked.
This is my code
Widget homeDashBoardCards(title, image, BuildContext context) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DashBoardScreen(),
),
),
);
}
and I need to just be able to change this context to chose the page, instead of creating a lot of Widgets.
homeDashBoardCards('Categories', 'chii-icon.png', context),
like creating 2 cases or more in this one...
builder: (context) => DashBoardScreen
Just as an example:
builder: (context) => case "1" = DashBoardScreen
case "2" = FavoriteScreen
Thank you guys...
1. What I think the Problem Is
What you want is not 100% clear from the question, but I think you simply want a function that would have a switch inside returning which page the user should go to?
In that case, you might want to check out:
Flutter's tutorials on routing
switch statement in Dart
2. One Possible Solution
Create a switch function for deciding which page to switch to. Instead of using Strings, I highly suggest using enums. But both are possible.
enum Screens {dashboard, favorite}
Widget screenSwitcher(Screens screenEnum) {
switch (screenEnum) {
case Screens.dashboard:
return DashBoardScreen();
case Screens.favorite:
return FavoriteScreen();
default:
throw Exception;
}
}
Note that, if you have a non-empty case clause ending with a return, throw or continue, you don't need a break. The default clause doesn't need a break.
Return the screen when routing by calling the switcher function — you can obviously create more parameters for the functions, if necessary —:
Widget homeDashBoardCards(title, image, Screens screenEnum, BuildContext context) {
return GestureDetector(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => screenSwitcher(screenEnum),
),
),
);
}
3. Further Reading
Your code will be much simpler and easier to read if you use Named Routing, as mentioned in the first section of this answer. Additionally, if routing is very important to your app and complicated, you might want to consider looking for or creating a package in the pub.dev package host.
If you accept the solution code I shared above, you're passing variables through different widgets, which is not very object-oriented and also not the Flutter way. You should then consider state sharing packages to work around that. Two of the most notable ones are the BLoC and the Provider packages, but you can also do it with rxdart and the get_it package, check this video by Fireship for a great summary.