Flutter GoRouter with Firebase Auth - flutter

This bounty has ended. Answers to this question are eligible for a +100 reputation bounty. Bounty grace period ends in 21 hours.
Chris wants to draw more attention to this question.
I am using the go_router and Firebase Auth for my web application but can not properly set it up. I tried this:
class AuthService {
static final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
static User? get currentUser => FirebaseAuth.instance.currentUser;
static bool isLoggedIn() {
return _firebaseAuth.currentUser != null;
}
...
With my router:
redirect: (context, state) {
final String destination = state.location;
final bool isOnStartView = destination == '/start';
final bool isOnEmailFlow = state.subloc.contains('/email');
if (!isOnStartView && !isOnEmailFlow && !AuthService.isLoggedIn()) {
return '/start';
}
return null;
},
However this is not working properly as isLoggedIn is always return false even if there a is a user logged in. I searched for this topic and found that I probably use some sort of Stream or Notifier with onAuthStateChange but I didn't find anything on how to implement a proper FirebaseAuthentication Flow in combination with GoRouter.
How would I do that?
Let me know if you need any more info.

You can use riverpod or provider to listen changes
here is code sample
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:testproject/Home%20Screen/home_screen.dart';
import 'package:testproject/Model/user_model.dart';
import 'package:testproject/Providers/UserProvider/user_data_provider.dart';
import 'package:testproject/Screens/login_screen.dart';
import 'package:testproject/Screens/sign_up.dart';
import 'package:testproject/Service/auth_service.dart';
import '../Providers/Satates/user_states.dart';
import '../Providers/UserProvider/user_state_provider.dart';
class MyRoutes extends ChangeNotifier {
final Ref ref;
MyRoutes(this.ref) {
ref.listen<LoginState>(
userStateprovider, (previous, next) => notifyListeners());
}
List<GoRoute> get _routes => [
GoRoute(
name: "Login",
path: "/login",
builder: (context, state) => SigninPage(),
),
GoRoute(
name: "Home",
path: "/home",
builder: (context, state) => const HomeScreen(),
),
GoRoute(
name: "Signup",
path: "/signup",
builder: (context, state) => const SignUpScreen(),
),
];
String? _redirectLogic(GoRouterState state) {
final loginState = ref.read(userStateprovider);
final user = ref.read(userProvider.state);
if (loginState is LoginStateInitial && auth.currentUser != null) {
log("message");
Future.delayed(const Duration(seconds: 0), () {
user.state = UserModel(
email: auth.currentUser!.email!, userid: auth.currentUser!.uid);
ref.read(userStateprovider.notifier).newstate = LoginStateSuccess(
UserModel(
email: auth.currentUser!.email!,
userid: auth.currentUser!.uid));
});
}
log(state.location);
log(auth.currentUser.toString());
final areWeLoggingIn = state.location == '/home';
log(areWeLoggingIn.toString());
if (areWeLoggingIn) {
return loginState is LoginStateSuccess ? null : "/login";
}
return null;
}
}
final routeProvider = Provider<GoRouter>((ref) {
final routeRef = MyRoutes(ref);
return GoRouter(
urlPathStrategy: UrlPathStrategy.path,
initialLocation: "/login",
refreshListenable: routeRef,
redirect: routeRef._redirectLogic,
routes: routeRef._routes);
});

You can do this
class AuthService {
static final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
static User? get currentUser => FirebaseAuth.instance.currentUser;
static bool? _isLoggedIn = null;
static bool isLoggedIn() {
if(_isLoggedIn == null) {
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
_isLoggedIn = user?.uid != null;
});
_isLoggedIn = _firebaseAuth.currentUser?.uid != null;
return _isLoggedIn ?? false;
}
else {
return _isLoggedIn ?? false;
}
...

Related

Flutter getting value from provider show null

I have a simple controller like this
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
user = user;
print(user.sId);
notifyListeners();
}
login(data) async {
var response = await ApiService().login(data);
final databody = json.decode(response);
if (databody['success']) {
UserData authUser = UserData.fromJson(databody['data']);
setUser(authUser);
notifyListeners();
return true;
} else {
return false;
}
}
}
I am trying to just print it like this on both widget and in initstate function but values are showing null. I can see in set function value is not null.
print('id ${context.watch<UserController>().user.sId.toString()}');
print(
'id2 ${Provider.of<UserController>(context, listen: false).user.sId.toString()}');
I already have added
ChangeNotifierProvider(create: (_) => UserController()),
],
in main.dart in MultiProvider
Also on Tap of login button I am doing this
showLoader(context);
UserController auth = Provider.of<UserController>(
context,
listen: false);
var data = {
"userEmail":
emailController.text.trim().toLowerCase(),
"userPassword": passwordController.text.trim(),
};
auth.login(data).then((v) {
if (v) {
hideLoader(context);
context.go('/homeroot');
} else {
hideLoader(context);
Fluttertoast.showToast(
backgroundColor: green,
textColor: Colors.white,
msg:
'Please enter correct email and password');
}
});
Try to include this while naming is same,
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Follow this structure
class UserController with ChangeNotifier {
UserData user = UserData();
UserData get userdata => user;
void setUser(UserData user) {
this.user = user;
print(user.sId);
notifyListeners();
}
Future<bool> login(String data) async {
await Future.delayed(Duration(seconds: 1));
UserData authUser = UserData(sId: data);
setUser(authUser);
notifyListeners();
return true;
}
}
class HPTest extends StatelessWidget {
const HPTest({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<UserController>(
builder: (context, value, child) {
return Text(value.user.sId);
},
),
floatingActionButton: FloatingActionButton(onPressed: () async {
final result = await Provider.of<UserController>(context, listen: false)
.login("new ID");
print("login $result");
;
}),
);
}
}

