How to read from an ID list in Firebase? - flutter

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

Related

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.

Lazy Loading prevent the search or filter in my app (flutter & firestore)

I am new with flutter,
I would be grateful if you could help me with this problem:
I have a huge set of firestore data that I have to display i a listview, so I had to use Lazy Loading when I used it, the problem appeared, and I can't search or filter in all the data, only the data shown
so I need to use lazy Load or pagination and in the same time can search or filter all the firestore data
enter image description here
and
enter image description here
void initState() {
_chatScrollController = ScrollController()
..addListener(() {
if (_chatScrollController!.position.atEdge) {
if (_chatScrollController!.position.pixels == 0)
;
else {
setState(() {
loadMoreMsgs = loadMoreMsgs + a;
});
}
}
});
super.initState();
}
stream: FirebaseFirestore.instance
.collection(kProductsCollection)
.limit(loadMoreMsgs)
.snapshots(),
builder: (context, snapshots) {
return (snapshots.connectionState == ConnectionState.waiting)
? const Center(
child: CircularProgressIndicator(),
)
: ListView.builder(
controller: _chatScrollController,
itemCount: snapshots.data!.docs.length,
itemBuilder: (context, index) {
var data = snapshots.data!.docs[index].data()
as Map<String, dynamic>;
List<Product> products = [];
for (var doc in snapshots.data!.docs) {
products.add(
Product(
pId: doc.id,
pName: doc.get(kProductName),
pPrice: doc.get(kProductPrice),
pUrl: doc.get(kProductUrl),
pIngredients: doc.get(kProductIngredients),
pCompany: doc.get(kProductCompany),
pDose: doc.get(kProductDose),
pCode: doc.get(kProductCode),
pClass: doc.get(kProductClass),
pCountry: doc.get(kProductCountry),
pDescription: doc.get(kProductDescription),
pPregnancyCategory:
doc.get(kProductPregnancyCategory),
),
);
}
if (products[index]
.pName
.toString()
.toLowerCase()
.contains(name.toLowerCase())) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 5, 20, 5),
child: GestureDetector(
onTap: () {
Navigator.pushNamed(context, ProductDetalis.id,
arguments: products[index]);
},
child: CardWidget(
width: width, height: height, data: data)),
);
}

how to change ui inside a build ? 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));
}

Flutter List UI not updating properly

I am building a one to one chat app using Flutter and firebase and I want to display all the chats under label of the day on which it happened,like it is on all major chat apps.
I retrieve data from firestore in ascending order of time by using order by command on the timestamp field of each message and as suggested by NiklasLehnfeld
i used groupBy method in the collection package to group my messages and used nested list view builders outer one for creating groups and inner one for filling those groups with data.
Here is the code
Widget build(BuildContext context) {
return FutureBuilder(
future: FirebaseAuth.instance.currentUser(),
builder: (ctx, futureSnapshot) {
if (futureSnapshot.connectionState == ConnectionState.waiting) return Center(child: CupertinoActivityIndicator());
return StreamBuilder(
stream: Firestore.instance
.collection('mychats/${futureSnapshot.data.uid}/${widget.recieverUid}')
.orderBy('createdAt', descending: true)
.snapshots(),
builder: (context, chatSnapshots) {
if (chatSnapshots.connectionState == ConnectionState.waiting)
return Center(child: CupertinoActivityIndicator());
else {
final List chatDocs = chatSnapshots.data.documents;
Map<String, List> grouped = groupBy(chatDocs, (chat) {
String dateTime = chat['dateTime'];
return dateTime;
});
(grouped.forEach((m, v) {
print('$m');
for (int i = 0; i < v.length; i++) {
print(v[i]['text']);
}
}));
return ListView.builder(
reverse: true,
itemCount: grouped.keys.length,
itemBuilder: (ctx, index1) {
String date = grouped.keys.toList()[index1];
List messages = grouped[date];
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text(date),
ListView.builder(
reverse: true,
primary: false,
shrinkWrap: true,
itemCount: messages.length,
itemBuilder: (context, index) {
return MyMessageBubble(
chatDocs[index].documentID,
chatDocs[index]['text'],
(chatDocs[index]['userId'] == futureSnapshot.data.uid) ? true : false,
ValueKey(chatDocs[index].documentID));
})
],
);
});
}
},
);
},
);
}
This is the list of messages that I am fetching
This is the UI .The results that i am printing in console are not displaying correctly in UI.I tried setting keys for both the list builders but no success. Is there any way it can be corrected
You are using the wrong list:
This:
chatDocs[index].documentID,
chatDocs[index]['text'],
(chatDocs[index]['userId'] == futureSnapshot.data.uid) ? true : false,
ValueKey(chatDocs[index].documentID));
should reference messages, not chatDocs. Because index is the index into messages.
the documentId is not work
chatDocs[index]['text'],
chatDocs[index]['userId'] == futureSnapshot.data.uid,
key: ValueKey(chatDocs[index].id),
just use only id

