How can I wait for my future function in map function? (Flutter) - firebase

So, I'm building my app in Flutter and unfortunately, I have recently come across an error. So what I want to do in my TestProvider class is to get data from firestore (what getQuestionFromFirebase() function is doing), and after that happens, I want to create a map from DocumentSnapshot (what questionMapFromFirebase() function is doing). And there comes an error, because I can't async in map function so my function doesn't wait for the result from previous function, and returns null. Any solutions? *I tried to return map from getQuestionFromFirebase() - Future, but later I can't use value from it because, my function wants pure map.
class TestProvider {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<DocumentSnapshot> getQuestionFromFirebase(String documentId) async {
return await _firestore.collection('questions').doc(documentId).get();
}
Map questionMapFromFirebase(String documentId) {
Map questionMapFromFirebase;
getQuestionFromFirebase(documentId).then((DocumentSnapshot carSnapshot) => {
questionMapFromFirebase = carSnapshot.data(),
});
return questionMapFromFirebase;
}
}
Later I'm using this function there:
I'm using this function later there
List<Question> listOfQuestions() {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions;
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
_testProvider.questionMapFromFirebase(range[1].toString())));
}
return listOfQuestions;
}
And that's creating error when Future occurs.
The argument type 'Future<Map<dynamic, dynamic>>' can't be assigned to the parameter type 'Map<String, dynamic>'.
Edit:
So recently I've made some changes to my code and now it looks like that
class TestProvider {
FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<DocumentSnapshot> getQuestionFromFirebase(String documentId) async {
return await _firestore.collection('questions').doc(documentId).get();
}
Future<Map> questionMapFromFirebase(String documentId) async {
DocumentSnapshot ds = await getQuestionFromFirebase(documentId);
return ds.data();
}
}
and repository
class TestRepository {
final int amountOfQuestions;
TestRepository({
#required this.amountOfQuestions,
});
TestProvider _testProvider;
Future listOfQuestions() async {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions;
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
await _testProvider.questionMapFromFirebase(range[i].toString())));
}
return listOfQuestions;
}
}
The problem I started to see that is that every time i tried to call function questionMapFromFirebase from TestProvider, it has been working just fine. But when i tried to call it from TestRepository it throw the error:
E/flutter (13348): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)] Unhandled Exception: NoSuchMethodError: The method 'questionMapFromFirebase' was called on null.
E/flutter (13348): Receiver: null
E/flutter (13348): Tried calling: questionMapFromFirebase("2")
Any other sugestions how can I handle it?

Future<Map> questionMapFromFirebase(String documentId) async {
DocumentSnapshot ds = await getQuestionFromFirebase(documentId);
return ds.data();
}
Edit
check FutureBuilder class
example, it will be inside your widget tree where the list need to be shown.
return FutureBuilder(
future: _loadQuestions(),
builder: (context, snapshot) {
if(snapshot.connectionState == ConnectionState.done){
return widgetForListing(snapshot.data);
}
return Center(child: Text('Loading...'));
},
);
And your _loadQuestions function will be as
_loadQuestions() async {
List<int> range = numberInRange(amountOfQuestions);
List<Question> listOfQuestions = [];
for (int i = 1; i <= amountOfQuestions; i++) {
listOfQuestions.add(Question.fromMap(
await _testProvider.questionMapFromFirebase(range[1].toString())));
}
return listOfQuestions; //you can get this list in **snapshot.data** of future builder
}

Related

How to sequentially call the Getx controllers

