Streambuilder only showing data from local cache on first launch of app - flutter

I'm using StreamBuilder to stream data from firestore.
StreamBuilder<QuerySnapshot>(
stream: _firestore.collection('meals').where('email', isEqualTo: loggedInUser.email).orderBy('date', descending: true).snapshots(),
If i take out the .where section of the stream, it returns all data to the device. Once this has been done, I can then put the .where section back in and it works fine. However, it doesn't work straight away. This would suggest the .where section only works once the cache already has data. Also, if I add a document using firestore console, it doesn't update the app with the new data. But for some reason it will show all of the updated documents if i remove the .where part.
I'm really confused. Any ideas?
Thanks Jason
UPDATE: I've now figured out how to solve this problem. Please see my answer below for how I solved it.

I finally figured out the answer to my problem.
I added queryUserData(); to the initState(). Here's how it looks in the code:
class HomeScreen extends StatefulWidget {
static const String id = 'home_screen';
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _firestore = Firestore.instance;
final _auth = FirebaseAuth.instance;
FirebaseUser loggedInUser;
#override
void initState() {
super.initState();
getCurrentUser();
queryUserData();
}
void getCurrentUser() async {
try {
final user = await _auth.currentUser();
if (user != null) {
loggedInUser = user;
print('this is a test${loggedInUser.email}');
}
} catch (e) {
print(e);
}
}
void queryUserData() async {
final user = await _auth.currentUser();
loggedInUser = user;
final query = await _firestore.collection('meals').orderBy('date', descending: true).where('email', isEqualTo: '${loggedInUser.email}').getDocuments(source: Source.cache);
var totalEquals = query.documents.length;
print('$totalEquals records found for this user');
if (totalEquals >= 1) {
print(query);
print('cache has data. Therefore data will now only be read from cache');
} else {
print('data will be read from firestore until you have at least 1 meal');
getFirestoreInitialData();
}
}
void getFirestoreInitialData() async {
final query = await _firestore.collection('meals').getDocuments();
print(query);
print('data still being read from firestore');
}

Related

How to handle _Future<dynamic> value in flutter?

I am trying to get value from firebase in flutter. during that time, I am receiving _Flutter value returning from the Future<> type returning function. please help someone
I am having a code for fetching values from firebase.. the function gets a value from firebase by querying with an attribute
class FirebaseMethods {
Future<List> findEvents(dynamic attribute, dynamic value) async {
CollectionReference eventCollection =
FirebaseFirestore.instance.collection('events');
return eventCollection
.where(attribute, isEqualTo: value)
.get()
.then((QuerySnapshot querySnapshot) {
List events = [];
querySnapshot.docs.forEach((doc) {
events.add(doc.data());
});
return events;
}).catchError((error) {
print("Failed to retrieve events: $error");
});
}
Future<List> findUsers(dynamic attribute, dynamic value) async {
CollectionReference userCollection =
FirebaseFirestore.instance.collection('profile');
return userCollection
.where(attribute, isEqualTo: value)
.get()
.then((QuerySnapshot querySnapshot) {
List users = [];
querySnapshot.docs.forEach((doc) {
users.add(doc.data());
});
return users;
}).catchError((error) {
print("Failed to retrieve users: $error");
});
}
}
And I am calling the above function 'findUsers' in the following way:
dynamic database_functions = FirebaseMethods();
class RenderProfileView extends StatefulWidget {
String email;
RenderProfileView(this.email, {super.key});
#override
State<RenderProfileView> createState() => _RenderProfileViewState();
}
class _RenderProfileViewState extends State<RenderProfileView> {
TextEditingController name_controller = TextEditingController();
TextEditingController phone_number_controller = TextEditingController();
late dynamic user_json = database_functions.findUser('email', widget.email); // without late I am getting error and getting values with attribute 'email' = widget.email
dynamic get_name() {
print(user_json);
return 'some_value';
}
}
When the 'findUser' function is called, the printing message is -> Instance of '_Future'
Someone please help.. if any other way to solve the issue please mention it.
Future describes async operations in flutter. you must await all Futures results. Either by using the await keyword or .then property.
You could try adding initState to your stateful widget or go with a FutureBuilder depending on your use case.
Below is an edited version of your code.
dynamic database_functions = FirebaseMethods();
class RenderProfileView extends StatefulWidget {
String email;
RenderProfileView(this.email, {super.key});
#override
State<RenderProfileView> createState() => _RenderProfileViewState();
}
class _RenderProfileViewState extends State<RenderProfileView> {
TextEditingController name_controller = TextEditingController();
TextEditingController phone_number_controller = TextEditingController();
late dynamic user_json;
#override
void initState() {
super.initState();
database_functions.findUser('email', widget.email).then((data) {
user_json = data
});
}
String get name => 'some_value';
}

displaying only the current user data

I protected data_service with current user to only display the current user's habits.
data_service.dart:
class DataService {...
late final Database db;
Users? _user;
late final StreamData<Map<int, Habit>> habits;
Future<void> init() async {
db = await HabitsDb.connectToDb();
habits = StreamData(initialValue: await _getAllHabits(), broadcast: true);
}
String get userEmail => AuthService.firebase().currentUser!.email;
Future<Map<int, Habit>> _getAllHabits() async {
getOrCreateUser(email: userEmail); //issue
final habits = await _getAllHabitsFromDb();
final map = Map<int, Habit>();
final currentUser = _user;
print(currentUser);
for (final habit in habits) {
if (currentUser != null) {
print(currentUser.id);
print(habit.userId);
if (habit.userId == currentUser.id) {
map[habit.id] = habit;
}
}
//map[habit.userId] = currentUser?.id;
}
return map;
}
Future<List<Habit>> _getAllHabitsFromDb() async {
final habitsMap = await HabitsDb.getAllHabits(db);
final habitsList = habitsMap.map((e) => Habit.fromDb(e)).toList();
return habitsList;
}
Future<Users> getOrCreateUser({
required String email,
bool setAsCurrentUser = true,
}) async {
try {
//we found the user
final user = await getUser(email: email);
if (setAsCurrentUser) {
_user = user;
}
print(_user?.email);
return user;
} on CouldNotFindUser {
//we didn't find the user
final createdUser = await createUser(email: email);
if (setAsCurrentUser) {
_user = createdUser;
}
return createdUser;
} catch (e) {
rethrow;
}
}
...}
in main class:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final dataService = DataService();
await dataService.init();
GetIt.I.registerSingleton(dataService);
... }
StreamData class:
class StreamData<T> {
List<Habit> _notes = [];
User? _user;
late final StreamController<T> _controller;
Stream<T> get stream => _controller.stream;
late T _value;
T get value => _value;
StreamData({required T initialValue, bool broadcast = true}) {
if (broadcast) {
_controller = StreamController<T>.broadcast();
} else {
_controller = StreamController<T>();
}
_value = initialValue;
}
the problem is that the line getOrCreateUser(email: userEmail); is only called once and it does not work when I switch user and I need to Hot Restart to fix it. I think using Futurebuilder will fix it. but if yes, how do I use it when there is a need to call dataService.init at the beginning of the main?
Since your getOrCreateUser function is declared as async, you'll want to use await when you call it in _getAllHabits:
await getOrCreateUser(email: userEmail)
This ensures the getOrCreateUser code has completed before the rest of the code in _getAllHabits (that depends on the result of getOrCreateUser) executes.

Cannot get a field on DocumentSnapshotPlatform which does not exist

I am trying to display the name to the appbar but userid part seems like giving an error. iam a beginner to flutter and firestore can someone help me
class Profile extends StatefulWidget {
#override
_ProfileState createState() => _ProfileState();
}
class _ProfileState extends State<Profile> {
//To retrieve the Userid
User? user;
Future<void> getUserData() async {
User userData = await FirebaseAuth.instance.currentUser!;
setState(() {
user = userData;
print(userData.uid);
});
}
///////////////////////////////////////
Future<String>? _title;
#override
void initState() {
getUserData().then((value) => _title = _getAppBarNameWidget());
// _title = _getAppBarNameWidget();
super.initState();
}
//To retrieve the name from firestore
Future<String> _getAppBarNameWidget() async =>
await FirebaseFirestore.instance
.collection('customer')
.doc(user!.uid)
.get()
.then((DocumentSnapshot ds) async {
var name = ds['name'];
return name;
});
DocumentSnapshot ds does not directly contain document data, only document id, you have to use data() function to get data. It is also a good idea to check whether the document really exists, because you will get a snapshot event if the document is not found.
Examples:
ds.exists // will return true if document is found
ds.id // will return document reference
ds.data()!['name'] // will return 'name' field of document

Keep the user logged in flutter (The app has 2 different login and main, one for Client and one for Driver)

I am doing an app in flutter and I am working on the authentication part. I want to know how I can keep my user logged in after I reload the app. Now the thing is that my app has 2 kinds of users (Client and Driver). So each has its own space, like sign in and sign up and main (after logging in).
This is the code that I used for logging.
class Initializer extends StatefulWidget {
// Access to this Screen
static String id = 'initializer';
#override
_InitializerState createState() => _InitializerState();
}
class _InitializerState extends State<Initializer> {
// Firebase Stuff
final _auth = FirebaseAuth.instance;
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
User _user;
// To Check if There's a Driver
bool isDriver = true;
void getCurrentUser() async {
try {
final getCurrentUser = _auth.currentUser;
if (getCurrentUser != null) {
getUserKind();
_user = getCurrentUser;
}
} catch (e) {
print(e);
}
}
getUserKind() async {
try {
// To fetch Database for Driver
final QuerySnapshot checkOfDriver =
await _firestore.collection('driver').where('uid', isEqualTo: _user.uid).get().catchError((error) {
print(error);
});
if (checkOfDriver.docs.isEmpty)
setState(() {
isDriver = false;
});
else
setState(() {
isDriver = true;
});
} catch (e) {
print(e);
return null;
}
}
#override
void setState(fn) {
if (mounted) {
super.setState(fn);
}
}
#override
void initState() {
super.initState();
getCurrentUser();
}
#override
Widget build(BuildContext context) {
getCurrentUser();
SizeConfig().init(context);
return _user == null
? WelcomeScreen()
: isDriver
? DriverMain()
: ClientMain();
}
}
It's actually working but not properly, because when I reload the app while I'm logging in as a Client, the app shows me DriverMain at the beginning for one second then it switches to the right side which is ClientMain and that causes me some errors sometimes, and it's not an efficient work anyway.
So, what I should add to the code or ...
Firebase already persists the users credentials, and restores them automatically when the app restarts.
But this is an asynchronous process, as it requires a call to the server. By the time your getCurrentUser = _auth.currentUser code runs, that asynchronous process hasn't finished yet, so you get null.
To properly respond to the auth state being restored (and other changes), you'll want to use an auth state change listener as shown in the documentation on authentication state:
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});
If you want to use this in your UI, you'll typically wrap it in a StreamBuilder instead of calling listen yourself.

Flutter Firestore getDocuments() not working on initState

I'm having an issue trying to call a document from Firestore in initState. I can pull data from a StreamBuilder just fine, but I just want to call the data once on initState.
Here is what I'm doing:
class _PainLevelState extends State<PainLevel> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final CollectionReference userCollection =
Firestore.instance.collection('users');
static final now = DateTime.now();
String _uid;
double myPainLevel = 0;
Future<void> getCurrentUser() async {
final FirebaseUser user = await _auth.currentUser();
if (mounted) {
setState(() {
_uid = user.uid;
});
}
}
Future<void> getCurrentPainLevel() async {
await userCollection
.document(_uid)
.collection('pain')
.where('time',
isGreaterThanOrEqualTo: DateTime(now.year, now.month, now.day))
.getDocuments()
.then((QuerySnapshot docs) {
if (docs.documents.isNotEmpty) {
print('yes');
} else {
print('no');
}
});
}
#override
void initState() {
super.initState();
getCurrentUser();
getCurrentPainLevel();
}
...
I just get a "no" every time I print to console. It's not get any documents when there is one. If I take the same code inside the future and put it somewhere else, like in the build method, it works, but it constantly builds and I don't want that. Any suggestion as to why it is not working? Thanks in advance.
I'm guessing here that your code will not always work, because getCurrentPainLevel might get called before getCurrentUser is completed, which will cause _uid to be null and therefore, not work as expected. Try to put then keyword after getCurrentUser method, like this:
#override
void initState() {
super.initState();
getCurrentUser().then((_) {
getCurrentPainLevel();
});
}
By the way, you should NOT be calling async methods in initState. Try to put the code somewhere else, like WidgetsBinding.instance.addPostFrameCallback(...).