how to change ui inside a build ? flutter? - flutter

i am creating a app in which i need to show total price of the cart items:
so i am getting data in json and returning that data in futurebuilder and accesing it like;
list[index]['price']
and intialize a variable for total :
double totalPrice = 0;
and adding values as follows:
FutureBuilder(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
// still waiting for data to come
return Center(
child: CircularProgressIndicator(),
);
} else if (snapshot.hasData &&
snapshot.data.isEmpty) {
return Center(child: Text("No Products"));
;
} else {
if (snapshot.hasError) print(snapshot.error);
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (BuildContext context, index) {
//below the code of adding price
List list = snapshot.data;
totalPrice = totalPrice + list[index]['price'];
print("this is the total:$totalPrice");
return Container(..My Ui..
):Center(child: Text("no data"));
}
}, //futurebuilder ends here
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
'Total :₹ $totalPrice', // showing total price here
style: TextStyle(
fontSize: 20,
color: darkGrey,
fontWeight: FontWeight.bold),
),
),
but this totalPrice always show 0
can someone tell me how to update this variable inside a build?
Thanks in advance <3

Not try to do like this. Use these method..
FutureBuilder(builder: (context, snapshot) {
if(snapshot.hasData){
//Only run when you get data
final list = snapshot.data;
var totalPrice = list.map((e) => e[price]).toList()
.reduce((value, element) => value + element);
return ReturnYourWidget();
}
//Until you get data show the loader
return Center(child: CircularProgressIndicator(),);
},);

I am assuming your totalPrice is in a stateful widget?
I would make a function that takes the snapshot data and calculates the total price. After that updating the totalPrice. This allows you to keep the ListviewBuilder as it is.
Most importantly, don't forget to use setState(() => {totalPrice = <calculated>});. This notifies the widget of the change.
Additionally, this ensures your totalPrice is accurate since the ListBuilder only builds items that are currently being rendered on the screen.

if (snapshot.hasData) {
final list = snapshot.data;
var totalPrice = list.map((e) => e[price]).toList()
.reduce((value, element) => value + element));
}

Related

How to sort a length of int from a doccumentSnapshot in a listview.builder Flutter

I have already get the snapShot of array and count it using length. And displayed it. But I want to reorder it from much to lower in a listview.builder. How can I achieve that?
Backed structure
Code so far
//I forgot to mention
//This is the usersCommentId from the snapShot of StreamBuilder on top of the widget tree
final usersComment = snapshot.data?['usersComment'];
ListView.builder(
physics: const BouncingScrollPhysics(),
itemCount: usersComment.length,
shrinkWrap: true,
itemBuilder: (context, index) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection("usersComment")
//I tried filtered it here But that doesn’t work
.where(usersComment[index])
.snapshots(),
builder: (context,
AsyncSnapshot<
QuerySnapshot<
Map<String, dynamic>>>
snapshot) {
final userComments = snapshot.data!.docs
..sort((a, b) => ((b.data()['vote']
as List<dynamic>?)
?.length ??
0)
.compareTo((a.data()['vote']
as List<dynamic>?)
?.length ??
0));
final comment = userComments[index];
final countVote = (comment.data()['vote']
as List<dynamic>?)
?.length ??
0;
if (!snapshot.hasData) {
return Container();
}
return Text(
countVote.toString(),
style: const TextStyle(
color: Colors.black, fontSize: 15),
);
});
}),
As you have already taken all List of userComments, I have a suggestion to make it with in single Stream query as following
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("usersComment").snapshots(),
builder: (context,
AsyncSnapshot<QuerySnapshot<Map<String, dynamic>>>
snapshot) {
final userComments = snapshot.data!.docs..sort((a, b) => ((b.data()['vote'] as List<String>?)?.length ?? 0).compareTo((a.data()['vote'] as List<String>?)?.length?? 0));
ListView.builder(
itemCount: userComments.length,
itemBuilder: (context, index){
final comment = userComments[index];
final countVote = (comment.data()['vote'] as List<String>?)?.length ?? 0;
return Text(
countVote.toString(),
style: const TextStyle(
color: Colors.grey,
fontSize: 9),
);
});
});
If you want to filter the userComments, then stream: FirebaseFirestore.instance .collection("usersComment").where(%your condition%).snapshots()
take out the listview.
return listView inside the stream builder.
return everything in the collection (not each doc).
add all the votes in one list inside your stream builder.
sort the list and display using the ListView that is inside the stream
how to sort in dart
You can sort the usersComment array before passing it to the ListView.builder by using the sort method from the List class. You can sort the list by using a custom sorting function that compares the vote counts of each DocumentSnapshot in the list. Here is an example:
usersComment.sort((a, b) {
return FirebaseFirestore.instance
.collection("usersComment")
.doc(b)
.get()
.then((bSnapshot) {
final bVote = bSnapshot.data?['vote'];
final bCountVote = bVote?.length ?? 0;
return FirebaseFirestore.instance
.collection("usersComment")
.doc(a)
.get()
.then((aSnapshot) {
final aVote = aSnapshot.data?['vote'];
final aCountVote = aVote?.length ?? 0;
return bCountVote - aCountVote;
});
});
});