How to apply an async operation on list view builder?

I am trying to get set of encrypted data documents from firebase and display them on a list view on flutter.
I used a stream builder for obtaining data and started displaying it on the list view. But I cant perform the decryption operation on each data item as it is an async operation. What is the best way to do this?
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection(ScopedModel.of<User>(context).userId)
.snapshots(),
builder: (BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: new Container(
child: CircularProgressIndicator(),
));
default:
if (snapshot.data.documents.length == 0) {
return Container(
padding: EdgeInsets.all(16.0),
child: Row(
children: <Widget>[
Text('Empty',),
],
),
);
}
final docs = snapshot.data.documents;
return ScrollConfiguration(
behavior: ScrollBehavior(),
child: ListView.builder(
itemCount: len,
scrollDirection: Axis.horizontal,
itemBuilder: (context, position) {
// Where should I decrypt the below data?
// let decrypted = await myDecryptionFunction(docs[position]['myDataKey']) ;
// the above is not working
// this will show the encrypted text
return Text(docs[position]['myDataKey']);
}
....
For your situation you can use a StreamController with a helper class to hold the information.
The following is just an example but adapt it to your own needs.
// Helper classes
// adapt it to your own business case
class Notes {
String title;
String description;
Notes({this.title, this.description});
}
class NotesFromDb {
String connectionState;
bool hasError;
String error;
List<Notes> notes;
NotesFromDb({this.connectionState, this.hasError, this.error, this.notes});
}
// The Streambuilder
StreamBuilder<NotesFromDb>(
stream: sController.stream,
builder: (BuildContext context, AsyncSnapshot<NotesFromDb> snapshot) {
// Here you can check for errors
if (snapshot.data.hasError == true) {
return Container(
color: Colors.red,
child: Text(snapshot.data.error),
);
}
// Here you can check for your connection state
if (snapshot.data.connectionState == 'Loading') {
return Container(
color: Colors.yellow,
child: CircularProgressIndicator(),
);
}
// Here you can show your data
var info = snapshot.data.notes.map((doc) {
return Text(doc.title);
});
return Center(
child: Container(
color: Colors.deepOrange,
child: Column(
children: info.toList(),
),
));
})
// Here is how you can handle the decrypt data
// using a FloatingActionButton for loading the data (for example)
FloatingActionButton(
onPressed: () async { // you would need to add the async
List<Notes> theNotes; //just to hold the information
// Use this to allow to show the CircularProgressIndicator
sController.sink.add(NotesFromDb(connectionState: 'Loading'));
var snapshots = Firestore.instance.collection('notes').snapshots();
snapshots.listen((QuerySnapshot data) {
theNotes = data.documents.map((DocumentSnapshot doc) {
// Build your data
return Notes(
title: doc.data['title'],
description: doc.data['description']);
}).toList();
}, onError: (err, stack) {
// If an error happend then send the error to the stream
sController.sink
.add(NotesFromDb(hasError: true, error: err.error));
});
// Here you can to decrypt the documents with your function
var decryptDocuments = await Future.delayed(Duration(seconds: 2)); //Whatever future function
// Once you have the decrypt documents, you would need to send that to the stream.
// set the connectionState to Done, so the spinner is not showed anymore.
sController.sink.add(NotesFromDb(
hasError: false, connectionState: 'Done', notes: decryptDocuments));
},
child: Icon(Icons.arrow_forward),
)
Take the example just for illustration on how to do it.
Hope this help.