Firebase signOut() issue(Flutter) - firebase

I have an app using firebase, I handle sign in or log in in RootPage then it passes to my FirstPage, if I directly logout without go any route from FirstPage it is working well and leading me to my RootPage unsigned version but if I go any route from my FirstPage and go back there then try to signOut it just give below error:
D/FirebaseAuth(10720): Notifying id token listeners about a sign-out event.
D/FirebaseAuth(10720): Notifying auth state listeners about a sign-out event.
I/flutter (10720): NoSuchMethodError: The method 'call' was called on null.
I/flutter (10720): Receiver: null
I/flutter (10720): Tried calling: call()
I have a movetohome function to go back my FirstPage from any page and I think there is something missing at it, my function is below;
_moveToHome(BuildContext context) {
Route route = MaterialPageRoute(builder: (context) => MyFirstPage(auth: FirebaseAuth.instance,userId: userId,));
Navigator.push(context, route);
}
and you can find firebase related parts of my pages;
main.dart:
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
const MyApp();
#override
Widget build(BuildContext context) {
return DynamicTheme(
defaultBrightness: Brightness.light,
data: (brightness) => ThemeData(
pageTransitionsTheme: PageTransitionsTheme(
builders: {
TargetPlatform.android: CupertinoPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
}
),
primarySwatch: Colors.blueGrey,
accentColor: Colors.blueGrey,
fontFamily: 'AvenirNext',
brightness: brightness,
),
themedWidgetBuilder: (context, theme) {
return MaterialApp(
theme: theme,
home: new RootPage(auth: new Auth()),
routes: <String, WidgetBuilder>{
MyFirstPage.routeName: (context) => new MyFirstPage(auth: new Auth()),
DetailPage.routeName: (context) => new DetailPage(auth: new Auth()),
GenrePage.routeName: (context) => new GenrePage(auth: new Auth())
},
);
});
}
}
RootPage:
import 'package:flutter/material.dart';
import 'package:randommoviefinal/screens/login_singup_page.dart';
import 'package:randommoviefinal/services/authentication.dart';
import 'package:randommoviefinal/screens/HomePage.dart';
enum AuthStatus {
NOT_DETERMINED,
NOT_LOGGED_IN,
LOGGED_IN,
}
class RootPage extends StatefulWidget {
RootPage({this.auth});
final BaseAuth auth;
#override
State<StatefulWidget> createState() => new _RootPageState();
}
class _RootPageState extends State<RootPage> {
AuthStatus authStatus = AuthStatus.NOT_DETERMINED;
String _userId = "";
#override
void initState() {
super.initState();
widget.auth.getCurrentUser().then((user) {
setState(() {
if (user != null) {
_userId = user?.uid;
}
authStatus =
user?.uid == null ? AuthStatus.NOT_LOGGED_IN : AuthStatus.LOGGED_IN;
});
});
}
void loginCallback() {
widget.auth.getCurrentUser().then((user) {
setState(() {
_userId = user.uid.toString();
});
});
setState(() {
authStatus = AuthStatus.LOGGED_IN;
});
}
void logoutCallback() {
setState(() {
authStatus = AuthStatus.NOT_LOGGED_IN;
_userId = "";
});
}
Widget buildWaitingScreen() {
return Scaffold(
body: Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
),
);
}
#override
Widget build(BuildContext context) {
switch (authStatus) {
case AuthStatus.NOT_DETERMINED:
return buildWaitingScreen();
break;
case AuthStatus.NOT_LOGGED_IN:
return new LoginSignupPage(
auth: widget.auth,
loginCallback: loginCallback,
);
break;
case AuthStatus.LOGGED_IN:
if (_userId.length > 0 && _userId != null) {
return new MyFirstPage(
userId: _userId,
auth: widget.auth,
logoutCallback: logoutCallback,
);
} else
return buildWaitingScreen();
break;
default:
return buildWaitingScreen();
}
}
}
MyFirstPage:
class MyFirstPage extends StatefulWidget {
MyFirstPage({Key key, this.auth, this.userId, this.logoutCallback})
: super(key: key);
final auth;
final VoidCallback logoutCallback;
final String userId;
static String routeName = "/MyFirstPage";
#override
_MyFirstPageState createState() => new _MyFirstPageState();
}
class _MyFirstPageState extends State<MyFirstPage> {
String userId;
List<Movies> _moviesList;
final FirebaseDatabase _database = FirebaseDatabase.instance;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
Query _moviesQuery;
StreamSubscription<Event> _onMoviesAddedSubscription;
StreamSubscription<Event> _onMoviesChangedSubscription;
#override
void initState() {
// TODO: implement initState
super.initState();
_moviesList = new List();
_moviesQuery = _database
.reference()
.child("movies")
.orderByChild("userId")
.equalTo(widget.userId);
_onMoviesAddedSubscription = _moviesQuery.onChildAdded.listen(onEntryAdded);
_onMoviesChangedSubscription =
_moviesQuery.onChildChanged.listen(onEntryChanged);
}
void dispose() {
_onMoviesAddedSubscription.cancel();
_onMoviesChangedSubscription.cancel();
super.dispose();
}
onEntryChanged(Event event) {
var oldEntry = _moviesList.singleWhere((entry) {
return entry.key == event.snapshot.key;
});
setState(() {
_moviesList[_moviesList.indexOf(oldEntry)] =
Movies.fromSnapshot(event.snapshot);
});
}
onEntryAdded(Event event) {
setState(() {
_moviesList.add(Movies.fromSnapshot(event.snapshot));
});
}
signOut() async {
try {
await widget.auth.signOut();
widget.logoutCallback();
} catch (e) {
print(e);
}
}
updateMovies(Movies movies) {
//Toggle completed
movies.watched = !movies.watched;
if (movies != null) {
_database.reference().child("movies").child(movies.key).set(movies.toJson());
}
}
deleteMovies(String moviesId, int index) {
_database.reference().child("movies").child(moviesId).remove().then((_) {
print("Delete $moviesId successful");
setState(() {
_moviesList.removeAt(index);
});
});
}
My another page for example:
class DetailPage extends StatefulWidget {
MovieDetailPage({Key key, this.title, this.auth, this.userId})
: super(key: key);
final auth;
final String userId;
static String routeName = "/MyHomePage";
final String title;
#override
_DetailPageState createState() => _DetailPageState();}
class _DetailPageState extends State<MovieDetailPage> {
String userId;
final FirebaseDatabase _database = FirebaseDatabase.instance;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
StreamSubscription<Event> _onMoviesAddedSubscription;
StreamSubscription<Event> _onMoviesChangedSubscription;
Query _moviesQuery;
List<Movies> _moviesList;
_moveToHome(BuildContext context) {
Route route = MaterialPageRoute(builder: (context) => MyFirstPage(auth: FirebaseAuth.instance,userId: userId,));
Navigator.push(context, route);}
#override
void initState() {
// TODO: implement initState
super.initState();
_moviesList = new List();
_moviesQuery = _database
.reference()
.child("movies")
.orderByChild("userId")
.equalTo(widget.userId);
_onMoviesAddedSubscription = _moviesQuery.onChildAdded.listen(onEntryAdded);
_onMoviesChangedSubscription =
_moviesQuery.onChildChanged.listen(onEntryChanged);
widget.auth.getCurrentUser().then((user) {
if (user != null) {
userId = user?.uid;
}});
}
void dispose() {
_onMoviesAddedSubscription.cancel();
_onMoviesChangedSubscription.cancel();
super.dispose();
}
onEntryChanged(Event event) {
var oldEntry = _moviesList.singleWhere((entry) {
return entry.key == event.snapshot.key;
});
setState(() {
_moviesList[_moviesList.indexOf(oldEntry)] =
Movies.fromSnapshot(event.snapshot);
});
}
onEntryAdded(Event event) {
setState(() {
_moviesList.add(Movies.fromSnapshot(event.snapshot));
});
}
updateMovies(Movies movies) {
//Toggle completed
movies.watched = !movies.watched;
if (movies != null) {
_database.reference().child("movies").child(movies.key).set(movies.toJson());
}
}
deleteMovies(String moviesId, int index) {
_database.reference().child("movies").child(moviesId).remove().then((_) {
print("Delete $moviesId successful");
setState(() {
_moviesList.removeAt(index);
});
});
}
...
IconButton(
icon: Icon(Icons.home),
iconSize: 35,
onPressed: () {
_moveToHome(context);
},
),
...
and authentication.dart
import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
abstract class BaseAuth {
Future<String> signIn(String email, String password);
Future<String> signUp(String email, String password);
Future<FirebaseUser> getCurrentUser();
Future<void> sendEmailVerification();
Future<void> signOut();
Future<bool> isEmailVerified();
}
class Auth implements BaseAuth {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Future<String> signIn(String email, String password) async {
AuthResult result = await _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
return user.uid;
}
Future<String> signUp(String email, String password) async {
AuthResult result = await _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
FirebaseUser user = result.user;
return user.uid;
}
Future<FirebaseUser> getCurrentUser() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user;
}
Future<void> signOut() async {
return _firebaseAuth.signOut();
}
Future<void> sendEmailVerification() async {
FirebaseUser user = await _firebaseAuth.currentUser();
user.sendEmailVerification();
}
Future<bool> isEmailVerified() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user.isEmailVerified;
}
}

