Flutter Asynchronous Data Retrieval - flutter

My app has 3 main pages:
Listings Home
Social Home
Search Home
When the user logs in I create a local "User" object, in the constructor I fire off 3 async functions that are supposed to retrieve the required data for the above 3 pages:
class User {
ParseUser user_;
ParseObject user_info_;
Position? current_location_;
bool retrieve_related_data_;
List<Listing> listings_ = []; // data for Listings Home
List<Conversation> conversations = []; // data for Social Home
List<Search> serach_results_ = []; // data for Search home
User(this.user_, this.user_info_, this.current_location_, this.retrieve_related_data_) {
if(retrieve_related_data_) {
print("RETRIEVING USER DATA");
getListings();
getConversations();
getSearchResults();
}
}
// returns all listings connected to the logged in user
void getListings() async {
listings_ = (await back4app_listing.getListings(user_info_));
}
// returns all conversations connected to the logged in user
void getConversations() async {
conversations = (await back4app_conversation.getConversations(user_info_));
}
// returns all search results connected to the logged in user
void getSearchResults() async {
serach_results_ = (await back4app_search.getSearchResults(user_info_))
}
}
If the user for example navigates to the Listing Home page before the getListings() function has finished and put the data in the listing_ list, how do I await for the function to complete? Same scenario if they were to navigate to the other pages before the functions had returned. I want the pages states to update once these functions return from retrieving data.
I was thinking of creating bools for each async function and toggling them from false to true depending on the state the function is in, but I feel that there is a better way to implement this.

Related

Flutter listeners keep listening to old events every time the page is rebuilt

I am currently developing an order management app for a restaurant, in which users can place orders through the app. Once orders are placed, an admin (restaurant manager) can accept orders on the admin portal, and the user will be notified to go pick up their food when the order is completed.
I am using flutter's "awesome notifications" package to handle notifications.
In the method below, I am essentially listening for new entries into the "ManagedOrders" table of our database (firebase real time database). Accepted orders are moved into this table, therefore I want to notify the user that their order has been accepted if the order moved into this table contains a customerID equal to the customerID of the user currently logged in.
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import '../models/notifications.dart';
final DatabaseReference _dbRef = FirebaseDatabase.instance.ref();
late StreamSubscription _orderStream;
String loggedInUserID = "9ibdsUENaAdnpA3qxm35Y8xRe9F3"; //Hard coded for now
Map<dynamic, dynamic> databaseMapper = {};
List<String> placedOrderIDsList = [];
void listenForAcceptedOrders() async {
//This method listens for accepted orders and displays a notification
//It checks for new records entered into the managedOrders table containing the same customerID as the user currently logged in.
//If the customerID matches the ID of the current user logged in, it means that an order placed by THIS user has been accepted.
_orderStream = _dbRef.child("ManagedOrders").onChildAdded.listen((event) {
databaseMapper = event.snapshot.value as Map;
String customerID = databaseMapper["customerID"].toString();
print("CUSTOMERID ______......>>" + customerID);
if (customerID == loggedInUserID) {
acceptedOrderNotification(); //A notification defined in another class
} else {
print("NO MATCH FOUND");
}
});
//_orderStream.cancel();
}
I then call this method in my homescreen in the initState method:
listenForAcceptedOrders();
The issue I am having is once a record is added to the "ManagedOrders" table with a customerID that matches the ID of the user logged in, I continue to receive the notification every time I navigate back to the home page, even if I delete the record from the "ManagedOrders" table.
I tried to cancel the listeners at the end of the "listenForAcceptedOrders()" method, but that results in no listeners at all (I.E adding a record to the "ManagedOrders" table does not trigger any notification).
How do I make it so that the user can receive the notification once, and not have it repeat every time they navigate to the home page?
Any help would be much appreciated!
just add a bool variable and set it to true once you receive the notification so it will not repeat it self again
Look the correct code below:
import 'dart:async';
import 'package:firebase_database/firebase_database.dart';
import '../models/notifications.dart';
final DatabaseReference _dbRef = FirebaseDatabase.instance.ref();
bool example = false;
late StreamSubscription _orderStream;
String loggedInUserID = "9ibdsUENaAdnpA3qxm35Y8xRe9F3"; //Hard coded fornow
Map<dynamic, dynamic> databaseMapper = {};
List<String> placedOrderIDsList = [];
void listenForAcceptedOrders() async {
_orderStream = _dbRef.child("ManagedOrders").onChildAdded.listen((event) {
databaseMapper = event.snapshot.value as Map;
String customerID = databaseMapper["customerID"].toString();
print("CUSTOMERID ______......>>" + customerID);
if (customerID == loggedInUserID) {
if(example = false){
acceptedOrderNotification(); //A notification defined in another class
example = true;
}
} else {
print("NO MATCH FOUND");
}
});
//_orderStream.cancel();
}

