how to use async/await in Listview builder - flutter

I have a table in my sqflite database containing the call history of the respective users. Now on my Call history page in flutter, I am showing the complete history data, fetched from sqflite up till now its working fine. But now I want to check whether the numbers are in my history list exist in contact. If yes, then I want to show their contact name and avatar in the list. Otherwise I just want to show the number. Here's my code:
List<Map<String, dynamic>> ok =
await DatabaseHelper.instance.getAllLogs(argv);
setState(() {
queryRows = ok;
});
var historyRecords = List<HistoryRecord>.from(queryRows.map((row) => HistoryRecord.fromJson(row)));
FutureBuilder<List<HistoryRecord>>(
future: _checkContact(historyRecords),
builder: (context, snapshot) {
return ListView.builder(
itemCount: historyRecords.length,
itemBuilder: (context, index) {
print(historyRecords[index]);
},
);
},
)
Future<List<HistoryRecord>> _checkContact(List<HistoryRecord> rec)async
{
for(int i=0;i<rec.length;i++) {
var conhere=await
ContactsService.getContactsForPhone(rec[i].callHistoryNumber);
//how should i map iterable contact list to Historyrecord
}
}

To call an asynchronous call in UI, you can use FutureBuilder. You can run a check for each and every items in the list like this:
FutureBuilder<bool>(
initialData: false, // You can set initial data or check snapshot.hasData in the builder
future: _checkRecordInContact(queryRow), // Run check for a single queryRow
builder: (context, snapshot) {
if (snapshot.data) { // snapshot.data is what being return from the above async function
// True: Return your UI element with Name and Avatar here for number in Contacts
} else {
// False: Return UI element withouut Name and Avatar
}
},
);
However I don't recommended this method since there would be too many async calls that will slow down the app. What I recommend is to run a check for all items in the queryRows first, then send it to UI.
First of all you should use an Object to represent your history records instead of Map<String, dynamic> to avoid bugs when handling data. Let's say we have a list of HistoryRecord objects, parse from queryRows. Let's call this list historyRecords
var historyRecords = List<HistoryRecord>.from(queryRows.map((row) => HistoryRecord.fromJson(row)));
Each object should have a Boolean property fromContact to check if it's in the Contacts or not. We can then do this:
Widget buildListView(historyRecords) {
return FutureBuilder<List<HistoryRecord>>(
future: _checkContact(historyRecords), // Here you run the check for all queryRows items and assign the fromContact property of each item
builder: (context, snapshot) {
ListView.builder(
itemCount: historyRecords.length,
itemBuilder: (context, index) {
if (historyRecords[index].fromContact) { // Check if the record is in Contacts
// True: Return your UI element with Name and Avatar here
} else {
// False: Return UI element without Name and Avatar
}
},
);
},
);
}
You can then check the contacts with the following property of HistoryRecord and function:
class HistoryRecord {
bool fromContact;
Uint8List avatar;
String name;
//... other properties
HistoryRecord({this.fromContact, this.avatar, this.name});
}
Future<List<HistoryRecord>> _checkContact(List<HistoryRecord> rec) async {
for (int i = 0; i < rec.length; i++) {
Iterable<Contact> conhere =
await ContactsService.getContactsForPhone(rec[i].callHistoryNumber);
if (conhere != null) {
rec[i]
..name = conhere.first.displayName
..avatar = conhere.first.avatar
..fromContact = true;
}
}
return rec;
}

You can use FutureBuilder to check each number like:
ListView.builder(
itemCount: history.length,
itemBuilder: (context, index) {
FutureBuilder(
future: checkContactExists(history[0]),
builder: (context, snap){
if(snap.hasData){
if(snap.data = true){
return PersonContact();
}else{
return JustNumber();
}
}
return Loading();
}
)
},
);

Related

How to build a "Recent files" list?

