How to build a "Recent files" list? - flutter

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

Related

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.

FLUTTER: returned value is NULL

I am working on an Exercises app.
I am using FIREBASE with a StreamBuilder to retrieve an "exercise id" that has been favorited in my app. This is working fine and I get the correct Id.
I then pass it to a simple method to retrieve the actual "exercise" so that I can display it. This method passes through a List of exercises, checking if the exercise id, is found, and then I want it to return the exercise.
However I am always getting null returned. I can't figure out why this is.
I'd really appreciate some help, as very stuck with it right now.
Here below is my code:
This is the method:
Exercise getExerciseByID(String exerciseId) {
_exercises.forEach((exercise) {
print('COMPARE1: ${exercise.id} AND $exerciseId');
if (exercise.id == exerciseId) {
print('COMPARE2: ${exercise.id} AND ${exerciseId}');
return exercise;
}
});
}
Note that the two print statements both print and show matching id's
And here is the Build with StreamBuilder etc..
return Scaffold(
body: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('favorites')
.doc('${_firebaseAuth.currentUser.uid}')
.collection('userFavorites')
.snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, index) {
//TODO: GET EXERCISE FOR DOC
String _exerciseId = snapshot.data.docs[index].id;
_exercise = getExerciseByID(_exerciseId);
//TODO: DISPLAY CARD
print("Retrieved Exercise: $_exercise");
return Center(child: Text(""));
},
);
},
// padding: EdgeInsets.only(left: sideMargin),
// },
),
);
Note: the print statement: print("Retrieved Exercise: $_exercise"); is where I am always finding the NULL.
Many thanks for any assistance.
change this
Exercise getExerciseByID(String exerciseId) {
_exercises.forEach((exercise) {
print('COMPARE1: ${exercise.id} AND $exerciseId');
if (exercise.id == exerciseId) {
print('COMPARE2: ${exercise.id} AND ${exerciseId}');
return exercise;
}
});
}
to this
Exercise getExerciseByID(String exerciseId) {
Exercise returningExercise;
_exercises.forEach((exercise) {
print('COMPARE1: ${exercise.id} AND $exerciseId');
if (exercise.id == exerciseId) {
print('COMPARE2: ${exercise.id} AND ${exerciseId}');
returningExercise = exercise;
}
});
return returningExercise;
}
You are returning inside ForEach Statement. This doesn't return to your function.
put return statement outside ForEach statement.
Note: This solution is not best. But you now know whats going wrong.

how to use async/await in Listview builder

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();
}
)
},
);

Flutter firestore streambuilder with a future

I have a simple question. The reference to my firestore collection is dynamic. In this piece of code, getDocumentReference() gives me a reference to document after checking the user's email.
I use this document reference to get my snapshots.
Future<Stream<QuerySnapshot>> getHabits() async {
DocumentReference document = await getDocumentReference();
var snapshots = document.collection('habits').snapshots();
return snapshots;
}
As you can see, I want to use this Future<Stream<QuerySnapshot>> for a streambuilder. How can I do that? I tried something like this. But it is not taking the future as input to stream
return StreamBuilder(
stream: getHabits(),
);
You can wrap it in a FutureBuilder:
return FutureBuilder<Stream<QuerySnapshot>>(
future: getHabits(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return StreamBuilder(stream: snapshot.data); // Success
} else if (snapshot.hasError) {
return Text('${snapshot.error}'); // Error
} else {
return CircularProgressIndicator(); // Loading
}
},
);