Firestore get length data disappear in flutter stream

I make a chat using firebase firestore.
So. I tried express not read count in the Chat list.
But, Initially the number appears, but it changes to null data.
I don't know Why data chage null data?
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('chatRooms')
.where('emails', arrayContainsAny: [user?.email]).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
var chatLists = snapshot.data?.docs;
if (snapshot.hasError) {
return Text('Something error...');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Text('is Loading...');
}
return ListView.builder(
itemCount: chatLists?.length,
itemBuilder: (context, index) {
if (chatLists?[index]['currentMsg'] != null &&
chatLists?[index]['currentMsg'] != "") {
var list = List.from(chatLists?[index]['members']);
var member = '';
if (loginUser['userName'] != null) {
for (int i = 0; i < list.length; i++) {
if (list[i] != loginUser['userName']) {
member += list[i];
}
}
}
return ListTile(
title: Row(
children: [
Text(member),
const SizedBox(
width: 20.0,
),
ChatLength(docId: chatLists![index].id, uid: user!.uid),
],
),
subtitle: SizedBox(
height: 40.0,
child: Text(
chatLists[index]['currentMsg'],
overflow: TextOverflow.ellipsis,
)),
trailing: Text(
tmFormmater(chatLists?[index]['currentTm']),
style: const TextStyle(
color: Color(0xff999999),
),
),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ChatScreen(
docId: chatLists![index].id,
title: member,
),
)),
);
} else {
return Container();
}
},
);
return Container();
},
),
class ChatLength extends StatelessWidget {
const ChatLength({super.key, required this.docId, required this.uid});
final String docId;
final String uid;
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection('message_read')
.doc(docId)
.collection('message')
.where('userId', isNotEqualTo: uid)
.where('read', isEqualTo: false)
.snapshots(),
builder: (context, snapshot) {
print(snapshot.data?.size);
if (snapshot.data?.size != null) {
return Container(
child: Text('${snapshot.data?.size}'),
);
} else {
return Container();
}
},
);
}
}
===== Debug console ====
flutter: 1
flutter: null // Data changes to null immediately
message_read collection structure
message_read -> documentId -> message -> documentId -> field(userId(chat writer), read)
I'm trying get a snapshot data from firestore.
And I put the imported data into the text.
But Data changes to null immediately.
Since you use snapshots() the code is not just reading the data once, but then continues listening for changes to the data. So the two values you see mean that the snapshots() stream was triggered twice, the first time with a single message - and the second time without any messages.
My educated guess is that your code changes the read value of the document after it gets it, since it has now displayed that message to the user. But doing so means the document no longer meets the criteria of your query, so the snapshots stream gets a new event without that message in it.
Consider using a different mechanism for the query, for example I usually use a timestamp to determine what messages to show. Step by step:
Ensure each message document has a timestamp field.
For each user store (either in the database or in local storage of the app) when they last started the app.
Then request from the database the messages since they last started the app.
Make sure to start the query before you update the timestamp value, otherwise you'll never get any results.

How to read from an ID list in Firebase?

I have a list of IDs (Future<List<String>> _listaIdEmpresasF) and I would like to query data based on each ID in the list. I'm using two FutureBuilders: The first one to get the list of IDs, and the second one to display the data related of each ID.
My code looks like this...
#override
void initState() {
super.initState();
_listaIdEmpresasF = _usuarioBloc.cargarEmpresasDeUsuario(_prefs.idUsuario);
}
Widget _crearListado(BuildContext context) {
return FutureBuilder(
future: _listaIdEmpresasF,
builder: (BuildContext context, AsyncSnapshot snapshot) {
print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData != null){
final List<String> _listaIdEmpresas = snapshot.data;
return FutureBuilder(
future: _empresaDatosBloc.cargarEmpresaDatosListado(_listaIdEmpresas),
builder: (BuildContext context, AsyncSnapshot snapshot2) {
print(snapshot2.connectionState);
if (snapshot2.connectionState == ConnectionState.done && snapshot2.hasdata) {
final List<EmpresaDatosModel> _listaEmpresas = snapshot2.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _listaEmpresas.length,
itemBuilder: (context, i) {
return _crearItem(context, _listaEmpresas[i], i);
},
padding: EdgeInsets.only(left: 10.0, right: 10.0, top: 0.0, bottom: 20.0),
),
],
);
} else return Align(
alignment: Alignment.center,
child: Center (child: Image(image: AssetImage('Preloader.gif'), height: 500.0,)),
);
},
);
}
else return Align(
alignment: Alignment.center,
child: Center (child: Image(image: AssetImage('Preloader.gif'), height: 500.0,)),
);
},
);
}
To get the data from each ID, I'm using a loop as follows:
Future<List<EmpresaDatosModel>> cargarEmpresaDatosListado(List<String> listaIdEmpresas) async {
final List<EmpresaDatosModel> listaEmpresas = new List();
listaIdEmpresas.forEach((id) async {
Query resp = db.child('empresas/$id/datos');
final snapshot = await resp.once();
if (snapshot.value == null) return [];
final temp = EmpresaDatosModel.fromJson(Map<String,dynamic>.from(snapshot.value));
temp.idEmpresa = id;
listaEmpresas.add(temp);
print('${temp.nombre} subida');
await resp.once().then((snapshot) {
print("Los Datos de una Empresa se cargaron totalmente - ${temp.nombre}");
});
});
listaEmpresas.forEach((element) {print('Emp ${element.nombre}');});
return listaEmpresas;
}
The problem: I can't be able to obtain the full data. For example, if I have three IDs in _listaIdEmpresas, sometimes the app displays a list of one company (related to the first ID), sometimes two companies (first and secod ID) and sometimes the three companies. I supposed this is because the ConnectionState of the second snapshot is done before reading all three loops..
I tried to add a condition but it didn't work...
if (snapshot2.connectionState == ConnectionState.done && snapshot2.data.length == _listaIdEmpresas.length)
I cannot query all companies and filter it as a list of companies due to query security rules that I configured.
I would like to include the specific list of IDs in the query parameters, but I don't know if it's possible.
How is the right way to read data from a list of IDs?
snapshot.key will give you the id of the item