I'm struggling to find a solution to build a widget that displays "Recent Files".
To find the files I have the following
Future<List<File>> findFiles(String prefix) async {
List<File> files = [];
await for (var entity
in directory.list(recursive: true, followLinks: false)) {
File file = File(entity.path);
if (p.extension(file.path) == ".json") {
print("Found in:" + entity.path);
if (p.basename(file.path).startsWith(prefix)) files.add(file);
}
}
return files;
}
Then I call it using
var files = await FileManager().findFiles("test");
But I am not sure how to build a list view to display the name of each file as it is a Future and needs awaiting.
Any suggestions?
To display the result of your function in a ListView, since your function returns a Future, you should use a FutureBuilder:
FutureBuilder(
future: findFiles("bla"),
builder: (BuildContext context, AsyncSnapshot<List<File>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data?[index].path);
},
);
} else if (snapshot.hasError) {
return Text("Error: ${snapshot.error}");
}
return CircularProgressIndicator();
},
)
Here is a YouTube video by the Flutter team explaining FutureBuilder

Flutter: StreamBuilder with Firebase

I have a ListView of objects from Firebase in which I would like to have it refresh using a StreamBuilder when the data changes.
I can load up my list fine & when data changes my list does refresh.
The issue I am having is instead of the ListTile that has the change just updating, I see that tile being duplicated so I see the new change & the old change.
Here's my setup:
final ref = FirebaseDatabase.instance.reference();
late DatabaseReference itemRef;
late FirebaseDatabase database = FirebaseDatabase();
late StreamSubscription _objectInfoStreamSub; // Not sure if needed?
late List<CustomObject> data = [];
#override
void initState() {
super.initState();
final keys = Global.kData.keys;
for (final key in keys) {
// Initialize this...
itemRef = database.reference().child('ABC').child(key.toString());
}
// Load the data...
_setListeners();
}
// Here is where I load my data initially...
Future<void> _setListeners() async {
// Clear out our data before we reload...
data.clear();
final keys = Global.kData.keys;
for (final key in keys) {
_objectInfoStreamSub =
ref.child("ABC").child(key.toString()).onValue.listen(
(event) {
setState(() {
// Mapped the data...
final firebaseData = Map<String, dynamic>.from(event.snapshot.value);
// Create the Room...
final room = CustomObject.fromJson(firebaseData);
// Check if blocked...
// Logic to see if user is blocked
// check if isArchived
// Logic to see if room is archvied
if (!isBlocked && !isArchived) {
if (!data.contains(room)) {
data.add(room);
}
}
// Sort by date
// Logic to sort so the most recent is at top
});
},
);
}
}
// Here is my updated StreamBuilder...
SliverToBoxAdapter(
child: StreamBuilder<dynamic>(
stream: itemRef.child('ABC').onValue,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.hasData &&
snapshot.connectionState == ConnectionState.active) {
return ListView.builder(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: data.length,
itemBuilder: (BuildContext context, int index) {
return ChatRoom(
data: data[index],
);
},
);
} else {
return Container();
}
},
),
),
Not sure if this causes your problem, but try wrapping the ListView.builder() with StreamBuilder instead of only it's items. Because in your current state of code, if you would add another item and your data.length would change, the ListView.builder() wouldn't get rebuilt and it wouldn't build new data.

why snapshot.data gives me "Object?", but it don't gives returning of future?

