Flutter [firebase_auth] onAuthStateChanged stoped listening - firebase

onAuthStateChanged no longer listens to changes, so basically, when I create a new account or sign in to an existing one, it's stuck in the loading spinner but the user did create it in the firebase users.
I'm using StreamBuilder:
home:StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (ctx, userSnapshot) {
if (userSnapshot.hasData) {
return SellerScreen(docsRef);
}
return AuthScreen();
},
),
what I get in the console:
D/FirebaseAuth( 9477): Notifying id token listeners about user ( VQghDgOucFdCcWLdPlCJNTd5eO72 ).
D/FirebaseAuth( 9477): Notifying auth state listeners about user ( VQghDgOucFdCcWLdPlCJNTd5eO72 ).

The code in the console stipulates that the user has either been created or signed in.
Your problem might be with where your spinner widget is located in the code...
Can i have a snippet on that to see if I can help?
...in any case, what i usually do is:
bool loading = false;
#override
Widget build(BuildContext context) {
return loading ? Loading() : HomeScreen();
}
With this, I've told flutter to show the loading screen while the user is being created or signed in.
The bool type loading is just to toggle between the true and false state of the loading widget

Related

Flutter Firebase Authentication - delay on app startup/refresh

I am running an app that has a 'landing page', Basically, when the user opens/restarts/refreshes the app, the landing screen decides whether to direct the user to the sign-in screen (if the user is not logged in) or the home screen (if they are already logged in).
The code I am using for this functionality (involving a StreamBuilder) is this:
class LandingPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
final auth = Provider.of<AuthBase>(context, listen: false);
return StreamBuilder<User?>(
stream: auth.authStateChanges(),
builder: (context, snapshot) {
/// If the connection state is waiting, show splash screen or loading icon
if (snapshot.connectionState == ConnectionState.waiting) {
print('Connection waiting');
return Scaffold(
body: Center(
child: PlatformCircularProgressIndicator(),
),
);
}
/// Once the connection is established, then show the disclaimer acceptance or sign-in screen (depending if there is a logged in user or not)
if (snapshot.connectionState == ConnectionState.active) {
User? user = snapshot.data;
return FutureBuilder<IdTokenResult>(
future: user?.getIdTokenResult(true),
builder: (
BuildContext context,
AsyncSnapshot<IdTokenResult> snapshotData,
) {
if (user == null ||
user.emailVerified == false ||
snapshotData.data?.claims!['status'] != 'active') {
return SignInHomePage.create(context);
} else {
return Provider<User?>.value(
value: user,
child: DisclaimerAcceptanceHomeScreen(
userName: user.displayName,
),
);
}
},
);
} else {
return Scaffold(
body: Center(
child: PlatformCircularProgressIndicator(),
),
);
}
});
}
}
The reason I am using a FutureBuilder is that my app is subscription based and I am using cloud functions and custom claims to check to see if a user has an active subscription before allowing them to log in.
My issue is that when a user refreshes/reloads the app, if they are logged in, there is a brief flash where the sign-in screen is shown (can't be more than a second) before the other screen is shown. That comes across as quite poor from a user impression point of view, so I am trying to fix that. Basically, if a user is logged in, I do not want them to be shown even a flash of the sign in screen. I would like it to go straight from the splash screen to the disclaimer home screen that you see.
I came across this answer in which someone appeared to have the same issue as me. They explained that I need to do something when the connection state is waiting, which is why you see the code in there.
Unfortunately, that isn't working for me. For me, the flash of the sign in screen happens when the connection state is active, not when it is waiting. I am not too sure how to correct this.
Does anyone have thoughts about how I could go about fixing this, so that there is not a flash of the sign-in screen at all?
You might use firestore exists on snapshots. I think Signin page shows because when user found with emailVerified false and after that claims value comes, which redirects to landing page. So I'd do something like this:
// make sure snapshot completed
if (snapshotData.exists) {
// if user null or user found but status not verified
if (user == null) {
return SignInHomePage.create(context);
} else {
if (snapshotData.data?.claims!['status'] != 'active') {
return SignInHomePage.create(context);
} else {
return Provider<User?>.value(
value: user,
child: DisclaimerAcceptanceHomeScreen(
userName: user.displayName,
),
);
}
}
} else {
// show loading indicator or something
}

How can I load the previous signed in user?

In my application, I happen to have two types of users, hospital, and patients, both are configured to use the same authentication by firebase. What I would like to know is, how do I load the page for the respective user depending on how they were signed in previously? For example, if a hospital had signed in and not logged out, when they run the app, it should display the hospitals' dashboard and not the patients'. How do I configure this?
shared_prefs supports Flutter web. Save a value stating the account type.
See this answer:
https://stackoverflow.com/a/59579821/13714686
SharedPreferences prefs = await SharedPreferences.getInstance();
bool isHospital = (prefs.getBool('isHospitalAccount');
if(isHospital == true){
//hospital navigation
}
else{
//patient navigation
}
First, you need to check whether the user is logged in or not. For this, you can check through the below code.
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context){
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot){
if (snapshot.hasData){
FirebaseUser user = snapshot.data; // this is your user
/// is because there is a user already logged, So route them to a Screen of Dashboard directly.
return MainScreen();
}
/// other way there is no user logged.
return LoginScreen();
}
);
}
}
Now, you need to maintain a list of users in FireStore where the user's metadata like the last login, User Type information will be stored. Using this information you can Route the user accordingly.

