How to use detect changes in elements of an array using provider? - flutter

I started developing my first app with flutter and I have some questions.
I have a screen that contains a Horizontal ListView where each item represents a month and occupy the entire screen. Each item is another list of itens. It's basically a list showing expenses for every month. I'm controlling state using Provider, and I'm having trouble to detect changes in a month. For example, I'm in january, but I added an expense in february, so the february item must detect the change and rewrite. I haven't been able to do it so far. Here is the screenshots of the app:
I have a provider that has an array of "Periods". And a "Period" is a class that represents a month, and contains an array of expenses.
I guess I'm not organising my data the right away, I'd appreciate if someone could point me in the right direction.
EDIT:
So, I watched this video here: https://www.youtube.com/watch?v=d_m5csmrf7I, and that helped me a little at least on how to detect changes in nested providers. I basically did this:
Widget build(BuildContext context) {
...
final size = MediaQuery.of(context).size;
final financeiro = Provider.of<Financeiro>(context);
return ImprovedListView.builder(
controller: _listController,
scrollDirection: Axis.horizontal,
itemCount: financeiro.periodos.length,
itemSize: size.width,
physics: const NeverScrollableScrollPhysics(),
initialScrollOffset: financeiro.indexMesAtual * size.width,
itemBuilder: (ctx, index) {
final periodo = financeiro.periodos[index]; //get a piece of the list and pass to another change notifier provider
return ChangeNotifierProvider<Periodo>.value(
value: periodo,
child: PeriodoFinanceiro(
key: ValueKey('${periodo.ano}${periodo.mes}'),
index: index,
scrollTo: scrollTo,
carregarDadosIniciais: carregarDadosIniciais,
excluirLancamento: excluirLancamento,
),
);
};
}
Now I just have to figure out how to better control the scrolloffset of the list when its re-rendered.
Thanks

Related

ListView glitch Flutter

In total I have three files: Home.dart, Post.dart and PostBuilder.dart
In Home.dart I have a listView.seperated:
List post = [];
void initState() {
super.initState();
getPost();
}
Future getPost()async
{
//function that gets and paginates the data from the backend and adds it to the list.
}
body: ListView.separated(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
itemCount: post.length,
itemBuilder: (context, index) {
return SizedBox(
width: double.infinity,
child: PostBuilder(post[index]);
},
separatorBuilder: (context, index) {
return const Divider();
}),
And this code on my PostBuilder.dart file:
class PostBuilder extends StatefulWidget {
final post;
const PostBuilder(this.post, {super.key});
#override
State<PostBuilder> createState() => _PostBuilderState();
}
class _PostBuilderState extends State<PostBuilder> {
return Container(child: CachedNetworkImage(
imageUrl: widget.post['postUrl'],
fit: BoxFit.contain,
),
),
Now the issue I'm facing is that whenever I navigate back from Post.dart screen to the home screen, since the height of the cached network image is dynamic, it rebuilds and it seems like a glitch.
Is there any way to stop this behaviour? Maybe something like saving the state of the screen when navigating, which might prevent reloading/rebuilding the list and hence everything stays as it was.
Also I did try the following things:
Adding listview.separated with cacheExtent.
Adding listview.separated in a parent SingleChildListView widget.
Adding 'AutomaticKeepAliveClientMixin' to the class
Unfortunately, none of this worked.
After a couple of days of experimentation, I ended up creating my own "image cacher" (similar to my video cacher) that got me the height and width of the image and used that to set the container size of the list which solve the "glitchiness" when the Image would reload after navigating back.
Here is how I did it if anyone is still looking for the answer:
I used a package called image_pixels
When I got the image information I convert it into flutter logical pixels by simply using this formula:
For getting height: MediaQuery.of(context).width/ (image.width/image.height)
For getting width: MediaQuery.of(context).height/(image.width/image.height)
What it does is first it gets the aspect ratio of the image and then that ratio is used to determine the height of the container to preserve the intrinsic aspect ratio of the image.
Since child size is determined by the Parent if you have a dynamic image you will have to get its size and use it to determine the parent. Or else you will have this glitchy list behaviour as it only builds visible widgets and if it is dynamic, then as it rebuilds the child from scratch it will resize the list.
Hope this helps to anyone looking for an answer.

How to create recommended ListView in flutter and fire store

There's a way to create a recommended from user ListView using fire base and flutter...
For example I have a list of data in firebase that I am fetching them from firebase as I show them in the data list screen, and I have a list of recommended list view from user for example the clicked data item from user something shows like the below image:
To be more specific how figure if the data was viewed by user or not?
There's a way or docs to do something like this?
In case your intention is to provide some kind of "user likes" functionality.
You can create Provider of ChangeNotifier with Provider package at root (for example) and store Set<YourShopItem> there.
Then expose methods like add(YourShopItem item) and remove(YourShopItem item) on this ChangeNotifier which should add and remove items from your set and call notifyListeners() every time you call this method.
Then when you need to determine if your item is liked just obtain your ChangeNotifier and check if item is in set. Your widgets is gonna be updated every time add or remove methods are called because of their subscription to ChangeNotifier through Provider.
In case your intention is to track visibility of item.
You can use visibility detector package to track whether certain widget is visible. You can subscribe to certain widget and when it's shown, a callback is gonna be fired. So you should wrap every widget of your list into VisibilityDetector widget and save that your item was viewed.
Something like that should do the job:
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return VisibilityDetector(
onVisibilityChanged: (VisibilityInfo info) {
if (info.visibleFraction == 1) {
ON_YOUR_ITEM_IS_VISIBLE_FUNCTION();
}
},
child: Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
),
);
}
);
Also refer to this: https://stackoverflow.com/a/63577928/13737975.

