How to trigger dispose from another page - firebase

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

How to fix circular wait progress while logging out in flutter app using Firebase authentication?

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);
}
}
}
}

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 use sharedPreference in flutter to stay user loggedin in flutter using a setBool and GetBool

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
}

Firebase email verification Flutter

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();
}
}

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.

Resources