How to provide data with provider using nested data models [List > Item > Sublist > SubItem] - flutter

i started using Provider package for state management, and using it in a basic way.
As the app gets more complex i want to extend the usage.
Now i have this model structure in mind: List<Client> having a List<Product> (and deeper having a List<Component>).
I have a MultiProvider using a ChangeNotifierProvider for the Clients, means the List<Client> is managed by the provider, so far so good.
Now i want to directly use the List<Product> in a provider, or later the List<Component> inside the List<Product>. I do not want to go the way through the List<Client>...down to the Component.
Here i have an image map of the structure to visualize.
Here is some simplified code:
// Just an example idea of..
Class Product with ChangeNotifier {
final String title;
}
Class Client with ChangeNotifier {
final String name;
final String List<Product>;
}
Class Clients with ChangeNotifier {
final List<Client> _items;
}
void main() {
// start the app
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (ctx) => Clients()),
// How to provide a List<Product> that actually in the model
// belongs to a Client in the List<Client>
],
child: MaterialApp(
body: ...
)
);
}
}
So the main question is how to provide a List<Product> that actually in the model
belongs to a Client in the List<Client>?

Thanks for your help in the comments.
There seem to be two possible solutions to it.
Do not use nested structure. This is possible by keeping the id of the parent inside the before nested list and throw it out the of the parent list. Then give it an own ChangeNotifierProvider and filter the items by the parent id when needed.
So in my example List and List are on the same level and both have an
ChangeNotifierProvider. The product model holds the id of it's client and the list has an method getbyClientId(clientId) that results in a filtered productslist to the specified client. That's it.
Use ChangeNotifierProxyProvider to delegate the request into the nested list.
I have chosen the first way as in my case it seemed the right way. And after working some time now with it i can not say it was a wrong decision, so i have my freedom with it.
Probably for other cases the second approach might be better. I haven't invested too much time in ChangeNotifierProxyProvider, so please check other sources for more details on that.

Related

Where to put bloc providers in flutter