How to implement authentication in flutter using riverpod

I am trying to do a login using riverpod, the login must call an api but at the moment I am using a fake api I want that while the api is called I can show a loader to the user and if everything is correct navigate to home.
I have the following:
authentication_service.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:watch_movie_app/src/data/data_source/local/fake_data.dart';
import 'package:watch_movie_app/src/data/models/models.dart';
import 'package:watch_movie_app/src/domain/models/models.dart';
class AuthenticationService {
Future<Auth> login(User user) async {
await Future.delayed(const Duration(seconds: 2));
User isName = fakeUsers.firstWhere(
(element) => element.name == user.name,
orElse: () => emptyUser(),
);
User isPassword = fakeUsers.firstWhere(
(element) => element.password == user.password,
orElse: () => emptyUser(),
);
if (isName.name != '' && isPassword.password != '') {
return Auth(
message: 'Succes welcome user',
status: true,
aditionalData: getRandomString(15));
}
return Auth(message: 'Credenciales incorrectas', status: false);
}
}
auth_provider.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:watch_movie_app/src/data/enums/enum_login_status.dart';
import 'package:watch_movie_app/src/domain/constants/constants.dart';
import 'package:watch_movie_app/src/domain/providers/app_providers.dart';
final userAuthProvider = StateProvider<Map<String, dynamic>>((_) => {
'signedIn': false,
'loaded': false,
'status': LoginStatus.initialize,
});
final saveUserTokenProvider = StateProvider.family<bool, String>((ref, token) {
final localStore = ref.read(localStoreProvider);
localStore.write(tokenKey, token);
return true;
});
final userTokenProvider = StateProvider<String>((ref) {
final localStore = ref.read(localStoreProvider);
return localStore.read(tokenKey);
});
login_controller.dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:watch_movie_app/src/data/enums/enum_login_status.dart';
import 'package:watch_movie_app/src/data/models/models.dart';
import 'package:watch_movie_app/src/domain/models/models.dart';
import 'package:watch_movie_app/src/domain/providers/auth_provider.dart';
import 'package:watch_movie_app/src/domain/services/authentication_service.dart';
final authProvider = StateNotifierProvider((ref) => AuthNotifier(ref));
class AuthNotifier extends StateNotifier<Auth> {
final WidgetRef ref;
dynamic _authRepository;
AuthNotifier(this.ref) : super(Auth()) {
_authRepository = ref.read(authRepository);
}
Future<void> login(User user) async {
Map<String, dynamic> userAuthStatus = ref.read(userAuthProvider.state).state;
userAuthStatus = {...userAuthStatus, 'loaded': true};
final Auth loginResult = await _authRepository.login(user);
state = loginResult;
if (loginResult.status) {
userAuthStatus = {'loaded': false, 'signedIn': true, 'status': LoginStatus.success};
} else {
userAuthStatus = {...userAuthStatus, 'loaded': false, 'status': LoginStatus.failed};
}
}
void clearUser() {
state = Auth();
}
}
auth.dart
class Auth {
final String message, aditionalData;
bool status;
Auth({this.message = '', this.status = false, this.aditionalData = ''});
}
versions packages:
flutter_riverpod: ^2.0.0-dev.0
Flutter 2.10.2
Dart 2.15.1 DevTools 2.9.2

State managment in flutter with consumer and scaffoldState

