I am just learning Flutter and am trying to use a StreamBuilder to display a Login / Register page if the user is logged out, or a Profile page if the user is logged in. My code is below:
Auth Service:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class AuthUser {
AuthUser({@required this.uid, @required this.email});
final String uid;
final String email;
}
abstract class AuthBase {
Future<AuthUser> currentUser();
Future<AuthUser> signIn({String email, String pw});
Future<AuthUser> registerUser({String email, String pw});
Stream<AuthUser> get onAuthStateChanged;
Future<void> signOut();
}
class Auth implements AuthBase {
final _firebaseAuth = FirebaseAuth.instance;
AuthUser _userFromFirebase(FirebaseUser user) {
if (user != null) {
return AuthUser(uid: user.uid, email: user.email);
} else {
return null;
}
}
@override
Stream<AuthUser> get onAuthStateChanged {
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
@override
Future<AuthUser> currentUser() async {
final user = await _firebaseAuth.currentUser();
return _userFromFirebase(user);
}
@override
Future<AuthUser> signIn({String email, String pw}) async {
final authResult = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
@override
Future<AuthUser> registerUser({String email, String pw}) async {
final authResult = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: pw);
return _userFromFirebase(authResult.user);
}
@override
Future<void> signOut() async {
await _firebaseAuth.signOut();
}
}
StreamBuilder:
class WelcomeScreen extends StatelessWidget {
WelcomeScreen({@required this.auth});
static const String id = '/';
final AuthBase auth;
@override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.hasData) {
AuthUser user = snapshot.data;
if (user == null) {
return displayLoginOrRegPage(context);
} else {
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
It was my understanding the stream would begin emitting 'null' once it was initialized, and would continue doing so until it fired off an Auth state change...
But the snapshot continually reports "No Data" and thus my code is stuck on the CircularProgressIndicator.
BTW, if I display the log-in screen in place of the progress indicator, the code works. So I'm clearly not understanding the whole stream initialization process.
Can somebody kindly explain to me where I have gone wrong here? Thanks a million in advance.
As you mentioned, when stream
initialises it emits null
, but when the user is not logged in, it still emits null, which stream
considers as no data i.e null
that's the reason for the error.
You can use Streambuilder
's connection state to differentiate between no user null and null after initialisation.
I hope following code helps you.
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data == null) {
return displayLoginOrRegPage(context);
} else {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}