Reload FirebaseAuth in Flutter - firebase

i have created in Flutter the following Code:
if (_auth.currentUser != null) {
&& FirebaseAuth.instance.currentUser.reload() != null
Timer(
Duration(seconds: 3),
() => Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) =>
HomeScreen(username: _auth.currentUser.displayName)),
(Route<dynamic> route) => false),
);
} else {
Timer(Duration(seconds: 4),
() => Navigator.pushReplacementNamed(context, "/auth"));
}
}
The Problem is actually that the Part && FirebaseAuth.instance.currentUser.reload() != null is not working.
Do you now why? I want to reaload the currentUser-Firebase-AuthState every time the App is openend.
Thanks for helping!!!

The reload() method is asynchronous and returns a Future<void>. You should use the await keyword to wait for a future to complete, see here.
It is not 100% clear to me why you need to use the reload() method. Since you use the displayName property in your code, is it because , in your app, this property is frequently changing? I would kindly suggest that you read in this SO answer the explanations on why one should use this method: "Calling reload() reloads that user's profile data from the server".
So, if it appears that you really need to use this method, you could do something along the following lines:
void navigate() async {
if (_auth.currentUser != null) {
await _auth.currentUser.reload();
// continue your business logic here
// For example
if (_auth.currentUser.isEmailVerified) {
// Navigate to ...
}
} else {...}
}
Note that, as explained in the FlutterFire doc, you could alternatively use the userChanges() method: "This stream provides realtime updates to the User class without having to call reload(), such as when credentials are linked, unlinked and when the user's profile is updated".

Related

StreamProvider listening to User doesn't update when User changes

In my app, I listen to changes from a User Document in Cloud Firestore.
I do this by getting the current user ID, and then getting the document associated with that ID.
class UserService {
...
//GET A USER'S INFORMATION AS A STREAM
// ? IF NO UID IS PASSED, IT GETS THE INFO OF THE CURRENT USER
Stream<User> getUserInfoAsStream({String uid}) async* {
if (uid == null) {
uid = await AuthService().getUID();
}
yield* Firestore.instance
.collection('users')
.document(uid)
.snapshots()
.map((doc) => User.fromFirestore(doc));
}
...
I then use a StreamProvider to listen to the stream in my main.dart
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<User>.value(
value: UserService().getUserInfoAsStream(),
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: SplashScreen(),
),
);
}
}
During the course of the app's lifecycle, it works perfectly, but when the user signs out using FirebaseAuth.instance.signOut(); and then logs in with a different user, the stream remains constant (i.e it listens to the old uid stream), and the StreamProvider doesn't listen to the new stream of data.
| Sign Out Code For Reference |
// ? SIGN OUT CODE: If user signed out, it returns true, else, false
Future<bool> signOut() async {
try {
await _firebaseAuth.signOut();
return true;
} catch (error) {
print(error);
return false;
}
}
| Where it is used |
FlatButton(
onPressed: () {
AuthService().signOut().then((value) =>
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (BuildContext context) {
return Onboarding();
}), (route) => false));
},
child: Text("Yes")),
To solve the problem, I would've passed the current uid to the StreamProvider instead, but I can only get the current uid asynchronously.
How can I listen to an asynchronous stream using the StreamProvider, and update it when the user changes?
EDIT: I managed to fix the problem to some extent by moving the provider up the widget tree to the screen immediately after the login page. But because providers are scoped, I had to create a completely new MaterialApp after my original MaterialApp which is messing up my some components in my app.
Is there any better workaround?
I managed to fix the problem by switching from the provider package to get_it.
get_it allows you to register and unregister singletons, meaning that when a user logs in, I can register the singleton so it can be used across all screens that depend on it. Then, when I logout, I simply unregister it. That way, the User is always updated after signing in and out.
Here's how to do it yourself.
Install the package get_it in your pubspec.yaml.
get_it: ^4.0.2
Create a new file next to your main.dart called locator.dart. Inside it, add this code:
GetIt locator = GetIt.instance;
void setupLocator() {
// Replace this with the object you're trying to listen to.
User user;
Stream<User> userStream = UserService().getUserInfoAsStream();
userStream.listen((event) => user = event);
locator.registerLazySingleton(() => user); // Register your object
}
When you login, just call setupLocator(); and when you log out, use this code:
locator.unregister<User>();
That's all I did to get it up and running!
Edit: I managed to make it even better and lighter by using a UserProvider Singleton that listens to changes in Authentication and then gets the current user when a user logs in.
import 'package:planster/models/core/user.dart';
import 'package:planster/models/services/auth_service.dart';
import 'package:planster/models/services/user_service.dart';
class UserProvider {
// SINGLETON INITIALIZATION
static final UserProvider _singleton = UserProvider._internal();
factory UserProvider.instance() {
return _singleton;
}
UserProvider._internal() {
listenToUserAuthState();
}
// VARIABLES
User user;
void listenToUserAuthState() async {
AuthService().onAuthStateChanged.listen((event) {
uid = event.uid;
if (uid != null)
UserService().getUserInfoAsStream(uid: uid).listen((userEvent) {
user = userEvent;
});
});
}
}