I think FirebaseAuth is losing current user when you leave your home page try moving
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
class Auth implements BaseAuth {
//Rest of class
to right outside your class this way it has global scope just make sure it not in the abstract class that's right above it

Related

Flutter Androit | Firebase Authentication Persistance with Firestore

I wan't to persist authentication state of an user registered in Firebase Auth. The user has data in Firestore DB.
My final attempt :
main.dart
#override
Widget build(BuildContext context) {
return StreamProvider<AppUser?>.value(
value: AuthenticationService().user,
initialData: null,
child: const ....
);
}
home.dart
#override
Widget build(BuildContext context) {
var user = Provider.of<AppUser?>(context);
print(user);
Home.user = user;
...
}
authentication.dart
class AuthenticationService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseFirestore _db = FirebaseFirestore.instance;
final CollectionReference _usersCollection = FirebaseFirestore.instance.collection('users');
Stream<AppUser?> get user {
return _auth.authStateChanges().map((firebaseUser) {
AppUser? user;
_usersCollection.doc(firebaseUser!.uid).get().then((DocumentSnapshot userSnapshot) {
user = _toAppUser(firebaseUser, userSnapshot);
});
return user;
});
}
}
But with this code, the get user is always null, even just afte logging in
So after dozens of changes, the following code is "working" :
main.dart
#override
Widget build(BuildContext context) {
cart.cache();
return const MaterialApp(
title: 'CharleMi\'App',
debugShowCheckedModeBanner: false,
home: Home(),
);
}
home.dart
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasData) {
AuthenticationService.delegate(snapshot.data).then((user) {
if (user != null) {
Home.user = user;
}
});
} else if (snapshot.hasError) {
print(snapshot.error);
}
return ....
authentication.dart
static Future<AppUser?> delegate(User? data) async {
return AuthenticationService()._toAsyncAppUser(data, null);
}
Future<AppUser?> _toAsyncAppUser(User? user) async {
AppUser _user = AppUser(uid: user!.uid);
var exists = await _user.init(); //Return true, get vars from firestore
if (exists) {
return _user; //Returned here when logging in (because exists)
}
return null;
}

