Flutter, reload an item in List View - flutter

This should be common but I can't find much information about this. I have a lazy loading list with Cards to show data for each record fetched from http request. How do I refresh the data of a single record when a button or action in a Card is being clicked or done?
Structure is something like:
Stateful Widget
loadData() //http request by page loading
Widget build
TextButton onPressed(){loadData}
ListView.builder(
itemCount: fullList.length,
itemBuilder: (BuildContext context, int index) {
return dataCard(context, fullList, index);
}),
dataCard(context, dl, i)
Text(dl[i].Name)
Text(dl[i].Price)
setIcon(dl[i].type)
...
setIcon(type)
switch type
case 1
return Icon A
case 2
return Icon B
...
How can I detect action in dataCard or setIcon and reload the corresponding item in the list?

By adding a function in stateful class, update the data list by index, then setState().
void refreshRecord(index) async{
dataList[index].name = new value
setState()
}

Related

Flutter - Provider's value change does not update PageView's item

I have a custom widget with a PageView in it's build().
PageView pageview = PageView.builder(
...
itemCount : pages.length,
itemBuilder : (context, index) => pages[index]
);
The PageView's item built with a function with the StatefulWidget's context passed to it.
pages.add(Builder(builder : (BuildContext context) => build(this.context)));
And the function read Provider's value with given context.
(BuildContext context){
...
bool v = context.watch<Provider>().value();
...
}
The problem is Provider's notifyListeners with the same context as above does not trigger PageView's item update.
I assumed that the function call was passed so it shouldn't cause this issue. But it looks like the update need to run it again but it's impossible.
I just want the provider value change notifying to call the function again if necessary, to update the targeted widget. But as for now, the function only being called once during the page swipe.

Flutter: listen data from Bloc state

