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.
Related
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.
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;
});
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
}
I am trying to set the value of a variable based on the return value of a field (bool) in Firestore.
So far, this is what I have come up with;
First I call the method here;
#override
void initState() {
super.initState();
getAdventureStatus();
}
And this is the method.
Future getAdventureStatus() async {
Firestore.instance
.collection('adventures')
.document(widget.currentUser.id)
.collection('user_adventures')
.where('adventure_active', isEqualTo: 'false');
setState(() {
adventureActive = true;
print('${adventureActive.toString()}');
});}
What am I doing wrong and what is the most pragmatic way of doing this?
I think you just remove the set state call and set the bool to true direct.
Future getAdventureStatus() async {
Firebase.instance.document()
...
adventureActive = true;
}
In my class I'm loading some files, and for efficiency I wanted to make a thread safe cache. I see in the map class that there is a putIfAbsent method, but it doesn't accept async types. Also not sure if this structure in general is safe to use.
This is the style of what I'm trying to do:
final Map<String, String> _cache = new Map();
Future<String> parse(final String name) async {
_cache.putIfAbsent(name, () async { // this async is not allowed
return await new File(name).readAsString();
});
return _cache[name];
}
Since I can use async on the parameter I've opted to use locks instead, but it makes the code far more verbose..
final Lock _lock = new Lock();
final Map<String, String> _cache = new Map();
Future<String> parse(final String name) async {
if (!_cache.containsKey(name)) {
await _lock.synchronized(() async {
if (!_cache.containsKey(name)) {
_cache[name] = await new File(name).readAsString();
}
});
}
return _cache[name];
}
Does anyone know how I can simplify this code, or if there are better libraries I can use for thread safe cache?
What do you mean by "this async is not allowed"? I see no particular issue with the putIfAbsent code, and I believe it should work.
The one probelem I see is that the cache is not caching futures, but strings. Since your function is returning a future anyway, you might as well store the future in the cache.
I would write it as:
final Map<String, Future<String>> _cache = new Map();
Future<String> parse(final String name) =>
_cache.putIfAbsent(name, () => File(name).readAsString());
but apart from fixing the _cache map type, that is effectively the same, it's just avoiding creating and waiting for a couple of extra futures.
I've created an extension to support an asynchronous action for putIfAbsent:
extension MapUtils<K, V> on Map<K, V> {
Future<V> putIfAbsentAsync(K key, FutureOr<V> Function() action) async {
final V? previous = this[key];
final V current;
if (previous == null) {
current = await action();
this[key] = current;
} else {
current = previous;
}
return current;
}
}
You can use like this:
final Map<String, String> _cache = {};
Future<String> parse(final String name) async {
return await _cache.putIfAbsentAsync(
name,
() async => await File(name).readAsString(),
// ^^^^^ this `async` is now allowed
);
}