Trying to login with google but always return null

hey guys i trying to login with google using firebase in flutter but its always return null in the next page
how can i get the current user in the next page after login with google
i already connect with firebase and generate sha-1 and sha-256
here's the code
login button
onPressed: () async {
setState(() {
_isSigningIn = true;
});
try {
final user = await googleSignIn.signIn();
final googleAuth = await user.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken
);
final googleAccount = await FirebaseAuth.instance.signInWithCredential(credential);
if(googleAccount != null) {
Navigator.pushReplacementNamed(context, HomeScreen.id);
}
} catch (e) {
final snackbar = SnackBar(content: Text(e.toString()));
_scaffoldKey.currentState.showSnackBar(snackbar);
} finally {
setState(() {
_isSigningIn = false;
});
}
},
home_screen.dart
class _HomeScreenState extends State<HomeScreen> {
final _auth = FirebaseAuth.instance;
User _currentUser;
void getCurrentUser() async {
try {
var currentUser = await _auth.currentUser;
if (currentUser != null) {
_currentUser = currentUser;
}
} catch(e) {
print(e);
}
}
#override
void initState() {
super.initState();
getCurrentUser();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_currentUser.email),
),
);
}
}
thank you
This line is an asynchronous function, it is for this reason that you receive null, you must add an await.
final googleAccount = await FirebaseAuth.instance.signInWithCredential(credential);
In addition to this, to access the information of the current user you must do it this way.
googleAccount.user