Does streambuilder from firebase rtdb will update list<User> user data?

currently I understadn with the method streamBuilder I can fetch updated data and add in the List<User> users.
But what if this user which is already added in the List<User> users has updated data, and then it could be double adding this user data in the List<User> users right?
Could you plz show me how to confirm whether for the new coming data List<User> users has already same userId, if yes, the new data / userId will replace this exisiting userId?
If the user is deleted from Firebase rtdb, the stream will be notified, and therefore remove this user from List<User> users?
here is example, my concern is since stream will always add data to the List users, but what if this user is removed from database or disconnect, how to remove this user from this list?
_streamSubscription = availableUserStream.onValue.listen((snap) {
if (snap.snapshot.exists && snap.snapshot.value != null) {
DataSnapshot snapshotData = snap.snapshot;
for (var userSnapshot in snapshotData.children) {
final data = Map<String, dynamic>.from(userSnapshot.value as Map);
List<User> users = [];
User newUser = User.fromJson(data);
users.add(newUser);
firebaseController.setUsers(users: users);
}
}
});
So I thought to do a double confirm here if this user is still exisitng in the database:
User getRandomSenderUser({User asReceiverUser}) {
if (availableSenderUsersList.isNotEmpty) {
final random = Random();
var i = random.nextInt(availableSenderUsersList.length);
User randomUser = availableSenderUsersList[i];
bool thisRandomUserIsAvailable; //TODO
I don't know how to do this check, e.g. if this randomerUser is unavailable, so I need to get next randomUser, so it should be a loop? But it will slow down the response speed.
updateSenderUserAvailableStatus(asReceiverUser:asReceiverUser,connectionUser: randomUser);
return randomUser;
} else {
return null;
}
}
thank you!
Update:
Here is the example code, so now I understand stream will pass user data to List<User> users, but in my way there will always be user who is added in this list before, but was already removed from database, my plan is using while loop for double confirming to remove unavailable user when getting the randomUser, but it sounds not smart and still waste time I guess....
#override
void initState() {
_listenAvailableUsers();
}
_listenAvailableUsers() {
var availableUserStream =
FirebaseDatabase.instance.ref().child('/waitingList');
_streamSubscription = availableUserStream.onValue.listen((snap) {
if (snap.snapshot.exists && snap.snapshot.value != null) {
DataSnapshot snapshotData = snap.snapshot;
for (var userSnapshot in snapshotData.children) {
final data = Map<String, dynamic>.from(userSnapshot.value as Map);
List<User> users = [];
User newUser = User.fromJson(data);
users.add(newUser);
firebaseController.setUsers(users: users);
}
}
});
}
Here is the method I though to confirm if the randomUser is still existing in the database:
Future<User> getRandomSenderUser({User asReceiverUser}) async {
if (availableSenderUsersList.isNotEmpty) {
User randomUser;
while (true) {
final random = Random();
var i = random.nextInt(availableSenderUsersList.length);
randomUser = availableSenderUsersList[i];
DatabaseEvent event = await databaseReference
.child('/waitingList/${randomUser.userId}')
.once();
print('randomUser is ${randomUser.toString()}');
if (event.snapshot.value != null) {
break;
}
}
await updateSenderUserAvailableStatus(
asReceiverUser: asReceiverUser, connectionUser: randomUser);
print('connectionUserId is $connectionUserId');
return randomUser;
} else {
return null;
}
}
Since you're listening to the onValue of a path in the database, the DataSnapshot you get will contain the entire data at that path. When there was only a small change in the data, the server will only send that update to the client, but the SDK will then merge that with the existing data and still fire an event with a snapshot of all the data at the path.
Since you're starting with an empty list (List<User> users = [];) each time you get an event from the stream, that means you're rebuilding the entire lit of users each time, which seems correct to me.

How to close Functions in ChangeNotifier Provider Flutter

