Describe the bug
On web, reloading a page with a previously signed in user makes authStateChanges() sink two states one after the other.
The first one is null and the other one is the correct one, with the real authState of the previously signed in user.
In android authStateChanges()also sink two states but both are the real authState of the user.
This make weird behavior if UI is linked with authStateChanges values.
To Reproduce
Create Ć project with Firebase Auth enabled
Then run
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
title: 'Example App',
theme: ThemeData.dark(),
home: AuthExample(),
));
}
/// Provides a UI to authenticate
class AuthExample extends StatefulWidget {
@override
_AuthExampleState createState() => _AuthExampleState();
}
class _AuthExampleState extends State<AuthExample> {
String _log = "";
///TODO enter valid email and password
String email = "[email protected]";
String password = "userPassword";
@override
void initState() {
if (kIsWeb) FirebaseAuth.instance.setPersistence(Persistence.LOCAL);
int i = 0;
///Listen on authStateChanges for log
FirebaseAuth.instance.authStateChanges().listen((event) {
final DateTime time = DateTime.now();
print("timestamp : $time");
print("Event n°$i : $event");
_log = "timestamp: $time\nEvent n°$i : ${event?.uid}\n******\n$_log";
if (mounted) {
setState(() {});
}
i++;
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Firebase Example App"),
),
body: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
"Log :",
style: Theme.of(context).textTheme.headline6,
),
),
),
SizedBox(
height: 500, child: SingleChildScrollView(child: Text(_log))),
StreamBuilder<User>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(
child: CircularProgressIndicator(),
);
} else {
if (snapshot.hasError) {
return Center(
child: Text(snapshot.error.toString()),
);
} else {
return Container(
///Add color to better see the weird UI reload that it cause
color: (snapshot.hasData && snapshot.data.uid != null)
? Colors.transparent
: Colors.white,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (snapshot.hasData && snapshot.data.uid != null)
Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: FlatButton.icon(
icon: const Icon(Icons.exit_to_app),
color: Colors.red,
label: const Text('Sign Out'),
onPressed: () {
print("Signing out");
FirebaseAuth.instance.signOut();
},
),
)
else
Container(
padding: const EdgeInsets.all(16),
alignment: Alignment.center,
child: FlatButton.icon(
icon: const Icon(Icons.verified_user),
color: Colors.orange,
label: const Text('Sign In'),
onPressed: () {
print("Signing in");
FirebaseAuth.instance
.signInWithEmailAndPassword(
email: email, password: password)
.then((value) => print(value),
onError: (error) {
Scaffold.of(context).showSnackBar(SnackBar(
content:
Text("Error : ${error.toString()}")));
print("Error : $error");
});
},
),
),
],
),
);
}
}
},
),
],
),
);
}
}
Expected behavior
Same as android, sink the right authState at first time.
Better if it's think only one authState instead of two.
Additional context
Using firebase_auth: ^0.18.0+1
Result of a page reload on a previously logged in user :
Event 0 : null
Event 1 : the correct user id loaded

Flutter doctor
[ā] Flutter (Channel beta, 1.20.2, on Mac OS X 10.15.6 19G2021, locale fr-FR)
[ā] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[ā] Xcode - develop for iOS and macOS (Xcode 11.6)
[ā] Chrome - develop for the web
[ā] Android Studio (version 3.6)
[ā] VS Code (version 1.48.1)
[ā] Connected device (3 available)
⢠No issues found!
I have the same problem.
[ā] Flutter (Channel beta, 1.21.0-9.1.pre, on Mac OS X 10.15.4 19E287, locale pt-BR)
[ā] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[ā] Xcode - develop for iOS and macOS (Xcode 11.4)
[ā] Chrome - develop for the web
[ā] Android Studio (version 4.0)
[ā] IntelliJ IDEA Community Edition (version 2020.2)
[ā] VS Code (version 1.48.2)
[ā] Connected device (2 available)
⢠No issues found!
I've been creating a timeout on authStateChanges stream and catch the error to reflect the user is not logged in.
FirebaseAuth.instance.authStateChanges().timeout(Duration(seconds: 2))
In this case, I ignored the null values and user snapshot.hasError to detect no user logged.
The documentation of firebase web sdk contains something that can be related to this.
_Note: currentUser might also be null because the auth object has not finished initializing. If you use an observer to keep track of the user's sign-in status, you don't need to handle this case._
Yeah this is something we need to address once we focus on web, it's currently on handled on native only. Basically what @lucashilles is correct, what we need to be doing is setting up a initialization flow when initializeApp is called (which is what happens on native) which allows us to fetch any constant/pre-defined values before you start using the app. In this case it'd be something like:
Unfortunately this isn't so simple due to a number of issues (has to be done pre-registration, handling errors (e.g. trying to access firebase if it's not been included in the index.html file etc)).
Not tested, but a workaround could be doing this:
// Import/add the firebase-dart lib
import 'package:firebase/firebase.dart' as firebase;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Force auth initialization
await firebase
.auth(firebase.app(app.name))
.onAuthStateChanged
.first;
runApp(MaterialApp(
title: 'Example App',
theme: ThemeData.dark(),
home: AuthExample(),
));
}
That might work for now.
I have same problem :(
This issue was bugging me more than it should have. I'm not sure if it is how I am consuming the authStateChanges, but @Ehesp's fix didn't seem to work for me. What I ended up doing is trying to "warm up" the authStateChanges from index.html
firebase.initializeApp(firebaseConfig);
const unsub = firebase.auth().onAuthStateChanged(() => unsub());
I was able to solve it by using @sjones512 proposed solution. This is still an issue to be looked at.
Yes, all these streams are running two times on ios and android too at startup if the user is previously signed in:
Each runs twice, both times with the same correct value - at least on ios and android.
_I know that userChanges() stream is a superset of both [authStateChanges] and [idTokenChanges], i just mention that each runs twice._
For me this is not a big problem right now, I just have an unnecessary bloc state update. But with a minor workaround i can solve this 'problem', by simply checking if the value is the same. Hopefully this will be fixed soon.
This can be fixed by using dart's async distinct method.
For cases where both values returned are the same i.e neither is null
await firebase
.auth(firebase.app(app.name))
.authStateChanges()
.distinct((first, second) => first.uid == second.uid);
And if the first value returned is null
await firebase
.auth(firebase.app(app.name))
.authStateChanges()
.distinct((first, _) => first.uid == null);
I'm facing similar issue on iOS, then trying to signOut()
authStateChanges() is still returning not null value.
I noticed such behaviour only for some accounts. Cannot reproduce that on simulator.
The issue only on real device and only when i install via TestFlight.
I have the same problem (using Flutter to export for Web).
These are my imports in the index.html file:
<script src="https://www.gstatic.com/firebasejs/8.1.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.1.1/firebase-auth.js"></script>
When I change them to version 7.5.0 it works fine (all newer versions have the issue):
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.5.0/firebase-auth.js"></script>
Most helpful comment
This issue was bugging me more than it should have. I'm not sure if it is how I am consuming the authStateChanges, but @Ehesp's fix didn't seem to work for me. What I ended up doing is trying to "warm up" the authStateChanges from
index.html