How to access return value of a callback function present inside a Future method | Dart

auth.dart
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// Sign in with Phone Number Future<bool>
Future signInWithPhone(String phone, BuildContext context) async {
final PhoneVerificationCompleted verificationCompleted = (AuthCredential credential) async {
..
};
final PhoneVerificationFailed verificationFailed = (AuthException exception) {
// This is the return value which i want to access outside of this callback
return exception.message;
};
final PhoneCodeSent codeSent = (String verificationId, [int forceResendingToken]) {
...
};
final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout = (String verificationId) {
Navigator.of(context).pop();
...
};
await _auth.verifyPhoneNumber(
phoneNumber: phone,
timeout: Duration(seconds: 10),
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout
);
// Trying to access the callback value
print(verificationFailed);
}
}
login.dart
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return Container(
// here i am calling the phonesign in method
await _auth.signInWithPhone(phoneNumber, context);
// and here i want to show the message
);
}
}
What is the Problem?
The problem is i want to access the value of any callback of the verifyPhoneNumber , For example verificationFailed which returns an exception message and i want to access it outside so that i can use that in other widgets.
What i have tried so far?
1) I have tried printing it and it returns AuthException Closure: () => String I Don't know how to access the value.
2) I have tried using setState but as my class is not inside a statefulWidget i can't assign that value to a variable
*Why i need it? *
Look i have two pages one is called login.dart and 2nd is called auth.dart .In my auth page i mostly place the classes and code thats related to backend (to properly organize the code) and in the login page i have a stateful widget, consising of a phone textfield and on submit i call the method signInWithPhone .Everything works but if any error appears such as wrong user phone number format then the verificationFailed callback is triggered and thus i want to access the return value outside(bcz we cant directly get the return value) so that i can show it from where i called it.
Can anybody please help me so that i can continue my code.
It's clear now what you're trying to do. Using Provider is one way to do it. Another way is to pass a callback to Login Page:
class LoginPage extends StatefulWidget {
final Function(String) verificationMessage;
const LoginPage({Key key, this.verificationMessage}) : super(key: key);
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
…
…
final PhoneVerificationFailed verificationFailed = (AuthException exception) {
widget.verificationMessage(exception.message);’
};
}
Now you just pass a callback function whenever you call LoginPage
LoginPage(verificationMessage: (String message) => {
setState(() {
// Do what you want with the message
})
})
In your case:
auth.dart
class AuthService {
**final Function(String) verificationMessage;**
**AuthService({#required this.verificationMessage});**
final FirebaseAuth _auth = FirebaseAuth.instance;
// Sign in with Phone Number Future<bool>
Future signInWithPhone(String phone, BuildContext context) async {
final PhoneVerificationCompleted verificationCompleted = (AuthCredential credential) async {
..
};
final PhoneVerificationFailed verificationFailed = (AuthException exception) {
// This is the return value which i want to access outside of this callback
**verificationMessage(exception.message);**
};
final PhoneCodeSent codeSent = (String verificationId, [int forceResendingToken]) {
...
};
final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout = (String verificationId) {
Navigator.of(context).pop();
...
};
await _auth.verifyPhoneNumber(
phoneNumber: phone,
timeout: Duration(seconds: 10),
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout
);
// Trying to access the callback value
print(verificationFailed);
}
}
login.dart
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
String message = "";
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Text(message),
Flatbutton(
....
onPressed: (){
AuthService(verificationMessage: (String newMessage) => {
setState(() {
message = newMessage
})
})
}
)
]
)
);
}
}
I suggest you make your widget stateful, then you can do this:
final PhoneVerificationFailed verifiFailed = (AuthException exception) {
setState(() {
errorMessage = exception.message;
});
print('${exception.message}');
};
It's very easy to switch your widget to be stateful:
class LoginPage extends StatefulWidget {
#override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
return Column(children: [
...
errorMessage == null ? SizedBox() : Text( errorMessage),
...
]);
}
}
When errorMessage is updated inside the setState the error message will be present.
You can also use Provider; here are a few links for you to look them up:
https://medium.com/flutter-community/flutter-firebase-login-using-provider-package-54ee4e5083c7
https://www.youtube.com/watch?v=MjY1_LaXyd8
https://medium.com/#JigneshPatel23/how-to-implement-an-authentication-feature-using-a-provider-in-flutter-1f351447d09d