I'm using the Provider dependencie to manage states on my screen. Currently I have created a Loading Screen that works with Lottie animation. In my Sign In page, whenever there is an error with the log in, a Snackbar is shown to the user. Althought now, when I use the splash screen, the screen doesn't return and the snackBar isn't shown.
This is a piece of the login screen:
Padding(
padding: const EdgeInsets.only(top: 20.0),
child: RaisedButton(
onPressed: userManager.loading
? null
: () {
if (formKey.currentState!
.validate()) {
userManager.signIn(
user: User(
email:
emailController.text,
password:
passController.text),
onFail: (e) {
scaffoldKey.currentState!
.showSnackBar(SnackBar(
content: Text(
'Falha ao entrar: $e'),
backgroundColor:
Colors.red,
));
},
onSucess: () {
debugPrint(
'Sucesso ao Logar');
Navigator.of(context).pop();
});
}
},
On the onFail I get this error, whenever I have a wrong password or other datas wrong:
Ocorreu uma exceção.
_CastError (Null check operator used on a null value)
This is how I'm changing between pages:
class LoginScreen extends StatelessWidget {
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
final TextEditingController emailController = TextEditingController();
final TextEditingController passController = TextEditingController();
#override
Widget build(BuildContext context) {
return Consumer<UserManager>(builder: (_, userManager, child) {
if (userManager.loading) {
return SplashScreen();
} else {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
UserManager:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:loja_virtual_nnananene/helpers/firebase_errors.dart';
import 'package:loja_virtual_nnananene/models/user.dart';
class UserManager extends ChangeNotifier {
UserManager() {
_loadCurrentUser();
}
final FirebaseAuth auth = FirebaseAuth.instance;
User? user;
bool _loading = false;
bool get loading => _loading;
bool get isLoggedIn => user != null;
Future<void> signIn(
{required User user,
required Function onFail,
required Function onSucess}) async {
loading = true;
try {
final AuthResult result = await auth.signInWithEmailAndPassword(
email: user.email!, password: user.password!);
await _loadCurrentUser(firebaseUser: result.user);
onSucess();
} on PlatformException catch (e) {
onFail(getErrorString(e.code));
}
loading = false;
notifyListeners();
}
Future<void> signUp(
{required User user,
required Function onFail,
required Function onSucess}) async {
loading = true;
try {
final AuthResult result = await auth.createUserWithEmailAndPassword(
email: user.email!, password: user.password!);
user.id = result.user.uid;
this.user = user;
await user.saveData();
onSucess();
} on PlatformException catch (e) {
onFail(getErrorString(e.code));
}
loading = false;
notifyListeners();
}
void signOut() {
auth.signOut();
user = null;
notifyListeners();
}
set loading(bool value) {
_loading = value;
notifyListeners();
}
Future<void> _loadCurrentUser({FirebaseUser? firebaseUser}) async {
final FirebaseUser currentUser = firebaseUser ?? await auth.currentUser();
if (currentUser != null) {
final DocumentSnapshot docUser = await Firestore.instance
.collection('users')
.document(currentUser.uid)
.get();
user = User.fromDocument(docUser);
final docAdmin = await Firestore.instance
.collection('admins')
.document(user!.id!)
.get();
if (docAdmin.exists) {
user!.admin = true;
}
notifyListeners();
}
}
bool get adminEnabled => user != null && user!.admin;
}
Is there another way to set the splash screen thats easier?
While I wait for you to add the UserManager class implementation, I think there's a missing notifyListeners() in the signIn method.

Flutter: ChangeNotifierProxyProvider: not providing updated correct state

I am trying to get state from one ChangeNotifier Auth.dart into another ChangeNotifier ProductsProvider.dart. But ChangeNotifierProxyProvider is providing incorrect state data for Auth.
main.dart
void main() => runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Auth>(create: (ctx) => Auth()),
ChangeNotifierProxyProvider<Auth, ProductsProvider>(
create: (context) => ProductsProvider(
Provider.of<Auth>(context, listen: false),
productList: [],
),
update: (ctx, auth, preProducts) {
print("ChangeNotifierProxyProvider Token ${auth.isAuth}");
print("ChangeNotifierProxyProvider Token ${auth.token}");
print("ChangeNotifierProxyProvider Test ${auth.test}");
return ProductsProvider(
auth,
productList:
preProducts == null ? [] : preProducts.getProductList,
);
},
),
ChangeNotifierProvider(create: (ctx) => Cart()),
ChangeNotifierProvider(create: (ctx) => Order()),
ChangeNotifierProvider(create: (ctx) => Auth()),
],
child: MyApp(),
),
);
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Auth>(
builder: (ctx, auth, _) {
print("Builder Token ${auth.isAuth}");
print("Builder Token ${auth.token}");
print("Builder Test ${auth.test}");
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.purple,
accentColor: Colors.orange,
fontFamily: "Lato",
),
routes: {
"/": (ctx) => auth.isAuth ? ProductOverviewScreen() : AuthScreen(),
ProductDetailScreen.PRODUCT_DETAIL_ROUTE: (ctx) =>
ProductDetailScreen(),
CartScreen.CART_SCREEN_ROUTE: (ctx) => CartScreen(),
OrderScreen.ORDER_SCREEN_ROUTE: (ctx) => OrderScreen(),
UserProductScreen.USER_PRODUCT_ROUTE: (ctx) => UserProductScreen(),
EditProductScreen.EDIT_PRODUCT_ROUTE: (ctx) => EditProductScreen(),
},
);
},
// ),
);
}
}
Auth.dart
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/HttpException.dart';
class Auth with ChangeNotifier {
String _token;
String _userId;
DateTime _expiryDate;
String test;
bool get isAuth => token != null;
String get token {
if (_token == null
// && _expiryDate != null &&
// _expiryDate.isAfter(DateTime.now())
) {
return null;
}
return _token;
}
Future<void> _authenticate(String email, String password, String url) async {
final uri = Uri.parse(url);
final response = await http.post(
uri,
body: jsonEncode({
"email": email,
"password": password,
"returnSecureToken": true,
}),
);
Map<String, dynamic> responseData = jsonDecode(response.body);
if (responseData["error"] != null) {
throw HttpException(responseData["error"]["message"]);
}
_token = responseData["idToken"];
_userId = responseData["localId"];
test = "_token";
// _expiryDate = DateTime.now()
// .add(Duration(seconds: int.parse(responseData["expiresIn"])));
notifyListeners();
}
Future<void> signUp(String email, String password) async {
const url =
"https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=[KEY]";
return await _authenticate(email, password, url);
}
Future<void> login(String email, String password) async {
const url =
"https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=[KEY]";
return await _authenticate(email, password, url);
}
}
ProductsProvider.dart
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shop_app/models/HttpException.dart';
import 'package:shop_app/models/Product.dart';
import 'package:shop_app/providers/Auth.dart';
class ProductsProvider with ChangeNotifier {
List<Product> _productList = [];
Auth _auth;
ProductsProvider(this._auth, {List<Product> productList = const []})
: _productList = productList;
Future<void> fetchAndSetProducts() async {
var uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products.json?auth=${_auth.token}");
try {
final response = await http.get(uri);
if (response.body != "null") {
final Map<String, dynamic> decodedJSON = jsonDecode(response.body);
final List<Product> loadedProductList = [];
decodedJSON.forEach((prodId, prodData) {
loadedProductList.add(Product(
id: prodId,
title: prodData["title"],
description: prodData["description"],
price: prodData["price"],
imageUrl: prodData["imageUrl"],
isFavourite: prodData["isFavourite"],
));
});
_productList = loadedProductList;
} else {
_productList = [];
}
notifyListeners();
} catch (error) {
throw error;
}
}
List<Product> get getProductList {
return _productList;
}
Product findById(String id) =>
_productList.firstWhere((element) => element.id == id);
Future<void> addProduct(Product product) async {
var uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products.json");
try {
final responseProduct =
await http.post(uri, body: jsonEncode(product.toJSon()));
final finalProduct = Product(
id: jsonDecode(responseProduct.body)["name"],
title: product.title,
description: product.description,
price: product.price,
imageUrl: product.imageUrl);
_productList.add(finalProduct);
notifyListeners();
return Future.value();
} catch (error) {
throw error;
}
}
Future<void> updateProduct(String id, Product product) async {
var productIndex = _productList.indexWhere((element) => element.id == id);
if (productIndex >= 0) {
var uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products/$id.json");
try {
await http.patch(uri,
body: jsonEncode({
"title": product.title,
"description": product.description,
"price": product.price,
"imageUrl": product.imageUrl,
"isFavourite": product.isFavourite,
}));
_productList[productIndex] = product;
notifyListeners();
} catch (error) {
throw error;
}
}
}
Future<void> deleteProduct(String id) async {
final uri = Uri.parse(
"https://flutter-shop-app-3035a-default-rtdb.europe-west1.firebasedatabase.app/products/$id.json");
final existingProductIndex =
_productList.indexWhere((element) => element.id == id);
var existingProduct = _productList[existingProductIndex];
_productList.removeAt(existingProductIndex);
final response = await http.delete(uri);
if (response.statusCode >= 400) {
_productList.insert(existingProductIndex, existingProduct);
notifyListeners();
throw HttpException("Could not delete product");
}
existingProduct = null;
notifyListeners();
}
}
After I click the login button the login method from the Auth.dart is trigged. After fetching the token the from firebase the screen is updated to ProductOverviewScreen. But the ProductsProvider.dart is not able to fetch the items because the ChangeNotifierProxyProvider update is returning an incorrect state for Auth.
Output:
Builder Token true
Builder Token eyJhbGciOiJSUzI1NiIsImtpZCI6IjNkOWNmYWE4OGVmMDViNDI0YmU2MjA1ZjQ2YjE4OGQ3MzI1N2JjNDIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmx1dHRlci1zaG9wLWFwcC0zMDM1YSIsImF1ZCI6ImZsdXR0ZXItc2hvcC1hcHAtMzAzNWEiLCJhdXRoX3RpbWUiOjE2MjE3NzUxMDYsInVzZXJfaWQiOiJ5Y0dVVWhwYTFvY2EwMThlYUx4VGZkQnRNbmsyIiwic3ViIjoieWNHVVVocGExb2NhMDE4ZWFMeFRmZEJ0TW5rMiIsImlhdCI6MTYyMTc3NTEwNiwiZXhwIjoxNjIxNzc4NzA2LCJlbWFpbCI6InRlc3RAdGVzdC5jb20iLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsidGVzdEB0ZXN0LmNvbSJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.t1xosbzllt79NV6FQ79mTQ2J3VCR5fILKMxE5-ObOxMI2DtD_kMg2AP9NXm_f1IsLF9AT5xeXeVU36goVDLQuKSWmbejOANDn7hsF6VzZMyBV1P9qehXpWgSmCscjXT8FRlKViZzxOZwCWSHS1M94n92YYhwaZltiDcQ87hhv7ZdKyLrlDsUPfr7IjNBeSVDmzws_9uBoZwYKRYCW1veWPc7HPWtdP8QT7K_vkCEvGLHfxbmVHOgUkDMzLqhgusZl34GCPVKr_PSpQ9SgC7Mg95QeZyzYzPmhasGUptq5pQsjEoqTxgYHnmEuMRRjksZT5lbfsQQFOJMsXBTIC7RDQ
Builder Test _token
ChangeNotifierProxyProvider Token false
ChangeNotifierProxyProvider Token null
ChangeNotifierProxyProvider Test null
Error: Expected a value of type 'int', but got one of type 'String'
at Object.throw_ [as throw] (http://localhost:1344/dart_sdk.js:5333:11)
at ProductProvider.ProductsProvider.new.fetchAndSetProducts (http://localhost:1344/packages/shop_app/providers/ProductProvider.dart.lib.js:84:21)
at fetchAndSetProducts.next (<anonymous>)
at http://localhost:1344/dart_sdk.js:39031:33
at _RootZone.runUnary (http://localhost:1344/dart_sdk.js:38888:58)
at _FutureListener.thenAwait.handleValue (http://localhost:1344/dart_sdk.js:33874:29)
at handleValueCallback (http://localhost:1344/dart_sdk.js:34434:49)
at Function._propagateToListeners (http://localhost:1344/dart_sdk.js:34472:17)
at _Future.new.[_completeWithValue] (http://localhost:1344/dart_sdk.js:34314:23)
at async._AsyncCallbackEntry.new.callback (http://localhost:1344/dart_sdk.js:34337:35)
at Object._microtaskLoop (http://localhost:1344/dart_sdk.js:39175:13)
at _startMicrotaskLoop (http://localhost:1344/dart_sdk.js:39181:13)
at http://localhost:1344/dart_sdk.js:34688:9
I am learning dart and flutter. I am not sure what I am missing. Can someone could help me fix this issue.
I was able to find what was causing this issue.
It appears I was reinitializing the Auth ChangeNotifierProvider. If you see the last line in the MultiProvider constructor providers argument.
If anyone else comes across such an issue ensure that you have specified the providers in the correct order.
Product Id defined int/Integer, but the parser from firebase is String. You need change in Product Id to String or use int.parse(prodId)

Flutter: StreamBuilder Snapshot -- No Data

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(),
),
);
}
Per #VirenVVarasadiya, it was definitely a case of checking the ConnectionState. Here is the final working code. Thank you!
#override
Widget build(BuildContext context) {
return StreamBuilder<AuthUser>(
stream: auth.onAuthStateChanged,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.data != null) {
AuthUser user = snapshot.data;
return ProjectScreen(
user: user,
auth: auth,
);
} else {
return displayLoginOrRegPage(context);
}
} else {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
},
);
}
Try to change WelcomeScreen to state full Widget.