I have two pages, Page_1 which uses didChangeAppLifecycleState which looks out for when the user closes the app. While the user is on the app, a timer increases, as soon as the user closes the app, the timer stops.
Page_2 has a logout button, which uses firebase signOut() function.
If I were to use the logout function in Page_1 and sign in with another account, the timer would start new for the second user, but If I were to logout from my Page_2 and sign in with another user, then the timer would of carried from the first user instead of starting again,
What I'm asking is how can I use WidgetsBinding.instance!.removeObserver(this); on Page_2 instead of Page_1
Page_1.dart
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
User? user = FirebaseAuth.instance.currentUser;
UserModel loggedInUser = UserModel();
final _auth = FirebaseAuth.instance;
StreamController<int> controller = StreamController();
late StreamSubscription<int> streamSubscription;
#override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
print(user!.uid);
FirebaseFirestore.instance
.collection("users")
.doc(user!.uid)
.get()
.then((value) {
loggedInUser = UserModel.fromMap(value.data());
setState(() {});
});
app_start = DateTime.now();
}
late DateTime app_start;
late DateTime app_end;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print(state);
// if user clicks logout end this funciton
// Checks if app is active
final isBackground = state == AppLifecycleState.paused;
if (state == AppLifecycleState.inactive ||
state == AppLifecycleState.detached) return;
if (isBackground) {
FirebaseFirestore.instance
.collection("users")
.doc(user!.uid)
.get()
.then((value) {
loggedInUser = UserModel.fromMap(value.data());
if (loggedInUser.timeActive == null) {
loggedInUser.timeActive = 1;
} else {
}
app_end = DateTime.now();
final differenceInDays = app_end.difference(app_start).inSeconds;
int? test = loggedInUser.timeActive;
int? totalOnTime = differenceInDays + loggedInUser.timeActive!.toInt();
FirebaseFirestore.instance
.collection('users')
.doc(loggedInUser.uid)
.update({"timeActive": totalOnTime});
setState(() {});
});
} else {
app_start = DateTime.now();
}
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
WidgetsBinding.instance!.removeObserver(this);
}
}
Page_2.dart
#override
void dispose() {
// TODO: implement dispose
super.dispose();
WidgetsBinding.instance!.removeObserver(this);
}
ElevatedButton(
onPressed: () {
dispose()
_auth.signOut();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) =>
LoginScreen()));
print("clicked");
},
child: Text('Log out'),
)
Related
I have a LoginPage() for login and a DashBoard() page which comes after logging in succesfully.
I am using a Controller() page to provide the authetication which listens for any authentication changes in firebase, it looks like this :
class Controller extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
else if (snapshot.hasData) {
return DashBoard();
}
return LoginPage();
},
));
}
}
I also have a Log out button in another page. When I try to logout, it throws me first to DashBoard() page (which means that snapshot.hasData has some value) and after 2-3 seconds it throws me then to LoginPage() but the code never goes into this section
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
It first goes into this block conditon
else if (snapshot.hasData) {
return DashBoard();
}
And then the LoginPage() section. Also when I try to Login, it never shows me the Circularwait, but throws me to DashBoard() page.
What is the best way to achieve this ?
My fireBase auth file looks like this :
class GoogleSignInProvider extends ChangeNotifier {
final googleSignIn = GoogleSignIn();
GoogleSignInAccount _user;
GoogleSignInAccount get user => _user;
Future signInWithGoogle() async {
try {
final GoogleSignInAccount googleuser = await googleSignIn.signIn();
if (googleuser == null) return;
_user = googleuser;
final GoogleSignInAuthentication googleAuth =
await googleuser.authentication;
final GoogleAuthCredential credential = GoogleAuthProvider.credential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
// Fluttertoast.showToast(msg: "Account created");
await FirebaseAuth.instance.signInWithCredential(credential);
print(_user);
notifyListeners();
} catch (e) {
print(e.toString());
}
}
Future signOutGoogle() async {
await googleSignIn.disconnect();
FirebaseAuth.instance.signOut();
}
}
interact with the bloc using a streambuilder in your ui. the ui creates events which the bloc code handles and response with state output
abstract class LoginEvent extends Equatable{
const LoginEvent();
#override
List<Object>get props=>[];
}
class LoginUser{
final String email;
final String password;
const LoginUser(this.email,this.password);
String get getEmail { return this.email;}
String get getPassword{ return this.password;}
}
class AuthenticateEvent extends LoginEvent{
final LoginUser user;
const AuthenticateEvent(this.user);
#override
List<Object> get props => [user];
LoginUser get getUser{return this.user;}
}
class LoginState extends Equatable{
final LoginView _login;
const LoginState(this._login);
#override
List<Object> get props => [_login];
LoginView get getLogin {return this._login;}
}
class BlocLogin
{
Stream<LoginState> get loginStream => _loginController.stream;
final _loginController = BehaviorSubject<LoginState>();
void dispose()
{
_loginController.close();
}
authenticate(BuildContext context,LoginEvent loginEvent) async
{
if (loginEvent is AuthenticateEvent)
{
LoginView param =
new LoginView(loginEvent.getUser.getEmail, loginEvent.getUser.getPassword);
LoginView loginValue =await Provider.of<Api>(context, listen: false)
.addLogin(context, param);
if (loginValue.returnMessage == "Failed") {
DialogCaller.showIncorrectLoginDialog(context).then((value2) {});
} else {
Provider.of<Api>(context, listen: false).dataCache.login = loginValue;
LoginState loginState=new LoginState(loginValue);
_loginController.sink.add(loginState);
}
}
}
}
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
I am practicing a email authentication in flutter and almost everything is over. Now, i want to use sharedPreference to stay the user logged in. I have tried something, but i don't get result. I am using a bool type to get whether user loggedIn or not. But i am very new to this, can you help me in this? and is there anything i am missing out?
This is the sharedPreference static Class i am using
class sharedPreference {
static String sharedPreferenceUserLoggedInKey = 'userLoggedIn';
static String sharedPreferenceUserSignedUpKey = 'userSignedUp';
//saving data to sharedPreference
static Future<bool> saveUserLoggedInSharedPreference(
bool isUserLoggedIn) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return await prefs.setBool(sharedPreferenceUserLoggedInKey, isUserLoggedIn);
}
static Future<bool> saveUserSignedUpSharedPreference(
bool isUserSignUp) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return await prefs.setBool(sharedPreferenceUserSignedUpKey, isUserSignUp);
}
//getting data to sharedPreference
static Future<bool> getUserLoggedInSharedPreference() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return await prefs.getBool(sharedPreferenceUserLoggedInKey);
}
static Future<bool> getUserSignedUpSharedPreference() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
return await prefs.getBool(sharedPreferenceUserSignedUpKey);
}
}
This is the signIn button triggering the setBool:
SignInButton:
FlatButton(
onPressed: ()
{
HelperFunction.saveUserLoggedInSharedPreference(true);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => DashBoard(email: email),
),
})
The main function
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light
.copyWith(systemNavigationBarColor: Colors.black));
runApp(
DevicePreview(
enabled: kReleaseMode,
builder: (context) => FlashChat(),
),
);
}
class FlashChat extends StatefulWidget {
#override
_FlashChatState createState() => _FlashChatState();
}
class _FlashChatState extends State<FlashChat> {
bool isUserLoggedIn;
bool isUserSignedUp;
void getLoggedInStatus() async {
await HelperFunction.getUserLoggedInSharedPreference().then((value) {
isUserLoggedIn = value;
});
}
void getSignedUpStatus() async {
await HelperFunction.getUserSignedUpSharedPreference().then((value) {
isUserSignedUp = value;
});
}
#override
void initState() {
getLoggedInStatus();
getSignedUpStatus();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: isUserLoggedIn == true
? DashBoard.id: WelcomeScreen.id,
routes: {
WelcomeScreen.id: (context) => WelcomeScreen(),
LoginScreen.id: (context) => LoginScreen(),
RegistrationScreen.id: (context) => RegistrationScreen(),
DashBoard.id: (context) => DashBoard(),
},
debugShowCheckedModeBanner: false,
);
});
});
when the user gets login set
prefs.setBool("isLogin", True);
and when the user get a logout in logout function put
pref.clear()
and in splash screen or at starting put this logic
SharedPreferences prefs = await SharedPreferences.getInstance();
var isLogin = prefs.getBool("isLogin");
if (isLogin)
{
//Navigate user to the required screen
}
else{
//navigate user to login screen
}
I know this question has been asked a lot and I have spent a lot of time reading and trying to implement the answers. So I am trying to get the response from isEmailVerified from Firebase Auth to work and it does work but right now it always returns false unless I refresh the app or close it and reopen it. which is obviously a bad user experience. How do I get the response to update without having to close the app.
here is the relevant pieces of code.
Future<bool> isEmailVerified() async {
FirebaseUser user = await _auth.currentUser();
if (user == null) {
return false;
} else {
await user.reload();
user = await _auth.currentUser();
return user.isEmailVerified;
}
}
main.dart
child: Consumer<Auth>(
builder: (_, auth, __) => MaterialApp(
theme: Provider.of<ThemeNotifier>(context).getTheme(),
home: FutureBuilder(
future: Future.wait([auth.isEmailVerified(), auth.tryAutoLogin()]),
builder: (BuildContext ctx, AsyncSnapshot authResultSnapshot) =>
authResultSnapshot.connectionState == ConnectionState.done
? authResultSnapshot.data[1]
? authResultSnapshot.data[0]
? HearingsScreen()
: SplashScreen(
emailVerified: true,
)
: LoginScreen()
: SplashScreen(),
),
It is not returning true until I restart the app
Things I have tried besides this:
1) await user.getIdToken(refresh: true);
2) sign user out then back in
3) firebase_user_stream package
Any help is appreciated.
I have implemented the same scenario in a splash screen with below code, you can change it as per your requirement. :
//To check is User is logged in
Future<bool> isLoggedIn() async {
FirebaseUser user = await _fireBaseAuth.currentUser();
if (user == null) {
return false;
}
return user.isEmailVerified;
}
and
countDownTime() async {
return Timer(
Duration(seconds: splashDuration),
() async {
if (await userAuth.isLoggedIn()) {
Navigator.pushReplacement(
context,
ScaleRoute(
widget: HomeScreen(),),
);
}
} else {
Navigator.pushReplacement(
context,
ScaleRoute(
widget: LoginScreen(),),
);
}
},
);
}
and
#override
void initState() {
super.initState();
countDownTime();
}
Update
One needs to implement isEmailVerified in initState() function periodically which can be the ideal approach to execute the verification with firebase.
bool _isUserEmailVerified;
Timer _timer;
#override
void initState() {
super.initState();
// ... any code here ...
Future(() async {
_timer = Timer.periodic(Duration(seconds: 10), (timer) async {
await FirebaseAuth.instance.currentUser()..reload();
var user = await FirebaseAuth.instance.currentUser();
if (user.isEmailVerified) {
setState((){
_isUserEmailVerified = user.isEmailVerified;
});
timer.cancel();
}
});
});
}
#override
void dispose() {
super.dispose();
if (_timer != null) {
_timer.cancel();
}
}
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.