Flutter Firebase Phone Authentication with BLoC

I'm trying to achieve Firebase phone authentication with BLoC pattern.
This is my bloc class
class AuthBloc extends Bloc<AuthEvent, AuthState> {
final AuthProvider authProvider;
AuthBloc({this.authProvider}) : assert(authProvider!= null);
#override
AuthState get initialState => Uninitialized();
#override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is AppLaunched) {
yield* _mapAppLaunchedToState();
} else if(event is OtpRequested) {
yield* _mapOtpRequestedToState();
} else if (event is LoggedIn) {
yield* _mapLoggedInToState();
} else if (event is LoggedOut) {
yield* _mapLoggedOutToState();
}
}
Stream<AuthState> _mapAppLaunchedToState() async* {
try {
final isSignedIn = await authProvider.isLoggedIn();
if (isSignedIn) {
final name = userProvider.firebaseUser;
yield Authenticated(name);
} else {
yield Unauthenticated();
}
} catch (_) {
yield Unauthenticated();
}
}
Stream<AuthState> _mapOtpRequestedTostate() async* {
yield AuthInProgress();
try {
FirebaseUser firebaseUser = await authProvider.verifyPhone();
if (firebaseUser != null) {
yield Authenticated(firebaseUser);
} else {
yield Unauthenticated();
}
} catch(_, stacktrace) {
yield Unauthenticated();
}
}
Stream<AuthState> _mapLoggedInToState() async* {
yield Authenticated(userProvider.firebaseUser);
}
Stream<AuthState> _mapLoggedOutToState() async* {
yield Unauthenticated();
authProvider.signOutUser();
}
}
This is the AuthProvider
class AuthProvider extends BaseAuthProvider {
String _verificationId;
FirebaseUser user;
final FirebaseAuth _firebaseAuth;
AuthProvider(
{FirebaseAuth firebaseAuth})
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance;
#override
Future<FirebaseUser> verifyPhone() async {
final PhoneVerificationCompleted verificationCompleted =
(AuthCredential phoneAuthCredential) async {
user = (await _firebaseAuth.signInWithCredential(phoneAuthCredential)).user;
};
final PhoneVerificationFailed verificationFailed =
(AuthException authException) {
print(
'Phone number verification failed. Code: ${authException.code}. Message: ${authException.message}');
};
final PhoneCodeSent codeSent =
(String verificationId, [int forceResendingToken]) async {
_verificationId = verificationId;
};
final PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
(String verificationId) {
_verificationId = verificationId;
};
await _firebaseAuth.verifyPhoneNumber(
phoneNumber: _phoneNumberProvider.number,
timeout: const Duration(seconds: 5),
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout);
return user;
}
Future<FirebaseUser> signInWithPhone() async {
final AuthCredential credential = PhoneAuthProvider.getCredential(
verificationId: _verificationId,
smsCode: _otpProvider.number,
);
final FirebaseUser user =
(await _firebaseAuth.signInWithCredential(credential)).user;
final FirebaseUser currentUser = await _firebaseAuth.currentUser();
assert(user.uid == currentUser.uid);
if (user != null) {
return currentUser;
} else {
return null;
}
}
#override
Future<void> signOutUser() async {
return Future.wait([_firebaseAuth.signOut()]); // terminate the session
}
#override
Future<FirebaseUser> getCurrentUser() async {
return await _firebaseAuth.currentUser(); //retrieve the current user
}
#override
Future<bool> isLoggedIn() async {
final user =
await _firebaseAuth.currentUser(); //check if user is logged in or not
return user != null;
}
#override
void dispose() {}
}
When the verifyPhone from AuthBloc is called it gets executed async and which in turn calls the mcallbacks which are again async. So the _mapOtpRequestedToState() will be finished before we get back the FirebaseUser from AuthProvider. Hence Authenticated State is not being yielded and user is not getting logged in.
Help is needed!!!
I think that most of the time, a code that is readable is much better.
The following sample implements the logic you intended to write, using an (Action -> Event) mechanism:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Provider<AppStateBloc>(
builder: (_) => AppStateBloc(),
dispose: (_, bloc) {
bloc.dispose();
},
child: MaterialApp(
home: TestPage(),
),
);
}
}
class TestPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
AppStateBloc appStateBloc = Provider.of<AppStateBloc>(context, listen: false);
return Scaffold(
appBar: AppBar(title: Text('Flow Test')),
body: Column(
children: <Widget>[
StreamBuilder<AppState>(
stream: appStateBloc.stateOut,
initialData: AppState.initial,
builder: (BuildContext context, AsyncSnapshot<AppState> snapshot) {
AppState state = snapshot.data;
return Column(
children: <Widget>[
Text('Current State: $state'),
SizedBox(height: 10.0),
if (state == AppState.initial || state == AppState.failure)
RaisedButton(
onPressed: () => appStateBloc.actionIn(AppStateAction.login),
child: Text('Authenticate'),
),
if (state == AppState.authenticated)
RaisedButton(
onPressed: () => appStateBloc.actionIn(AppStateAction.logout),
child: Text('Logout'),
),
],
);
},
),
],
),
);
}
}
class AppStateBloc {
StreamController<AppState> _controllerState = StreamController<AppState>.broadcast();
Stream<AppState> get stateOut => _controllerState.stream;
Function(AppState) get _stateIn => _controllerState.sink.add;
StreamController<AppStateAction> _controllerAction = StreamController<AppStateAction>.broadcast();
Function(AppStateAction) get actionIn => _controllerAction.sink.add;
StreamSubscription _subscription;
AppStateBloc() {
_subscription = _controllerAction.stream.listen(_businessLogic);
}
// All the business logic comes here
void _businessLogic(AppStateAction action) async {
switch (action) {
case AppStateAction.login:
// do authentication
User user = await fakeAuthenticator.verifyUser();
if (user == null) {
_stateIn(AppState.failure);
} else {
_stateIn(AppState.authenticated);
}
break;
case AppStateAction.logout:
// do what needs to be done in this case
await fakeAuthenticator.logout();
_stateIn(AppState.initial);
break;
default:
// nothing
break;
}
}
void dispose() {
_subscription?.cancel();
_controllerAction?.close();
_controllerState?.close();
}
}
enum AppStateAction {
none,
login,
logout,
}
enum AppState {
initial,
authenticated,
failure,
}
class User {}
class FakeAuthenticator {
User _user;
Future<User> verifyUser() async {
// Simulation of Authentication made at server side
await Future.delayed(const Duration(seconds: 1));
// Successful authentication
_user = User();
return _user;
}
Future<void> logout() async {
// Simulation of Authentication made at server side
await Future.delayed(const Duration(seconds: 1));
_user = null;
}
User get user => _user;
// ------- Singleton
static final FakeAuthenticator _instance = FakeAuthenticator._internal();
factory FakeAuthenticator() => _instance;
FakeAuthenticator._internal();
}
FakeAuthenticator fakeAuthenticator = FakeAuthenticator();
The main difference with your code is that with this one, but this is my personal feeling, you are more "in control" of your business logic.

