how to use getx obx instead of future builder for a displaying a list of images - flutter

I'm trying to display a list of images stored in my firebase cloud storage. I have the image name stored in my firestore. and my getURL() function gets the download url as a future for the respective image.
Using future builder I'm successful in displaying the list. I'm trying to achieve the same thing using Obx GetX. The problem is flutter is trying to display the images before the URL is retrieved. How do I successfully return a future of a widget in my second approach
ListView.builder(
itemCount: deviceListController.devices[index].images !=
null
? deviceListController.devices[index].images!.length
: 0,
scrollDirection: Axis.horizontal,
shrinkWrap: true,
itemBuilder: (context, imageIndex) {
return SizedBox(
height: 100,
width: 100,
child: FutureBuilder(
future:
deviceListController.getURL(index, imageIndex),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return CircularProgressIndicator();
case ConnectionState.done:
return Image.network(deviceListController
.devices[index].imageURL[imageIndex]!);
// case ConnectionState.none:
// case ConnectionState.active:
default:
return Icon(Icons.accessible_forward);
}
},
),
);
},
),
here is my attempt at this. the problem is I dont know how to return/implement a future in the obx style.
Obx(
() => ListView(
scrollDirection: Axis.horizontal,
children: deviceListController.devices[index].images!
.asMap()
.map((ind, image) {
deviceListController.getURL(index, ind);
return MapEntry(
ind, Image.network(device.imageURL[ind]!));
})
.values
.toList()),
),
Edit: This is closest I could come up with. and it still doesnt load the images. flutter is looking for images before the async task is done.
Obx(() {
return deviceListController.devices[index].images !=
null
? ListView(
scrollDirection: Axis.horizontal,
children: deviceListController
.devices[index].images!
.asMap()
.map((ind, image) {
pageState(AppState.loading);
deviceListController.getURL(index, ind);
pageState(AppState.loaded);
return pageState.value == AppState.loading
? MapEntry(
ind,
Image.network(
device.imageURL[ind]!))
: MapEntry(
ind, CircularProgressIndicator());
})
.values
.toList())
: Spacer();
}),

Try using an enum for your state management.
enum AppState { initial, loading, loaded, error, empty, disabled }
Rx<AppState> pageState = AppState.initial.obs;
pageState(AppState.loading);
await some time taking operation.
pageState(AppState.loaded);
Obx(
() => pageState.value == AppState.loading
? Center(child: CircularProgressIndicator())
: YOUR WIDGET
here, you can use an enum to create state, and show the widgets based on the state ready.

Related

What widget should I use?

This is what I am doing now.
Main Page:
I would like to make it same like this picture.Example:
I have tried couple ways and widget to build it but couldn't figure it out. Also, I want to retrieve the data from the Firebase and show them as the content.
Code 1: https://pastebin.com/A0nK1riQ
Code 2: https://pastebin.com/i1T7gBNy
Widget build(BuildContext context) {
return Container(
child: StreamBuilder(
stream: _products.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot =
streamSnapshot.data!.docs[index];
return Container(
margin: const EdgeInsets.all(10),
child: ListTile(
title: Text(documentSnapshot['name']),
subtitle: Text(documentSnapshot['price'].toString()),
trailing: SizedBox(
width: 100,
),
),
);
});
}
return SizedBox.shrink();
}),
);
}
You may use GridView.builder
https://api.flutter.dev/flutter/widgets/GridView-class.html
and in gridview build use column
According to me, first you have to check which NavigationRail icon clicked then put the condition on it's GestureDetector like
// global variable
String itemClickedValue = "";
then set the value in it according to user click
itemClickedValue = "first";
then check the condition while fetching data like
if(itemClickedValue.compareTo("first") == 0){
// pass that documentId or api and then show in list
}

how to use two future builder in one page in flutter

In my Flutter app, I need to display two lists that are coming from the database, but I am having trouble getting both lists to display on the same screen. I am using two FutureBuilder widgets, but the first list is displaying correctly while the second list is still loading.
Here is the code I am using:
var future1 = FutureBuilder<List<QuranTextModel>>(
future: database.getQuranText(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ScrollablePositionedList.builder(
itemScrollController: scrollToIndex,
itemCount: snapshot.data!.length,
initialScrollIndex: widget.position,
itemBuilder: (context, index) {
// Build the list item widget here
});
}else{
return const Center(child: CircularProgressIndicator(),);
}
}
);
var future2 = FutureBuilder<List<UrduTextModel>>(
future: database.getUrduTranlation(),
builder: (context, snapshot) {
if(snapshot.hasData){
return ScrollablePositionedList.builder(
itemScrollController: scrollToIndex,
itemCount: snapshot.data!.length,
initialScrollIndex: widget.position,
itemBuilder: (context, index) {
// Build the list item widget here
});
}else{
return const Center(child: CircularProgressIndicator(),);
}
}
);
Column(
children: [
SizedBox(
height: 200,
child: future1,
),
SizedBox(
height: 200,
child: future2,
),
],
)
The first FutureBuilder is used to build a list of QuranTextModel objects, and the second FutureBuilder is used to build a list of UrduTextModel objects. I am using a Column widget to display both lists, with each list contained within a SizedBox widget to give it a fixed height.
The issue I am having is that only the first list is displaying correctly, while the second list is still loading. How can I get both lists to display on the same screen?
Thank you for any help you can provide!
SingleChildScrollView(
child: Column(
children: [
SizedBox(
height: 200,
child: future1),
SizedBox(height: 200,child: future2,)
],
),
),
Try this.
also you have to check your future status before populate you can check that by using
if (snap.connectionState == ConnectionState.done) { your code. you can check does snpa has data in it. }
connection state has deferent states that can help you to make your UI more interactive