i want to learn flutter step by step, I created UI, now i wanted to create fake firebase, later i will implement real firebase and i will use cubit, and then bloc.
I found this error, and i don't know how to remove it:
"AsyncSnapshot<Object?> snapshot
The argument type 'Object?' can't be assigned to the parameter type 'FirstListModel'."
this is my code before i whas trying to implement fake firebase, as im trying to do now:
itemBuilder: (context, index) {
final oneElement = firstList[index];
return buildPlate(index,await oneElement);
},
this itemBuilder is inside "ReorderableListView". Now im going step further and im trying to implement my fakeFirebase:
itemBuilder: (context, index) {
return FutureBuilder(
future: fakeFirebase.getElement(index.toString()),
builder: (context, snapshot) {
if (snapshot.hasData) {
return buildPlate(index, snapshot.data);
} else {
return const CircularProgressIndicator();
}
},
);
},
in the code above i have error in "snapshot.data"
in order to have better understanding, it is my function from fakeFirebase:
#override
Future<FirstListModel> getElement(String? id) async {
FirstListModel error = FirstListModel(text: "error", id: "404");
if (id != null) {
return firstList[int.parse(id)];
}
return error;
}
and this is my model:
class FirstListModel {
String text;
String id;
FirstListModel({
required this.text,
required this.id,
});
}
this is begining of my "buildPlate":
Widget buildPlate(int index, FirstListModel oneElement) => ListTile(
if i need to post more of my code, fell free to ask, it is my own aplication, just to lear flutter
"firstList" is a list of models, it containes objects that will be added by the user, simulating firebase
Define the type parameter for the future builder and cast the snapshot.data to non null by using the ! operator
return FutureBuilder<FirstListModel>(
future: fakeFirebase.getElement(index.toString()),
builder: (context, snapshot) {
if (snapshot.hasData) {
return buildPlate(index, snapshot.data!);
} else {
return const CircularProgressIndicator();
}
},
);

Do Firebase and Flutter support one-time reads on the web?

I'm using Firebase and Flutter to read a List of Objects (EspecieModel). It's working perfect in IOS and Android, however It doesn't work on the Web (an empty List is retrieved).
I'm reading from Firebase as follows ...
Future<List<EspecieModel>> cargarTipoEspecie() async {
final List<EspecieModel> tipoEspecie = [];
Query resp = db.child('PATH/tipoespecie');
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
}
And I'm using a Future Builder to display the information...
FutureBuilder(
future: _tipoEspecieBloc.cargarTipoEspecie(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
// print(snapshot.connectionState);
if (snapshot.connectionState == ConnectionState.done && snapshot.hasData{
// print(snapshot.data);
final _especies = snapshot.data;
return Stack(
children: <Widget>[
ListView.builder(
itemCount: _especies!.length,
itemBuilder: (context, i) {
return _crearItem(context, _especies[i], i);
},
),
],
);
} else if (snapshot.hasError) {
print(snapshot.error);
return Text(snapshot.error.toString());
}
else {
return //CircleProgressIndicator Code
);
}
},
),
I can't identify what I'm doing wrong
How to do a one-time Firebase Query that works well on IOS, Android, and also on the Web??
This won't work:
resp.onChildAdded.forEach((element) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(element.snapshot.value));
temp.idEspecie = element.snapshot.key;
tipoEspecie.add(temp);
});
await resp.once().then((snapshot) {
print("Loaded - ${tipoEspecie.length}");
});
return tipoEspecie;
The onChildAdded is not part of the await, so I doubt everything waits the way you seem to want. Just adding await in one place, does not make the rest of your code synchronous.
Instead consider using just once() and then populating your tipoEspecie array by looping over snapshot.value.values (a relatively new addition to the API).
var snapshot = await resp.once();
snapshot.value.values.forEach((node) {
final temp = EspecieModel.fromJson(Map<String,dynamic>.from(node.value));
temp.idEspecie = node.key;
tipoEspecie.add(temp);
});
return tipoEspecie;
Note: I'm not completely sure of the .forEach and the code in there. So if you get errors there, check what type you get back from .values and what node is, to get the correct key and values from it.

How to make a Future<int> become a regular int that is usable by arrays?

I am trying to keep a shared preferences counter to keep track of an array across my application. The error I running into is that I am returning the value of a Future and trying to use that to define an array index.
Here is the code:
var titles = [];
Future<int> getCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int currentCount = prefs.getInt('counter');
print('Current Count: $currentCount');
return currentCount;
}
getValue() async {
final value = await getCounter();
return value;
}
Widget _displayPage()
{
int index = getValue();
titles[index] = 'text';
return ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
assert(context != null);
return new ListTile(
title: Text('Temp Text'),
onTap: () async {
//getCounter();
}
);
}
);
}
When I try building this page, the exact error I get is: "Type Future is not a sub type of int"
Things I have tried:
1. Like the code above, I have tried making multiple functions to change it into a regular int
2. I have tried using the return value of getCounter() directly in the definition of the array index like so:
titles[getCounter()] = 'text
You are getting the error because you are not using a FutureBuilder.
Try using a FutureBuilder.
You can solve it by replacing your displayPage() widget with the code below.
Check the code below: It works perfectly fine.
Widget _displayPage()
{
// use a future builder
return FutureBuilder<int>(
// assign a function to it (your getCounter method)
future: getCounter(),
builder: (context, snapshot) {
if(snapshot.hasData){
// print your integer value
print(snapshot.data);
return new ListTile(
title: Text('Temp Text'),
onTap: () {
}
);
} else {
return Text(snapshot.error.toString());
}
}
);
}
I hope this helps.