Dynamic ListView of stateful widgets not working

Dears,
it could be just a mistake of mine but ...it's driving me crazy ;)
I have a ListView inside my statefulwidget:
Expanded(
child: ListView.builder(
padding: EdgeInsets.all(10.0),
itemCount: searchableFoodList.length,
itemBuilder: (BuildContext context, int index) {
print('4 - SFL - ${searchableFoodList[index].id} - ${searchableFoodList[index].name}');
return
FoodItem(
id: searchableFoodList[index].id,
baseUnit: searchableFoodList[index].baseUnit,
baseUnitS: searchableFoodList[index].baseUnitS,
baseVal: searchableFoodList[index].baseVal,
baseValCal: searchableFoodList[index].baseValCal,
mediumVal: searchableFoodList[index].mediumVal,
mediumValCal: searchableFoodList[index].mediumValCal,
name: searchableFoodList[index].name,
note: searchableFoodList[index].note
);
},
),
searchableFoodList is modified (according user selection) inside a setState().
Let say that we have a 3 FoodItem(s), so my searchableFoodList is [FoodItem-1, FoodItem-2, FoodItem-3] and everything is working fine.
If I select a single item in my list, let say "FoodItem-2", the searchableFoodList becomes [FoodItem-2] and the list displayed contains (correctly) just one item but it is FoodItem-1.
Note that I inserted a print ...and it prints "FoodItem-2"
I guess that the problem is that it is considered the "original list" and it is changed only the length of the list but the list itself is not re-generated.
I have no more ideas ...any suggestions to solve this problem?
P.S.
FoodItem is stateful widget as well (an animated one). I did something similar with using stateless widget and I didn't have any problem

Dart/Flutter: horizontally scrollable week view - how to display particular items from the ListView?

I've been trying to make horizontal weekly calendar without using third party packages. I've created a horizontally scrollable Listview, containing three weeks : previous, current and next. Obviously, Listview displays from the first item in the list, which is the first day of the previuos week. Is there any way to control the items displayed and to display the current week with previous and next weeks being accessed through horizontal scrolling?
Here is the code for the ListView.builder:
Container(
height: 60,
child: ListView.builder(
scrollDirection: Axis.horizontal,
padding: const EdgeInsets.symmetric(horizontal: 20),
itemCount: threeWeeks2.length,
itemBuilder: (BuildContext context, int index) {
var date = DateFormat('d').format(threeWeeks2[index]);
return DayCardBig(date);
}
),
),
Also, I can't keep but wondering whether the logic behind this implementation of week view is sound. I tried a more complex widget using carousel slider and pageview, but it just got messy. Is there any other widget that I'm not familiar with that I could use for a scrollable week view ?

flutter - SliverList / SliverChildBuilderDelegate supply initial index or allow negative indices

I'm currently building a calendar view in Flutter using a SliverList and a SliverChildBuilderDelegate such that I don't have to render every single item in the calendar at once.
The first date is epoch time, Jan 1, 1970, and the last date is some odd amount of time computed after today's date.
My issue is is that when I first render the view, I want it to render the view starting today, not on Jan 1, 1970. However, if I have today as the 0 indexes, negative indices are not allowed (or supplied) to the builder delegate so you can't scroll up from that date. You also can't supply an initial index, as far as I can tell, to the builder or the list, so I can't make epoch time as the 0 indexes either since the list will just start there, making for quite the terrible experience! I'm not entirely sure how to proceed.
Does anyone have any suggestions?
I'm not aware of an easy way to do this, there's no initialPositition parameter in ListView nor in SliverList. The reason I can think of is that lists are a series of widgets embedded on a ScrollView, such that in order for you to set an initial item, you would need to know the exact scroll offset of that item.
By default the two list widgets make no assumption about the height of its items, so in general finding that offset would require you to compute the heights of all widgets before it one by one, which is inefficient.
However, you can make things easier if you know beforehand the height of all your list items, or if you can force them a fixed height through either the ListView.itemExtent field or the SliverFixedExtentList.
In case you do know (or forced) the height of your list items beforehand, you can set an initial item through an initialScrollOffset in your ScrollController. Here's an example with a ListView.
#override
Widget build(BuildContext context) {
final _itemExtent = 56.0; // I know item heights beforehand
final generatedList = List.generate(500, (index) => 'Item $index');
return ListView(
controller: ScrollController(initialScrollOffset: _itemExtent * 401),
children: generatedList
.map((index) =>
ListTile(title: Text(index, style: TextStyle(fontSize: 20.0))))
.toList(),
);
}
Or in a SliverList.
#override
Widget build(BuildContext context) {
final _itemExtent = 56.0;
final generatedList = List.generate(500, (index) => 'Item $index');
return CustomScrollView(
controller: ScrollController(initialScrollOffset: _itemExtent * 401),
slivers: [
SliverFixedExtentList(
itemExtent: _itemExtent, // I'm forcing item heights
delegate: SliverChildBuilderDelegate(
(context, index) => ListTile(
title: Text(
generatedList[index],
style: TextStyle(fontSize: 20.0),
),
),
childCount: generatedList.length,
),
),
],
);
}
In both cases this is the result when you first open the app.