My Task is :
I have an list of orders in firebase in users collection ,
I want to get information of those orders which is in orders collection,
And moment a list of order is updated in the firebase users collection.
The orders list should be automatically updated to get the latest information from the orders collection.
But for that i have to wait until list of orders is fetched from the users collection and only then i can query from orders collection about those orders..
I am stuck here,
And i want to actually understand Getx's bindStream , ever(), and observable variables,and Obx() is used in widget , But what if it is normal variable and i want to listen to it's changes ,how to do that, because Obx() can be only used while you use Widget
So far my code:
controllers.dart
UtilityController utilityController = UtilityController.instance;
CartController cartController = CartController.instance;
OrderController orderController = OrderController.instance;
UserModel.dart
class UserModel {
String? uid;
String? email;
String? name;
bool? isAdmin;
String? password;
List<CartItemModel>? cart;
String? token;
List<String>? orders;
UserModel({this.uid, this.email, this.name, this.isAdmin, this.password, this.cart, this.token, this.orders});
UserModel.fromSnapshot(DocumentSnapshot snapshot) {
uid = snapshot.id;
name = snapshot['name'];
token = snapshot['token'];
cart = _convertCartItems(snapshot['cart'] ?? []);
orders = new List<String>.from(snapshot['orders']);
}
List<CartItemModel> _convertCartItems(List cartFomDb) {
List<CartItemModel> _result = [];
if (cartFomDb.length > 0) {
cartFomDb.forEach((element) {
_result.add(CartItemModel.fromMap(element));
});
}
return _result;
}
}
UtilityController.dart
class UtilityController extends GetxController {
static UtilityController instance = Get.find();
Rx<UserModel> userModel = UserModel().obs;
#override
void onReady() {
super.onReady();
getUserType();
userModel.bindStream(listenToUser());
}
Stream<UserModel> listenToUser() {
return FirebaseFirestore.instance
.collection("users")
.doc(FirebaseAuth.instance.currentUser?.uid)
.snapshots()
.map((snapshot) => UserModel.fromSnapshot(snapshot));
}
OrderController.dart
class OrderController extends GetxController {
static OrderController instance = Get.find();
RxList<OrderModel> orders = RxList<OrderModel>([]);
#override
void onReady() {
super.onReady();
orders.bindStream(getAllOrders());
ever(utilityController.userModel, function); --> I am using this , but i know this is not the correct way
}
function(UserModel userModel) {
getAllOrders();
}
Stream<List<OrderModel>> getAllOrders() {
return FirebaseFirestore.instance
.collection("orders")
.where(FieldPath.documentId, whereIn: utilityController.userModel.value.orders)
.snapshots()
.map((query) => query.docs.map((item) => OrderModel.fromMap(item.data(), item.id)).toList());
}
}
The utilityController.userModel.value.orders is null !!! it's not yet loaded, so all the orders are fetched :(
And even if the orders are changed... But new orders are not fetched from the orders collection
How to get over this?
Consider using like this.
class OrderController extends GetxController {
static OrderController instance = Get.find();
final isLoading = true.obs;
final isDone = false.obs;
var orders = OrderModel([]).obs;
#override
void onInit() async {
await listenForOrders();
super.onInit();
}
Future listenForOrders() async {
isLoading.value = true;
isDone.value = false;
Stream<QuerySnapshot> _userOrders;
_userOrders = getAllOrders();
_userOrders.listen((QuerySnapshot query) {
if (query.docs.isNotEmpty) {
query.docs.forEach((element) {
orders.addIf(!orders.contains(element), OrderModel.fromDocumentSnapshot(element));
});
} else {
isDone.value = true;
}
isLoading.value = false;
});
}
Stream<QuerySnapshot> getAllOrders() {
return FirebaseFirestore.instance
.collection("orders")
.where(FieldPath.documentId, whereIn: utilityController.userModel.value.orders)
.snapshots();
}
}
The best approach is to use the worker functions provided by getx controller like:
ever - is called every time the Rx variable emits a new value.
everAll - Much like ever , but it takes a List of Rx values Called every time its variable is changed. That's it.
once - is called only the first time the variable has been changed.

Can't assign a data value to a string - returns null - flutter

In my code, am trying a assign a string value to an empty string and display on the page but it keeps showing null but when I print it out, it shows the value.
String fName = '';
#override
void initState() {
super.initState();
getData();
}
getData() async {
FirebaseAuth _auth = FirebaseAuth.instance;
User _firebaseUser = _auth.currentUser;
print("============ MyHome ================");
print(_firebaseUser.uid);
_currentUser = await Database().getUserData(_firebaseUser.uid);
if (_currentUser != null) {
fName = _currentUser.firstName;
print(_currentUser.firstName);
}
}
database
Future<UserData> getUserData(String uid) async {
UserData returnValue = UserData();
try {
DocumentSnapshot _docSnapshot =
await _firestore.collection("users").doc(uid).get();
returnValue.uid = uid;
returnValue.firstName = _docSnapshot.data()["firstName"];
returnValue.lastName = _docSnapshot.data()["lastName"];
returnValue.userMail = _docSnapshot.data()["userMail"];
returnValue.userType = _docSnapshot.data()["userType"];
print("====================== on getData =============");
print(returnValue.firstName);
} catch (e) {
print(e);
}
return returnValue;
}
And whenever I try displaying the data it gives me null
Text("Hello, $fName"),
Please how do I do this or am I missing something
use setState to rebuild the widget tree with the value:
setState(() {
fName = _currentUser.firstName;
});
Since the getData function is async, flutter has already built the widget tree before getData finished. You'll now have to update the state using setstate.
setState(() {
fName = _currentUser.firstName;
});
You need to set the new state since we have made changes to the previous state (since your getData function is async.
setState(() {
fName = _currentUser.firstName;
});

Wait for stream inside a Future: Flutter

I want to check if the Firebase DB is connected or not, so I have to use a Future to return a boolean
Have a check at my code..
#override
Future<bool> isAvailable() async {
bool ret = false;
await firebaseInstance.reference().child('.info/connected').onValue.listen((event) {
ret = event.snapshot.value;
});
return ret;
}
the firebaseInstace.reference is a StreamSubscription type and does not wait for the future to return me a result.
please help.
If you only need to know the current value, use once().then instead of onValue.listen
#override
Future<bool> isAvailable() async {
var snapshot = await firebaseInstance.reference().child('.info/connected').once();
return snapshot.value;
}
Instead of awaiting the end of the stream subscription (it never ends), just take the first value:
#override
Future<bool> isAvailable() => firebaseInstance.reference().child('.info/connected').onValue.first;
You can put the StreamSubcription in a variable
StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {
// you code here
if (someCondition == true) {
subscription.cancel();
}
});
More can be found here is there any way to cancel a dart Future?
You can do the following:
#override
Future<bool> isAvailable() async {
bool ret = false;
Stream<Event> events =
FirebaseDatabase.instance.reference().child('.info/connected').onValue;
await for (var value in events) {
ret = value.snapshot.value;
}
return ret;
}
onValue returns a Stream<Event> and then you can use await for to iterate inside the Stream and get the data, and then it will return.

Flutter: Future.then() never getting invoked

I'm currently playing around with Futures in flutter. I've some async functions that return a Future object. I register a Listener on the Future object with then(), so that I can update the ui as soon as the value comes in.
But the result is empty, because then() returns before all notes are loaded from the file system.
Future<List<Note>> loadNotes() async {
NoteService().findAll().then((result) {
result.forEach((note) => print(note.title)); //not printing -> result is emtpty...
});
}
//NoteService class
Future<List<Note>> findAll() async {
return noteRepository.findAll();
}
//NoteRepository class
#override
Future<List<Note>> findAll() async {
final Directory dir = await directory;
dir.list().toList().then((List<FileSystemEntity> list) async {
List<String> paths = List();
list.forEach((entity) => paths.add(entity.path));
List<File> _files = List();
paths.forEach((path) => _files.add(File(path)));
List<Note> notes = await _extractNotes(_files);
return Future.value(notes);
});
return Future.value(List());
}
Future<List<Note>> _extractNotes(List<File> _files) async {
List<Note> notes = List();
_files.forEach((file) {
String content = file.readAsStringSync();
print('content: ' + content); //this is getting printed correctly to the console
Map<String, dynamic> a = jsonDecode(content);
if(a.containsKey('codeSnippets')) {
notes.add(SnippetNoteEntity.fromJson(jsonDecode(content)));
} else {
notes.add(MarkdownNoteEntity.fromJson(jsonDecode(content)));
}
});
return Future.value(notes);
}

How to convert a sub collection to list with flutter and firestore?

I have an object Dish who containes a list of ingredients and I want to get them. How can I do?
In Firebase, Dish is a Document and Ingredient is a sub collection. I tried this but it doesn't work.
class Dish{
String name;
DocumentReference reference;
List<Ingredient> ingredients;
Dish.fromMap(Map<String, dynamic> map, {this.reference}){
this.name = map['name'];
this
.reference
.collection("ingredients")
.snapshots()
.listen((QuerySnapshot snap) {
final List<DocumentSnapshot> ingredientsDocuments = snap.documents;
List<Ingredient> ing = [];
for (var i = 0; i < ingredientsDocuments.length; i++) {
ing.add(Ingredient.fromSnapshot(ingredientsDocuments[i]));
}
this.ingredients = ing;
});
}
Dish.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Dish<$String>";
}
class Ingredient{
final String name;
final DocumentReference reference;
Ingredient.fromMap(Map<String, dynamic> map, {this.reference})
: assert(map['name'] != null),
name = map['name'];
Ingredient.fromSnapshot(DocumentSnapshot snapshot)
: this.fromMap(snapshot.data, reference: snapshot.reference);
#override
String toString() => "Ingredient<$String>";
}
How are you trying to fetch data from Firestore using Dish class? Were you using any asynchronous task(i.e. Future)? What's not working in your implementation? Any errors that you received?
Since I'm unable to run the repro you've provided, here's a sample code that you can try.
List<Ingredient> ingredients;
// call getDocuments() to fetch data from Firestore and add it to the List
Future<void> getDocuments() async {
ingredients = List();
var collection = FirebaseFirestore.instance
.collection('ingredients');
collection.get().then((value) {
value.docs.forEach((element) {
setState(() {
// add the object to the List
ingredients.add(Ingredient(Ingredient.fromMap(element.data())));
});
});
});
}
As for the Object, it can be as simple as this. No need to pass DocumentReference since we'll only be using it to map the data to the Object and be able to add it in the List.
class Ingredients {
var name;
Ingredients(Ingredients document) {
this.documentName = document.getName();
}
dynamic getName() => name;
Ingredients.fromMap(Map<dynamic, dynamic> document)
: name = document['name'];
}
You can check a working sample I've posted in here. It has pagination added, but it should have a similar approach with the code snippets I've shared here.

Resources