Flutter : How to show Progress indicator until the data is fetched from the server [duplicate]

This question already has answers here:
Flutter how to user setState()
(2 answers)
Closed 8 months ago.
I want to show a progress indicator until the required data is fetched from the server. Currently what I am doing is made a function getQuotes() that will fetch the data into a variable using setState(). And the Used the FutureBuilder where its future parameter is set to getQuotes(). But this approach gives me a non-ending CircularProgressIndicator. I don't why it is happening. Is ther any problem with the combination of FutureBuilder() and setState() ? Can Some one help ?
Here is my code,
Map<String, dynamic> userInfo = {};
Future<void> getQoutes() async {
var data = await FirebaseFirestore.instance.collection('user').doc(auth.currentUser!.uid).get();
setState(() {
userInfo = data.data() as Map<String, dynamic>;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Some Quotes',
),
backgroundColor: Colors.deepOrange,
),
body: FutureBuilder (
future: getQoutes(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
if (snapshot.hasError) {
return Text('Error : ${snapshot.error}');
}
return SingleChildScrollView(
child: Container(
height: MediaQuery.of(context).size.height,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: ListView.builder(
itemCount: 3,
scrollDirection: Axis.vertical,
itemBuilder: (context, index) {
return Card_fun(userInfo['quotes'][index]);
}
)
)
],
),
)
);
default:
return const CircularProgressIndicator();
}
}
),
);
}
Another solution would be to make your getQuotes() function returning a Future<String> instead of a Future<void> and then access the data via the snapshot instead of accessing the state.
The Flutter docs of the FutureBuilder Flutter Docs are also doing it that way in the demo. As long as you don't need the state of userInfo in other places this should be an acceptable solution and you could also remove userInfo as variable. If you want to maintain or manipulate it later you could try to put the setState({}) statement in the ConnectionState.done switch case within an if(snapshot.hasData){} block.

Combine itemBuilder with StickyHeader in Flutter

I'm new to flutter and I want to organise the elements that I get from Firestore in a list with StickyHeaders. I would like to do it with an itemBuilder and the snapshot I get from the database.
My problem is the itemBuilder builds each item separately and has to be returned, but StickyHeader needs to have all items added as children.
How can I achieve this? Just as a reference I paste my code without the StickyHeader. buildItemBubble returns a Card Widget.
StreamBuilder(
stream: buildSnapshots(_filters),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
return snapshot.data.documents.length == 0
? Center(child: Text('It\s empty here...'))
: CupertinoScrollbar(
child: ListView.builder(
key: new PageStorageKey('randomkey'),
shrinkWrap: true,
padding: const EdgeInsets.all(10.0),
itemCount: snapshot.data.documents.length,
itemBuilder: (ctx, i) =>
buildItemBubble(snapshot, i),
),
);
},
)

Dart: Minimising access to Firebase in Flutter app

I have the following widget which builds a to-do list from a subcollection of a task given its document ID. The code is working fine.
Widget buildFoodList() {
return SizedBox(
child: Container(
padding: const EdgeInsets.all(10.0),
child: StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('tasks').document(documentID).collection('todo')
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return new Text('Loading...');
default:
return new ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Row(
children: <Widget>[
Expanded (child:Text(ds['deadline'].toString()) ),
Expanded (child:Text(ds['description']) ),
Expanded (child:Text("\$"+ds['quantity'].toString()) ),
],
);
},
);
}
},
)
),
);
}
As you can see, I am using a StreamBuilder. However, I know that the subcollection is not going to change. So the question is whether using StreamBuilder is an overkill, because using stream to listen can be a waste of resources and access to Firebase. More importantly, the cost of using Firebase is calculated on an access basis.
To summarise, the question is whether using StreamBuilder is necessary. If not, what is the alternative approach which can help to avoid unnecessary access to Firebase.
Thanks.
StreamBuilder is necessary in apps where you need to fetch any update , insert or delete in a firebase collection ( in this case ). An alternative can be the FutureBuilder that fetch the data once and then you can wrap in a Swipe to refresh ( and the user decides when needs to see new data).