How to close a Function without disposing it. I needy this answer because when I log out, I need to close the functions in ChangeNotifier Class.
This Is my ChangeNotifier Class:
class ChatAndRequestProvider extends ChangeNotifier {
bool _areThereNewChatsAndRequests = false;
bool get areThereNewChatsAndRequests => _areThereNewChatsAndRequests;
set areThereNewChatsAndRequests(bool value) {
_areThereNewChatsAndRequests = value;
notifyListeners();
}
List _chatsList = [];
List get chatsList => _chatsList;
set chatsList(List list) {
_chatsList = list;
notifyListeners();
}
getChats() async {
var prefs = await SharedPreferences.getInstance();
print('The getChats Id is ${prefs.getString(kUserId)}');
FirebaseDatabase.instance
.reference()
.child('users')
.child(prefs.getString(kUserId))
.child('friendsArray')
.onValue
.listen((snapshot) {
Map list = snapshot.snapshot.value;
print('map is $list');
var newItems = [];
if (list != null) {
list.forEach((key, value) {
newItems.add(value);
});
chatsList = newItems;
var globalArray = [];
for (var item in newItems) {
if (item[kLastTimestamp] != item[kLastTimestampSeen]) {
areThereNewChatsAndRequests = true;
}
var status;
switch (item['friendsStatus']) {
case 'friends':
status = RequestStatus.alreadyAFriend;
break;
case 'notFriends':
status = RequestStatus.noRequest;
break;
case 'blocked':
status = RequestStatus.userThatBlockedMe;
break;
case 'unblocked':
status = RequestStatus.noRequest;
break;
}
globalArray.add({kUserId: item[kUserId], kTypeOfRequest: status});
}
valuesList = globalArray;
} else {
deleteFromList(null, RequestStatus.alreadyAFriend);
chatsList = [
{kUserId: 'null'}
];
}
});
}
So for example when I log In as user1 and I call this function in the LoadingScreen() I get all of users that are my friends, and I can go to the chats screen List and chat with my friends. Up to this point there is no issue. But when I log out and when I log in with another account lets say user2 and I call this function again, then I get error and two responses because I am calling this function twice. I am not using Auth Packet, I have my own database on MongoDB where I store user Info, but requests and chats are stored on RealTime Database.
So my Question is:
When user1 logs out of my app, I can not call dispose() on provider because if he wants to log in again to another account, he will get an error because Provider was disposed, so how can I stop listening to my database when user logs out and call this function again. Thank You very Much!!
I´m not sure if this works because I don't fully understand the flow of your app but you say that
I can not call dispose() on provider because if he wants to log in
again to another account,
when the users logs out shouldn't the app return to the first screen disposing the provider? (unless you create it in the MaterialApp, I'm not sure about that either). You could save the instance of the Firebase listener and then close it when you log out/ dispose the provider
var _myListener;
getChats() async {
var prefs = await SharedPreferences.getInstance();
print('The getChats Id is ${prefs.getString(kUserId)}');
_myListener = FirebaseDatabase.instance
.reference()
.child('users')
.child(prefs.getString(kUserId))
.child('friendsArray')
.onValue
.listen((snapshot) ...
....
/// The rest of your code
}
void closeListener(){ //call it when the user logs out
_myListener?.close();
}
#override
void dispose(){
closeListener(); // or call it in the dispose if you want
//to dispose and create a new provider when the user logs out/ sign in
super.dispose();
}

Flutter: Firebase user's name doesn't get updated when user logs in for the first time

I am trying to fetch a user's ID when a person is logged in with their firebase account. When I try to login a new user for the first time, I can fetch their id however the user's details only reflect after the person navigates from one page to another using the navigation bar. Is there a way I can get rid of that? I want the user's display name to reflect as soon as they create a new account.
This is my current code that I call in my initState()
getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
setState(() {
loggedInUser = user;
id = user.uid;
username = user.displayName;
});
}
} catch (e) {
print(e);
}
}

When I read data from firebase, it show my data. But it also show all user data. Not only my data. But I want only my data. Not other user

For Write Data. Here is the code. I call data from users account. But there are multiple user data. I only show my data. But it shows all user data.
getDriversList() async {
return await Firestore.instance.collection('users').getDocuments();
}
QuerySnapshot querySnapshot;
#override
void initState() {
super.initState();
getDriversList().then((results) {
setState(() {
querySnapshot = results;
});
});
}
You can use userId as documentID when create those users document.
Then callawait Firestore.instance.collection('users').document(your user Id).get(); to get users own data.