Flutter | Firebase User Authentication & Profile creation

I am new to Flutter/Firebase and I want to program an app where a user can login/register and then he needs to create a profile with his information like his name, age... and only if he has created his profile, he should be able to continue and see the "main part" of the app.
I already implemented the Firebase Auth with a working Login / Register Page, but my question is now, how to create the Profile thing the most efficent.
At the moment I created this method here at my own:
Future checkUserProfile() async{
// get snapshot from document
final snapShot = await Firestore.instance.collection('profiles').document(uid).get();
if(snapShot == null || !snapShot.exists){
User.gotProfile = false;
} else {
User.gotProfile = true;
}
This method is checking if an user-profile with the Firebase Auth User UID already exists and if not, the user will be send to the "ProfilePage" with a FutureBuilder executing the method above and if it already exists, he will see the main part of the app.
As I already said, I tried it by myself and I wanted to ask if this is already an good implementation or is there even an easier & better way to do it?
Yes this is an good implementation. In my app I have the check User method like yours. The following method is an example. When the user is not registered he forwarded to the RegisterPage else he forwarded to the MainPage.
checkUserAlreadyExists(FirebaseUser user) async {
final userData = await Firestore.instance.collection('users').document(user.uid).get();
if (userData == null || !userData.exists) {
setState(() {
Navigator.pushAndRemoveUntil(context,
MaterialPageRoute(builder: (BuildContext context) => RegisterPage()), ModalRoute.withName('/'));
});
} else {
setState(() {
Navigator.pushAndRemoveUntil(context,
MaterialPageRoute(builder: (BuildContext context) => MainPage()), ModalRoute.withName('/'));
});
}
}

Persistent log in using Firebase currentUser not working - Flutter app

My goal for this app is to have persistent log in, so that the user of my Flutter app needs to sign in only once (unless they sign out) and whenever my Flutter app restarts, they do not need to log back in. I saw many examples with using Firebase Authentication and the .currentUser() method, but I am still having to log in every time I restart the app. I have tested this on the simulator (ios) and on my physical iphone while running debug mode on xCode (usb connection). Is it possible that I cannot test this functionality this way? Because I do see a message pop up on both android studio and xCode terminals that mention lost connection to device or stopped running because of the restarting. If that's the case, how can I test that persistent log in is working?
If that isn't the case, what am I doing wrong?? I've included the code below. This is happening within a stateful widget, of course.
final _auth = FirebaseAuth.instance;
checkIfCurrentUserExists() async {
try {
await _auth.currentUser().then((user) {
print('this is the user $user');
if (user != null && user.email != null) {
userIsLoggedIn = true;
// this works fine!
}
});
} catch (e) {
print('current user was not found $e');
// this works fine!
}
}
// called inside initState()
setClientOnLoad() async {
await Spryte.checkIfCurrentUserExists();
var doesCurrentUserExist = userIsLoggedIn;
var currentUser = await returnCurrentUser();
if (doesCurrentUserExist == false) {
//if user is not authenticated, set anonymous user
// this works fine!
}
else {
//print('current user does exist');
await foo(currentUser.uid);
// 'foo' is meant to retrieve some data about the client on loading of the app
// so that the user doesn't have to log in every time the app restarts
// but for some reason it's not working for me
}
}
I have got the same problem. I was able to sign in but when restarting the app, I was getting another random UID.
It might sound stupid, but make sure you are not calling signInAnonymously(); at any point in your app without checking if there is already a current user.
This was my problem, I was always signing in anonymously at every app restart.
Here is what I did from my starting app page (which takes care of setting up the app including the user):
Future<FirebaseUser> signInAnonymously() async {
AuthResult result = await _auth.signInAnonymously();
final FirebaseUser user = result.user;
assert(user != null);
assert(await user.getIdToken() != null);
return user;
}
And here is the checking method:
Future<FirebaseUser> tryToFetchUser() async {
var user = await _auth.currentUser();
if (user == null) {
user = await FirebaseAuth.instance.onAuthStateChanged.first;
}
if (user == null) {
user = await signInAnonymously();
}
return Future.value(user);
}
I hope it will help some of you and avoid wasting time on stupid mistakes as I did!
You r calling setClientOnLoad() which is async inside init() method & other app navigation depends on this method so u need wrap all your async stuff in FutureBuilder().
if not then build() method ll be called before complete execution of setClientOnLoad()
e.g. :-
FutureBuilder(
future: setClientOnLoad(),
builder: (context, AsyncSnapshot<R> snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
// after executing method completely
},
);

Flutter, Is it possible to use Firestore Stream Builder in function without a widget build method?

I have a function that gets the results of a google places search for stores and searches Firestore to see if the store is already in the database.
Whenever I run it thought the Stream Builder is doing nothing.
I think the problem is that the function is not within a widget and does not have a build method. Here is the code:
void searchStores() async {
Prediction newStore = await PlacesAutocomplete.show(
context: context,
apiKey: kGoogleApiKey,
mode: Mode.overlay,
language: "en",
components: [new Component(Component.country, "au")]);
await places.getDetailsByPlaceId(newStore.placeId).then((detailStoreInfo) {
print('running 1');
StreamBuilder (
stream: Firestore.instance.collection('stores').document(detailStoreInfo.result.id).snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot){
print('running 2');
setState(() {
if (snapshot.connectionState == ConnectionState.active &&
snapshot.hasData){
print('running 3');
if (snapshot.data['veganOnly'] == null || snapshot.data['veganOnly'] == false){
print('running 4');
setState(() {
firstStore = true;
});
}
}
});
return null;
}
);
});
}
As you can see I added print statements to work out where my code is failing. 'running 1' is show so the places is returning a response.
But none of the other statements (2,3 or 4) are printing so it seems the stream builder is not working, not even coming back with null values.
I also tried putting a return before the stream builder but that had no effect either.
Am I correct in thinking this because it is not in the build method of a widget or is it something else entirely.
Thanks
A StreamBuilder is a Widget and as such, has to be inserted somewhere in your widget tree, just as you would for a Text widget. Just pass it a stream and return another widget inside it’s builder callback
You need to use the Stream class for it, for instance to get all details about a collection named "users", you can use,
Stream<QuerySnapshot> stream = _db.collection("users").snapshots();
stream.forEach((QuerySnapshot element) {
if(element == null)
return;
for(int count=0;count<element.documents.length;count++) {
print(element.documents[count].data.toString());
}
});

FirebaseAuth current user is not returning null after calling sign out?

I am trying to logout user and switch the widgets but after calling the following function -
void logoutUser() async {
await FirebaseAuth.instance.signOut();
}
If I check for the current user, it's returning user object but with null id -
FirebaseAuth.instance.currentUser()
I try to kick the user out to main.dart after logout which checks if user is signed in or not and loads a correct widget. Does anyone have any idea why currentUser() isn't returning null after calling signOut()?
_auth.currentUser() is probably returning an anonymous FirebaseUser object. Check the isAnonymous property.
Example :
auth.currentUser().then((user) {
if (user == null || user.isAnonymous) {
// what will you do?
return;
}
setState(() {
_uid = user.uid;
});
});
Yet, I would highly recommend to monitor the onAuthStateChanged stream instead. This way you will be informed when the user logs in or logs out immediately.
Check this article, it covers it in depth.

Resources