Null error when I want my firebase user id with currentUser()

Error
NoSuchMethodError: The method 'currentUser' was called on null.
Receiver: null
Tried calling: currentUser()
I'm interesting in know where i need to using this code:
String _uid = '';
void initState() {
super.initState();
auth.currentUser().then((userId) {
setState(() {
_uid = userId;
});
});
}
This code is from the home_page.dart (see below)
I had also tried to using:
widget.auth.current....
root_page
In bottom of the file i had tried different approach.
Instead of HomePage then _HomePageState(comments out)
class RootPage extends StatefulWidget {
RootPage({this.auth});
final BaseAuth auth;
#override
State<StatefulWidget> createState() => new _RootPageState();
}
enum AuthStatus {
notSignedIn,
signedIn
}
class _RootPageState extends State<RootPage>{
AuthStatus authStatus = AuthStatus.notSignedIn;
#override
void initState() {
super.initState();
widget.auth.currentUser().then((userId) {
setState(() {
authStatus = userId == null ? AuthStatus.notSignedIn : AuthStatus.signedIn;
});
});
}
void _signedIn(){
setState(() {
authStatus = AuthStatus.signedIn;
});
}
void _signedOut() {
setState(() {
authStatus = AuthStatus.notSignedIn;
});
}
#override
Widget build(BuildContext context) {
switch(authStatus) {
case AuthStatus.notSignedIn:
return new LoginPage(
auth: widget.auth,
onSignedIn: _signedIn,
);
case AuthStatus.signedIn:
/*
return new HomePageState(
auth: widget.auth,
onSignedOut: _signedOut,
);
*/
return new HomePage(
auth: widget.auth,
onSignedOut: _signedOut,
);
}
}
}
auth.dart
This is the class I'm using to get current user from auth.
abstract class BaseAuth{
Future<String> signInWithEmailAndPassword(String email, String password);
Future<String> createUserWithEmailAndPassword(String email, String password);
Future<String> currentUser();
Future<void> signOut();
}
class Auth implements BaseAuth{
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
Future<String> signInWithEmailAndPassword(String email, String password) async {
FirebaseUser user = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password);
return user.uid;
}
Future<String> createUserWithEmailAndPassword(String email, String password) async {
FirebaseUser user = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: password);
return user.uid;
}
Future<String> currentUser() async {
FirebaseUser user = await _firebaseAuth.currentUser();
return user.uid;
}
Future<void> signOut() async {
return _firebaseAuth.signOut();
}
}
home_page.dart
I had tried different things in this file but get different errors.
class HomePage extends StatefulWidget {
HomePage({this.auth, this.onSignedOut});
final BaseAuth auth;
final VoidCallback onSignedOut;
void _signOut() async {
try {
await auth.signOut();
onSignedOut();
} catch (e) {
print(e);
}
}
#override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
/*
_HomePageState({this.auth, this.onSignedOut});
final BaseAuth auth;
final VoidCallback onSignedOut;
*/
//final BaseAuth auth;
String _uid = '';
#override
void initState() {
super.initState();
auth.currentUser().then((userId) {
setState(() {
_uid = userId;
});
});
}
_auth.currentUser() does not return the user ID, it returns a FirebaseUser object.
Change it like this :
auth.currentUser().then((user) {
if (user == null || user.isAnonymous) {
// what will you do?
return;
}
setState(() {
_uid = user.uid;
});
});
Yet, I would 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.
You can use like this.
someMethod() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
print(user.uid);
}

Resources