I have tabs in my app:
news
promotions
shop
Also, I have blocs for fetching data for each tab (one bloc per tab). I want show tab only if I get a not empty list of data from bloc. How can I get access to this?
I push events while initState():
void _fetchData() {
context
..read<FeedBloc>().add(const FeedEvent.fetch(isFirstFetch: true))
..read<PromotionsBloc>().add(const PromotionsEvent.fetch(isFirstFetch: true))
..read<ShopBloc>().add(const ShopEvent.fetch(isFirstFetch: true));
}
Use BlocBuilder to check if data is not empty. You can try something like this:
BlocBuilder<FeedBloc, FeedBlocState>(
builder: (context, state) {
if(state.news == null || state.news!.isEmpty) return const SizedBox.shrink();
return NewsTab();
}

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.

Function invoke in Flutter to get attributes from Firebase

I want to define and invoke a function in Flutter to get required values from Firebase.
In the below code I have defined getCourseDetails() function and invoking it in the container by passing a parameter to get the value.
The Course_Details collection has many documents with course_id's which has attribute with name (course_name, subtitle). I use these values to build a listview cards in next steps.
I am able to get the values from the function using async await, but for some reason the values keeps on updating and never stops. It kind of goes to loop and keeps on running. I added print statements to check and it keeps on running and printing.
Please let me know what wrong I am doing here or how to define function here to avoid the issue. Thanks
class _CourseProgressListState extends State<CourseProgressList> {
String course_name, subtitle;
getCourseDetails(course_id_pass) async {
DocumentSnapshot document = await Firestore.instance.collection('Course_Details').document(course_id_pass).get();
setState(() {
course_name = document.data['course_name'];
subtitle = document.data['subtitle'];
});
}
#override
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: Firestore.instance.collection('Enrolling_Courses').where('student_id', isEqualTo: widget.id).snapshots(),
builder: (context, snapshot) {
if (snapshot.data == null) return Text('no data');
return ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (_, int index) {
var course_details = snapshot.data.documents[index];
getCourseDetails(course_details['course_id']);
Application Flow
On first build of CourseProgressList the Widget State _CourseProgressListState is loaded. This State in the build method uses a StreamBuilder to retrieve all documents from the firestore. As soon as the documents are received the StreamBuilder attempts to build the ListView using the ListViewBuilder.
In the ListViewBuilder you make the async call to getCourseDetails(course_details['course_id']); which when complete populates two attributes String course_name, subtitle;
The problem starts here
Problem
When you call
setState(() {
course_name = document.data['course_name'];
subtitle = document.data['subtitle'];
});
you trigger a Widget rebuild and so the process starts over again to rebuild the entire widget.
NB. refreshing state of a stateful widget will trigger a widget rebuild
NB. Firestore.instance.collection('Enrolling_Courses').where('student_id', isEqualTo: widget.id).snapshots() returns a stream of realtime changes implying that your List will also refresh each time there is a change to this collection
Recommendations
If you do not need to call the setState try not to call the setState.
You could let getCourseDetails(course_id_pass) return a Future/Stream with the values desired and use another FutureBuilder/StreamBuilder in your ListViewBuilder to return each ListViewItem. Your user may appreciate seeing some items instead of waiting for all the course details to be available
Abstract your request to firestore in a repository/provider or another function/class which will do the entire workload, i.e retrieving course ids then subsequently the course details and returning a Future<List<Course>> / Stream<List<Course>> for your main StreamBuilder in this widget (see reference snippet below as a guide and requires testing)
Reference Snippet
Your abstraction could look something like this but this decision is up to you. There are many software design patterns to consider or you could just start by getting it working.
//let's say we had a class Course
class Course {
String courseId;
String courseName;
String subTitle;
Course({this.courseId,this.courseName,this.subTitle})
}
Stream<List<Course>> getStudentCourses(int studentId){
return Firestore.instance
.collection('Enrolling_Courses')
.where('student_id', isEqualTo: studentId)
.snapshots()
//extract documents from snapshot
.map(snapshot => snapshot?.data ?? [])
//we will then request details for each document
.map(documents =>
/*because this is an asynchronous request for several
items which we are all interested in at the same time, we can wrap
this in
a Future.wait and retrieve the results of all as a list
*/
Future.wait(
documents.map(document =>
//making a request to firestore for each document
Firestore.instance
.collection('Course_Details')
.document(document['course_id'])
.get()
/* making a final transformation turning
each document into a Course item which we can easily pass to our
ListBuilder/Widgets
*/
.then(courseItem => Course(
courseId:document['course_id'],
courseName:
courseItem.data['course_name'],
subTitle:
courseItem.data['subtitle']
)
)
)
)
);
}
References/Resources
FutureBuilder - https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
StreamBuilder - https://api.flutter.dev/flutter/widgets/StreamBuilder-class.html
Future.wait - https://api.flutter.dev/flutter/dart-async/Future/wait.html

Flutter/Dart - Edit Page in PageView, then Refresh and Scroll back to Same Place?

Currently, I can submit edits to a single page in a PageView and then either Navigator.push to a newly created single edited page or Navigator.pop back to the original Pageview containing the unedited page.
But I'd prefer to pop back to the the same place in an updated/refreshed Pageview. I was thinking I could do this on the original PageView page:
Navigator.pushReplacement(context,new MaterialPageRoute(
builder: (BuildContext context) => EditPage()),);
But after editing, how can I pop back to a refreshed PageView which is scrolled to the now updated original page? Or is there a better way? Someone mentioned keys, but I've not yet learned to use them.
The question deals with the concept of Reactive App-State. The correct way to handle this is through having an app state management solution like Bloc or Redux.
Explanation: The app state takes care of the data which you are editing. the EditPage just tells the store(App-State container) to edit that data and the framework takes care of the data that should be updated in the PageView.
as a temporary solution you can use an async call to Navigation.push() and refresh the PageView State once the EditPage comes back. you can also use an overloaded version of pop() to return a success condition which aids for a conditional setState().
Do you know that Navigator.pushReplacement(...) returns a Future<T> which completes when you finally return to original context ?
So how are you going to utilize this fact ?
Lets say you want to update a String of the original page :
String itWillBeUpdated="old value";
#override
Widget build(BuildContext ctx)
{
.
.
.
onPressesed:() async {
itWillBeUpdated= await Navigator.pushReplacement(context,new MaterialPageRoute(
builder: (BuildContext context) => EditPage()),);
setState((){});
},
}
On your editing page , you can define Navigator.pop(...) like this :
Navigator.pop<String>(context, "new string");
by doing this , you can provide any data back to the original page and by calling setState((){}) , your page will reflect the changes
This isn't ideal, but works somewhat. First I created a provider class and added the following;
class AudioWidgetProvider with ChangeNotifier {
int refreshIndex;
setRefreshIndex (ri) {
refreshIndex = ri;
return refreshIndex;
}
}
Then in my PageView Builder on the first page, I did this;
Widget build(context) {
var audioWidgetProvider = Provider.of<AudioWidgetProvider>(context);
return
PreloadPageView.builder(
controller: PreloadPageController(initialPage: audioWidgetProvider.refreshIndex?? 0),
Then to get to the EditPage (2nd screen) I did this;
onPressed: () async {
audioWidgetProvider.setRefreshIndex(currentIndex);
Navigator.pushReplacement(context,new MaterialPageRoute(builder: (BuildContext context) => EditPage()),); }
And finally I did this to return to a reloaded PageView scrolled to the edited page;
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) =>HomePage()));
The only problem now is that the PageView list comes from a PHP/Mysql query and I'm not sure what to do if new items are added to the list from the Mysql database. This means the currentIndex will be wrong. But I guess that's the topic of another question.