W/System (10375): Ignoring header X-Firebase-Locale because its value was null

In flutter when I use firebase to signup or login a user it won't let the user switch to the other screen as you see in the code below it should allow the user to go to that page but it doesn't. It just keep loading. And this is the error I keep getting:
W/System (10375): Ignoring header X-Firebase-Locale because its value was null.
home: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (ctx, userSnapshot) {
if (userSnapshot.hasData) {
return NavigationScreen(
currentIndex: 1,
);
}
return AuthScreen();
}),
And I know an account is created because when I restart the app it goes to the other screen and it also tells me this when it keeps loading:
D/FirebaseAuth( 4303): Notifying id token listeners about user ( 2ab0Z7FcYlMr96lxbvBAPOfeLvk1 ).
D/FirebaseAuth( 4303): Notifying auth state listeners about user ( 2ab0Z7FcYlMr96lxbvBAPOfeLvk1 )
old question but incase someone else gets stuck:
Instead of :
return NavigationScreen(currentIndex: 1,);
write out the code to push to a different page.
Like this:
Navigator.push(context, MaterialPageRoute(builder: (context)=> NavigationScreen(currentIndex: 1,)));

How to have flutter show different screens based on whether or not firestore documents exist?

I am pretty new to flutter and are designing an app that requires user to login. Once users have registered an account they will be required to fill out some user information before being taken to the home screen. If the user registers an account however doesnt fill out their info the next time they log in they should be returned to this screen. Currently, there is a stream that watches the value of the user. If this value is null then it shows the login page, otherwise it will show the home page.
I am having trouble implementing the user information screen. Would setting up a stream that watches a firestore document and, if the document exists return the home page, otherwise show the user infromation screen be the best method and if so how would I go about doing so?
Here is my code that checks whether or not the user is signed in:
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(
home: SignedIn(),
)
);
}
And the code for the SignedIn() widget:
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user == null) {
return Authenticate();
} else {
return Home();
}
}
I would probably try something like this.
StreamSubscription _subscription;
_subscription = yourStream.listen((data) {
// navigate to whatever screen depending on the data
if(data.signedIn){
Navigator.pushNamed(context, "whatever_route");
}
});

Listening to changes in firebase auth credentials

I was building an app which requires me to sign in the user with two compulsory methods, the email as well as the phone number as both the type of data is crucial for the proper functioning of the app. I have implemented Firebase email-password based authentication and google sign in into my app along with phone verification.
Now, I have successfully implemented both the methods and managed to link the credentials of phone auth with email and the same is visible on the firebase console but the problem is that I'm not notified of any changes in auth credentials.
Here's the code I've used to determine which page to show depending on the state of snapshot.data
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting ||
snapshot.connectionState == ConnectionState.none) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
} else {
if (snapshot.hasError) {
return Scaffold(
body: Center(
child: Text("Error signing in. Please try later"),
),
);
} else if (snapshot.hasData) {
print("signInHandler.dart phone: ${snapshot.data.phoneNumber}");
if (snapshot.data.phoneNumber == null) {
return PhoneSignIn();
}
return MyHomePage();
}
return EmailSignIn();
}
},
);
Now I know as per documentaions, onAuthStateChanged is fired only if there was any sign in or sign out event but is there any way to know that the AuthCredentials have changed so that I can decide the appropriate page to show depending on the data?
However, when I hot reload the app after linking auth credentials, the appropriate screen is displayed i.e MyHomePage(). Before that, the app keeps displaying only the PhoneSignIn() screen ever after successfully linking the credentials.
Getting phone number using google's contacts API did not solve my problem as there are some google accounts which are not associated with any phone number.
I'd be very grateful if someone could point me in the right direction.
singh, you'll need to force refresh the user idToken, getIdToken(true), so as propagate the new values for credentials, claims or providers on the auth user instance. You can read here for more.
In the end, I could not find any out of the box solution for the problem and had to use a bunch of streams and Bloc for managing this state.

Resources