I have tried to use bloc in flutter for a period, but the question of where to put bloc provider confuse me.
At the beginning of learning flutter, I put nearly all the providers in the main() function by using MultipleBlocProvider.
But due to some of my blocs subscribe to streams such as firebase snapshots, I think it might cost extra resources (I am not sure but at least some memory was occupied I guess) if I do not close those subscriptions by closing the bloc.
However, if the provider is in the main(), change page or pop up would not close these blocs and the subscriptions inside.
In this scenario, I try to put some bloc providers into specific pages, so the bloc can be close and recreate when I goes in and out of this page. But there are some other questions occurs.
For example, if I have a bloc called ProductDetailsBloc which is used to control the widget of product details in product details page, its events contains an event called GetProductBySku which is used to get product from firebase and set the product inside the state(ProductDetailsState).
I want to put this bloc and its provider inside product details page and put an event trigger inside product list widget (located in product list page) and its onTap() function, where users click on the product list item (this is a thumbnail product item which is from another resources and only contains very basic info such as sku, title ) inside product list page and then navigator to product details page to view the full information of this product.
But as I mentioned before, If I put the bloc provider of ProductDetailsBloc inside product details page, I can not use GetProductBySku before entering product details page
So, I personally have two ideas for this questions,
the first one is passing the product sku through arguments to product details page and then call the GetProductBySku after the bloc has been created.
the second one is the put the ProductDetailsBloc inside the main(), so I can skip this questions, and directly use the GetProductBySku in product list widget, but this turns the problems in to the very front.
I do not know which one is better, so I would be very appreciative if someone can give some suggestions.
And back to the main questions, what is the best practice of putting bloc providers and what are the concerns if I put providers inside main().
Thanks for every one that read to here because this is a bit long and this is my first time of asking on attack overflow :3
You can find more discussion regarding this in the discord server of Bloc. More info: https://discord.com/channels/649708778631200778/649708993773699083/860604230930006016
I will copy paste the message and code here. we have been using bloc
in our app for over a year and it seems we have got an issue.
Scenario: List of Posts needs to be shown in home feed. Let's say each
post is encapsulated in PostBloc (operations on post to be performed
here). I have been using moor to reactively find if anything changes
in the data (like number of likes of post which might have trigged
from post detail page where post detail was fetched again). I want to
know if it is a wise decision to create BlocFactory which will be
providing Bloc object (here different PostBloc object) based on
Post_Id.
I have written a sample BlocFactoryProvider, please let me know your
thoughts
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
typedef CreateIfAbsent<T> = T Function();
//create bloc of <T> type
//author: https://github.com/imrhk
class BlocFactory<T extends Bloc<dynamic, dynamic>> {
Map<String, T> _cache = <String, T>{};
T createBloc({#required String id, #required CreateIfAbsent<T> createIfAbsent}) {
assert(id != null && createIfAbsent != null);
if (_cache.containsKey(id)) {
return _cache[id];
}
final value = createIfAbsent.call();
_cache[id] = value;
return value;
}
void dispose() {
_cache.values.forEach((bloc) => bloc?.close());
_cache.clear();
}
}
class BlocFactoryProvider<T extends Bloc<dynamic, dynamic>, V>
extends SingleChildStatelessWidget {
final BlocFactory<T> _blocFactory;
final Widget child;
BlocFactoryProvider({BlocFactory<T> blocFactory, this.child})
: _blocFactory = blocFactory ?? BlocFactory<T>();
#override
Widget buildWithChild(BuildContext context, Widget child) {
return InheritedProvider<BlocFactory<T>>(
create: (_) => _blocFactory,
dispose: (_, __) => _blocFactory.dispose(),
child: child,
lazy: false,
);
}
static BlocFactory<T> of<T extends Bloc<dynamic, dynamic>>(BuildContext context) {
try {
return Provider.of<BlocFactory<T>>(context, listen: false);
} on ProviderNotFoundException catch (e) {
if (e.valueType != T) rethrow;
throw FlutterError(
"""
BlocProvider.of() called with a context that does not contain a Bloc of type $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: $context
""",
);
}
}
Felix's replay was
It’s hard to say without more context but I’d recommend moving the
caching to the repository layer and instead have a single PostBloc
with PostChanged event that pulls the latest details (using cache).
So he did not completely discarded the idea.
declaration: I am the author of the below code shared over discord in March'21.

Is there a better way of structuring a functionality using BLoC?

Im wrote a bloc for a functionality that has a home page (that fetches a list of items) and a create/edit page (that creates/updates items).
My bloc is currently in charge of handling both pages since they belong to the same functionality, but my code is getting a little bit harder to read/write then usual because im handling 2 states at once.
class MeusSellersState extends Equatable {
const MeusSellersState({
this.home,
this.form,
});
// Home and Form are my two states.
final Home home;
final Form form;
MeusSellersState copyWith({
Home home,
Form form,
}) {
return MeusSellersState(
home: home ?? this.home,
form: form ?? this.form,
);
}
#override
List<Object> get props => [home, form];
}
I know i could separate this bloc in two, but im worried that more complex functionalities could make me write several blocs and pollute even more my code.
Does anyone know a better solution ?

Flutter, pasing parameters on StatefulWidget

Can someone tell me what is happening in this program?
body: new ListView.builder(
itemBuilder: (BuildContext context, int index) {
return new StuffInTiles(listOfTiles[index]);
},
itemCount: listOfTiles.length,
),
),
);
}
}
class StuffInTiles extends StatefulWidget{
final MyTile myTile;
const StuffInTiles(this.myTile);
#override
StuffInTilesState createState() => StuffInTilesState();
}
class StuffInTilesState extends State<StuffInTiles> {
#override
Widget build(BuildContext context) {
return Container(child:
//Text(widget.myTile.title),);
_buildTiles(widget.myTile));
}
Widget _buildTiles(MyTile t) {
I want to understand how passing parameters works,why i have
const StuffInTiles(this.myTile);
in this program, what this code is doing?
in my class StuffInTilesState extends State<StuffInTiles> i don't have any constructor, so how this code is working? why my parameters just happen to be there? before i was learning C++, so this is like a magic to me
If you learned C++ you are probably familiar with initializer list, which Dart has as well. In C++ you could do something:
MyClass {
MyClass(MyTitle title) : this.myTitle = title;
final MyTitle myTitle;
}
Which is also valid in Dart. However, Dart allows you to shorthand the call, by automatically assigning the reference to the new class property, without using any intermediate variable.
MyClass(this.myTitle);
Which is basically the same but ensures that any given property won't be null unless you explicitly pass null.
There are other type of constructors available on Dart, such as private constructors, factory constructors and named constructors. You may want to check the official documentation to learn more about it.
By default, when you create classes in Dart, there is just a default constructor implemented.
For example, if you have a class named AwesomeWidget. It will have a default constructor AwesomeWidget() that lets you create an instance of this widget.
So you could use a default constructor in code like so:
//Example 1
return AwesomeWidget();
//Example 2
AwesomeWidget myWidget = AwesomeWidget();
//Example 3
//...
Row(
children: [
Text("Example Code!"),
AwesomeWidget(),
Text("Example Footer Code!"),
],
),
//...
Now if you want to pass some values or some data to your Widget classes then you use the code you have posted above in your question.
The question is: Why would we want to send data to our widgets?
Answer: The biggest use case is when we make our list items as separate widgets. For example, in my food app, I have to show my user's order history in a ListView, so for the UI of each individual list item, I will just make a reusable Widget called OrderHistoryListItem.
In that OrderHistoryListItem, you want to show the date and time of the object. And the order id, and how much the user paid in that order or any other details, so to display this, we send this data to our Reusable Widget List Item, which displays it. Simple as that.
And that is one of the reasons why we pass values to Widgets. As a programmer, you can make use of this handy feature for more complex scenarios, be creative!

Keep a list from provider up to date with AnimatedListView

I'm using the Provider library and have a class that extends ChangeNotifier. The class provides an UnmodifiableListView of historicalRosters to my widgets.
I want to create an AnimatedListView that displays these rosters. The animated list supposedly needs a separate list which will tell the widget when the widget to animate in or out new list items.
final GlobalKey<AnimatedListState> _listKey = GlobalKey<AnimatedListState>();
This example uses a model to update both the list attached to the widget's state and the AnimateListState at the same time.
// Inside model
void insert(int index, E item) {
_items.insert(index, item);
_animatedList.insertItem(index);
}
I want to do the same thing but I'm having trouble thinking how. That is, what's a good way I can update the list provided by provider and also change the AnimatedListState.

I am losing stream data when navigating to another screen

I am new to Dart/Flutter and after "attending" a Udemy course,
everything has been going well.
Until now ;-)
As in the sample application in the Udemy course i am using the BLOC pattern.
Like this:
class App extends StatelessWidget {
Widget build(context) {
return AppBlocProvider(
child: MaterialApp(
(See "AppBlocProvider" which I later on use to get the "AppBloc")
The App as well as all the screens are StatelessWidget's.
The AppBlocProvider extends the InheritedWidget.
Like this:
class AppBlocProvider extends InheritedWidget {
final AppBloc bloc;
AppBlocProvider({Key key, Widget child})
: bloc = AppBloc(),
super(key: key, child: child);
bool updateShouldNotify(_) => true;
static AppBloc of(BuildContext context) {
return (context.inheritFromWidgetOfExactType(AppBlocProvider) as AppBlocProvider).bloc;
}
}
The AppBlocProvider provides an "AppBloc" containing two further bloc's to separate the different data a bit.
Like this:
class AppBloc {
//Variables holding the continuous state
Locale appLocale;
final UserBloc userBloc;
final GroupsBloc groupsBlock;
In my application I have a "GroupSearchScreen" with just one entry field, where you can enter a fragment of a group name. When clicking a button, a REST API call is done and list of group names is returned.
As in the sample application, I put the data in a stream too.
In the sample application the data fetching and putting it in the stream is done in the bloc itself.
On the next line, the screen that uses the data, is created.
Like this:
//Collecting data and putting it in the stream
storiesBloc.fetchTopIds();
//Creating a screen ths shows a list
return NewsList();
In my case however, there are two major differences:
After collecting the data in the GroupSearchScreen, I call/create the GroupsListScreen, where the list of groups shall be shown, using regular routing.
Like this:
//Add data to stream ("changeGroupList" privides the add function of the stream!)
appBloc.groupsBlock.changeGroupList(groups);
//Call/create screen to show list of groups
Navigator.pushNamed(context, '/groups_list');
In the GroupsListScreen, that is created, I fetch the bloc.
Like this:
Widget build(context) {
final AppBloc appBloc = AppBlocProvider.of(context);
These are the routes:
Route routes(RouteSettings settings) {
switch (settings.name) {
case '/':
return createLoginScreen();
case '/search_group':
return createSearchGroupScreen();
case '/groups_list':
return createGroupsListScreen();
default:
return null;
}
}//routes
And "/groups_list" points to this function:
Route createSearchGroupScreen() {
return MaterialPageRoute(
builder: (context) {
//Do we need a DashboardScreen BLOC?
//final storiesBloc = StoriesProvider.of(context);
//storiesBloc.fetchTopIds();
return GroupSearchScreen();
}
);
}
As you can see, the "AppBlocProvider" is only used once.
(I ran into that problem too ;-)
Now to the problem:
When the GroupsListScreen starts rendering the list, the stream/snapshot has no data!
(See "if (!snapshot.hasData)" )
Widget buildList(AppBloc appBloc) {
return StreamBuilder(
stream: appBloc.groupsBlock.groups,
builder: (context, AsyncSnapshot<List<Map<String, dynamic>>>snapshot) {
if (!snapshot.hasData) {
In order to test if all data in the bloc gets lost, I tried not to put the data in the stream directly, but in a member variable (in the bloc!).
In GroupSearchScreen I put the json data in a member variable in the bloc.
Now, just before the GroupsListScreen starts rendering the list, I take the data (json) out of the bloc, put it in the stream, which still resides in the bloc, and everything works fine!
The snapshot has data...
Like this (in the GroupsListScreen):
//Add data to Stream
appBloc.groupsBlock.changeGroupList(appBloc.groupsBlock.groupSearchResult);
Why on earth is the stream "losing" its data on the way from "GroupSearchScreen" to "GroupsListScreen" when the ordinary member variable is not? Both reside in the same bloc!
At the start of the build method of the GroupsListScreen, I have a print statement.
Hence I can see that GroupsListScreen is built twice.
That should be normal, but could that be the reason for not finding data in the stream?
Is the Stream listened on twice?
Widget buildList(AppBloc appBloc) {
return StreamBuilder(
stream: appBloc.groupsBlock.groups,
I tried to explain my problem this way, not providing tons of code.
But I don't know if it's enough to give a hint where I can continue to search...
Update 16.04.2019 - SOLUTION:
I built up my first app using another app seen in a Udemy course...
The most important difference between "his" app and mine is that he creates the Widget that listens to the stream and then adds data to the stream.
I add data to the stream and then navigate to the Widget that shows the data.
Unfortunately I used an RX-Dart "PublishSubject." If you listen to that one you will get all the data put in the stream starting at that time you started listening!
An RX-Dart "BehaviorSubject" however, will also give you the last data, just before you started listening.
And that's the behavior I needed here:
Put data on stream
Create Widget and start listening
I can encourage all Flutter newbies to read both of these very good tutorials:
https://medium.com/flutter-community/reactive-programming-streams-bloc-6f0d2bd2d248
https://www.didierboelens.com/2018/12/reactive-programming---streams---bloc---practical-use-cases/
In the first one, both of the streams mentioned, are explained very well.