Animating stateful list item on the basis of api changes Flutter

Is there any way I can animate a single list item (say glowing border of a container) on the basis of changes on api? I have a listview which has price of items and when price changes in real time, I want to animate that particular item container. I have been searching for a solution for months now and I haven't found any yet.
StreamBuilder<MyProducts>(
stream: myProductsBloc.subject.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.error != null &&
snapshot.data.error.length > 0) {
return ShowError(
error: snapshot.data.error,
onTap: () => myProductsBloc.getMyProducts(),
);
} else if (snapshot.data.data.length == 0) {
return EmptyWidget();
}
return ListView.separated(
separatorBuilder: (context, i) =>
const SizedBox(height: 10),
padding: EdgeInsets.all(10),
itemCount: snapshot.data.data.length,
itemBuilder: (context, i) {
ProductData productData = snapshot.data.data[i];
return GestureDetector(
onTap: () =>
_buildBottomSheet(argument: productData),
child: ProductCard(product: productData));
},
);
} else if (snapshot.hasError) {
return Center(
child: Text('${snapshot.data.error}'),
);
} else {
return Center(
child: LoadingWidget(text: "Fetching products.."));
}
}),
Here I want to animate border of product card if price on the api changes.
in pseudo code,
Border(borderWidth: (myListItemId == 'special' ? controller_value*2 : 1)
...should do. With colors it is a bit more tricky, but you could use an index of a list with colors

Flutter/Dart Build a different widget every x item with ListView.builder

I want to display a special widget with listView.builder every fourth item.
I got these two lists
List<Event> events = [];
List<SpecialEvent> specialEvent = [];
These lists get filled by two Streambuilder like so
StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
//Spinner
}
events = snapshot.data.map((doc) => Event.fromDocument(doc)).toList();
if (events.isEmpty) {
//Code here
}
return StreamBuilder(
stream: stream,
builder: (BuildContext context,
AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (!snapshot.hasData) {
//Spinner
}
specialEvents = snapshot.data
.map((doc) => specialEvents.fromDocument(doc))
.toList();
eventCount = 0;
specialEventCount = 0;
return Flexible(
child: ListView.builder(
itemCount: events.length + specialEvents.length,
itemBuilder: (context, index) {
int newIndex = index + 1;
if (index != 0 &&
newIndex % 4 == 0 &&
specialEventCount.length > specialEventCount ||
specialEventCount.length > specialEventCount &&
events.length <= eventCount) {
specialEventCount += 1;
return buildSpecialEvent(specialEventCount - 1);
} else if (events.length > eventCount) {
eventCount += 1;
return buildEvent(eventCount - 1);
} else {
return Text("");
}
},
),
);
});
},
);
This code works really well when it comes to the initial list view build.
Every fourth item is a specialEvent.
The issue:
The app user can tap on each of these events/specialEvents and gets navigated to a different screen. There the user can do some stuff and change data inside the database.
This leads to a rebuild of the list view.
Depending on the position inside of the list view the builder does not start to rebuild the widget with index 0. That in turn leads to a chaos with the evenCount and the specialEventcount and everything gets mixed up.
Does anybody know how to proper solve this issue?
I guess there has to be a better solution to build a special widget every fourth item than mine.
Instead of ListView.builder, use ListView.separated,
hear is an example snippet
ListView.separated(
itemCount: 40,
itemBuilder: (context, index) {
return ListTile(
title: Text('$index'),
);
},
separatorBuilder: (context, index) {
return index == 0
? SizedBox.shrink()
: index % 4 == 0
? Container(
height: 20,
color: Colors.red,
)
: SizedBox.shrink();
},
),