Flutter unable to login after create new account - flutter

After building the app, if I try to login it's working fine, but if I create a new account then try to login again, login is not working. I have seen after debugging that I'm getting token in _authenticate method. I think notifyListeners not working here but I don't know why?
I'm using the provider package in my flutter app. I have an Auth provider class where I'm saving data in firebase and also login by firebase. Below it's my provider class.
class Auth with ChangeNotifier {
String? _token;
late DateTime _expiryDate = DateTime.now();
late String _userId;
bool get isAuth {
return token != null;
}
String? get token {
if ((_expiryDate).isAfter(DateTime.now())) {
return _token;
}
return null;
}
Future _authenticate(String email, String password, String urlSegment) async {
var urlParse = Uri.parse(urlSegment);
try {
final response = await http.post(urlParse,
body: jsonEncode({
'email': email,
'password': password,
'returnSecureToken': true
})
);
final responseData = jsonDecode(response.body);
if (responseData['error'] != null) {
throw HttpException(responseData['error']['message']);
}
// set token and user id from firebase response
_token = responseData['idToken'];
_userId = responseData['localId'];
_expiryDate = DateTime.now()
.add(Duration(seconds: int.parse(responseData['expiresIn'])));
notifyListeners();
return responseData['idToken'];
} catch (error) {
rethrow;
}
}
Future login(String email, String password) async {
print(email);
String url = Constants.firebaseLoginUrl;
return _authenticate(email, password, url);
}
Future signup(String email, String password) async {
String url = Constants.firebaseSignupUrl;
return _authenticate(email, password, url);
}
}
In signUp page I have tried below code to create a new user
Future<void> signUpSubmit() async {
if (_formKey.currentState!.validate()) {
await Provider.of<Auth>(context, listen: false).signup(_email.text, _pass.text);
}
I have checked new data is saving perfectly.
In signUp page there has a login button, after click on login button I have redirect in login page,
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const LoginPage()
),
);
},
child: const Text('Log in to access'),
)
After click on login I have redirect in login page, then tried login again but it's not working. In debug I'm getting token if I print(token) in _authenticate method.
In main.dart my consumer is looks like
child: Consumer<Auth>(
builder: (ctx,auth, _) => MaterialApp(
home: auth.isAuth ? const HomePage():const LoginPage(),
)
After create account if I rebuild app again then login is working? How I will solve this problem?

See you are redirecting directly in loginPage rather than via the main page ! Just redirect the login button to main page, in your main page there has condition
home: auth.isAuth ? const HomePage(): const LoginPage()
So, if it is auth false it will always redirect to the login page.
Change
MaterialPageRoute( builder: (context) => const LoginPage() ),
To
MaterialPageRoute( builder: (context) => const MainPage() ),

Related

Page not disposed when signed up: Flutter

I am trying to implement provider package to signUp/signIn/signOut using Firebase Auth.
My ChangeNotifier class is-
import 'package:e_shoppie/db/authentication.dart';
import 'package:e_shoppie/db/user_services.dart';
import 'package:e_shoppie/structure/constants.dart';
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
//User can only be one of these states
/**
* uninitialized: User just opened the app (just opening)
* unauthenticated: Show the login screen to the user
* authenticating: Show a circular indicator the user
* authenticated: User is looged into the app
*/
enum Status { uninitialized, authenticated, authenticating, unauthenticated }
class UserProvider with ChangeNotifier {
FirebaseAuth _auth;
Auth _userAuth = Auth();
UserServices userServices = UserServices();
User? _user;
GoogleSignIn _googleSignIn = GoogleSignIn();
Status _status =
Status.uninitialized; //when the instance of the class is created
UserProvider.initialize() : _auth = FirebaseAuth.instance {
//subscribing to stream to listen to changes in user status
_auth.authStateChanges().listen(
(user) {
_onStatusChanged(user);
},
);
}
Status get status => _status;
User? get user => _user;
Future<bool> signUp(String username, String email, String password) async {
try {
//change the status of the user
_status = Status.authenticating;
//notify the listeners
notifyListeners();
// UserCredential credential =
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
Map<String, dynamic> values = {
'name': username,
'email': email,
'id': user!.uid,
};
userServices.createUser(values);
_status = Status.authenticated;
notifyListeners();
return true;
} catch (e) {
_status = Status.unauthenticated;
notifyListeners();
print(e.toString());
return false;
}
}
Future signOut() async {
print('entered signOut');
await _auth.signOut();
_status = Status.unauthenticated;
notifyListeners();
print('Exiting signOut');
return Future.delayed(Duration
.zero); //duration to return is set to zero (can work without it)
}
Future<void> _onStatusChanged(User? user) async {
if (user == null) {
_status = Status.unauthenticated;
} else {
_user = user;
_status = Status.authenticated;
}
notifyListeners();
}
}
The way I am navigating on state change is-
class ScreenController extends StatelessWidget {
const ScreenController({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<UserProvider>(context);
switch (user.status) {
case Status.uninitialized:
return SplashScreen();
case Status.unauthenticated:
return LoginScreen();
case Status.authenticating:
return LoadingScreen();
case Status.authenticated:
return HomePage();
default:
return LoginScreen();
}
}
}
Problem: in my SignUp page, I call the signUp method of the UserProvider class to signUp the user.
I expect the signup page gets destroyed and home page appears when user is created and sign up procedure is complete.
What I get: Home Page is built but the sign up page is not destroyed and remains on the screen unless I press the back button.
Sign Up button -
// minWidth: MediaQuery.of(context).size.width.,
child: Text(
'Sign Up and Register',
textAlign: TextAlign.center,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
),
),
onPressed: () async {
if (!await provider.signUp(
_nameTextController.text,
_emailTextController.text,
_passwordTextController.text)) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Sign In Failed')));
}
// Navigator.pop(context);
// Navigator.pushReplacement(
// context,
// MaterialPageRoute(
// builder: (context) => HomePage()));
},
),
Also- my Sign Up class is wrapped with Consumer.
I am using the provider package for navigation. The problem I am facing is: that Debug mode shows that the login page is disposed of when the user logs in and Home Page appears. But when I sign in from the Sign Up page, the page is not disposed and Home Page is constructed below it.
Please help!!
You have to use Navigator.popAndPushNamed(context) or Navigator.pushReplacementNamed(context). This is work perfect in your scenario both have the same output difference is only animations. For signUp/signIn/signOut this is ideal way.

Flutter Session stores data but can't make decisions based off it

I'm using FlutterSession to store phone number/email (based on user's choice) as a method of logging into the app. The session works fine in case of email but when I store and retrieve phone number, it shows me the snapshot has no data.
This is the method where I'm setting the session value for the phone number:
void phoneSignIn() async {
if (phoneNumber == null || phoneNumber.length < 10) {
print('Please enter a valid phone number!');
}
else {
await fillers.session.set("phoneNum", phoneNumber);
print('Phone number is: '+ phoneNumber);
setState(() {
phoneSignedIn = true;
});
var sessionCheck = await fillers.session.get("phoneNum");
print("Session value: $sessionCheck");
print(phoneSignedIn);
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => OnboardOne()));
}
}
In the above code, I've made sure using print statements that the session is storing phone number value and also that the phoneSignedIn bool is set to true.
This is the method where I'm setting session value for email:
void signIn() async {
response = await Dio().post(url,
data: {
"email": emailVal,
"password": passwordVal
}
);
if (response.statusCode == 200) {
await fillers.session.set('email', emailVal);
await fillers.session.set('password', passwordVal);
print('Sign In successful!');
print('Email: $emailVal');
print('Password: $passwordVal');
Navigator.pushReplacement(context, MaterialPageRoute(builder: (context) => OnboardOne()));
}
else
{
print('Exited with statuscode ${response.statusCode}');
print('Email: $emailVal');
print('Password: $passwordVal');
}
}
This is my firstpage where I decide on which page to go based on whether the user has logged in or not:
class _FirstPageState extends State<FirstPage> {
#override
Widget build(BuildContext context) {
return Material(
child: FutureBuilder(
future: phoneSignedIn ? session.get("phoneNum") : session.get('email'),
builder: (context, snapshot) {
return snapshot.hasData ? Dashboard() : SignUpIn();
}
),
);
}
}
As you can see, I've done nothing different in the email sign in (except using a backend api to auth the email). Ultimately, I'm using the same technique to store both the email and phone number. But when I've signed in using phone number and then reopen the app, the app goes to the SignUpIn() page, when it should actually go to the Dashboard() page.
Also, on the dashboard, there's a button "Sign Out", on pressing which, the session values are cleared.
What am I doing wrong?

Auth data resets whenever App is rebuild in Flutter

I have an Auth method setup that extends ChangeNotifier to give login and store tokens and other details.
and in my main.dart file I am calling a multi provider to get all providers, inside multi provider I have a Consumer as a child which decides which page to call as default. i.e if auth.isAuth returns true then MainPage.dart will be called or AuthScreen.dart.
Login works fine and everything else works fine.
But the issue which I am facing is, Whenever is reload or rebuild my application the auth data becomes null.
Any kind of help will be appreciated. Thanks in Advance.
main.dart
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(
value: Auth(),
),
ChangeNotifierProvider.value(
value: Cart(),
),
ChangeNotifierProvider.value(
value: AddressBook(Auth().token),
),
ChangeNotifierProvider.value(
value: Orders(),
),
],
child: Consumer<Auth>(
builder: (ctx, auth, _) => MaterialApp(
title: 'APP NAME',
theme: ThemeData(
fontFamily: 'Roboto',
primaryColor: Colors.white,
primaryColorDark: Colors.white,
backgroundColor: Colors.white),
home: auth.isAuth ? MainPage() : AuthScreen(),
routes: {
MainPage.routeName: (ctx) => MainPage(),
CartScreen.routeName: (ctx) => CartScreen(),
OrderScreen.routeName: (ctx) => OrderScreen(),
EditAddressScreen.routeName: (ctx) => EditAddressScreen(),
}),
),
);
}
auth.dart
class Auth extends ChangeNotifier {
String _token;
String _refresh_token;
DateTime _expiryDate;
int _userId;
bool get isAuth {
return _token != null;
}
String get token {
if (_expiryDate != null &&
_expiryDate.isAfter(DateTime.now()) &&
_token != null) {
return _token;
}
return null;
}
int get userId {
return _userId;
}
Future<void> _authenticate(String email, String password, String urlSegment) async {
final url = 'http://192.168.1.120:8080/oauth/token';
Map<String, dynamic> body = {'grant_type': 'password', 'username': email, 'password': password};
var parts = [];
parts.add('${Uri.encodeQueryComponent("grant_type")}=${Uri.encodeQueryComponent("password")}');
parts.add('${Uri.encodeQueryComponent("username")}=${Uri.encodeQueryComponent(email)}');
parts.add('${Uri.encodeQueryComponent("password")}=${Uri.encodeQueryComponent(password)}');
var formData = parts.join('&');
try {
final response = await http.post(
url,
body: formData,
headers: {
'content-type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic XXXXXXXXXXXXXXXXXXXXX',
}
);
final responseData = json.decode(response.body);
print(response.statusCode);
if(response.statusCode != 200){
throw HttpException(responseData['error']['error_description']);
}else{
_token = responseData['access_token'];
_userId = responseData['customerId'];
_refresh_token = responseData['refresh_token'];
_expiryDate = DateTime.now().add(
Duration(
seconds: responseData['expires_in'],
),
);
notifyListeners();
}
if (responseData['error'] != null) {
throw HttpException(responseData['error']['error_description']);
}
} catch (error) {
throw error;
}
}
Future<void> signup(String email, String password) async {
return _authenticate(email, password, 'signUp');
}
Future<void> login(String email, String password) async {
return _authenticate(email, password, 'signInWithPassword');
}
}
At the moment it looks like you are storing OAuth tokens in memory, so they are dropped upon every restart, and this provides poor usability.
MOBILE TOKEN STORAGE
OAuth tokens can be saved to secure encrypted storage to improve usability, perhaps using this Flutter library. The encryption used should be private to your app, so that no other app can use the tokens.
STRUCTURING YOUR CODE
I quite like how you've structured your code, where the rest of the app just calls Auth.dart's getToken method when calling a Web API. It looks like your Auth.dart source file needs to do more work to resolve your problem.
EXAMPLE OF MINE
This code is in Swift and uses a different flow, with AppAuth libraries. However, the general design pattern can be applied in any language, including Flutter.
Token Storage Module
OAuth Interface used by the rest of the App
OAuth Implementation uses Token Storage
API Client that calls GetToken
View that Calls an API

What to do after login but before essential user data gets returned in flutter

I have a flutter app that uses firebase for authentication.
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return HomeScreen();
} else {
return LoginScreen();
}
},
);
so basically as soon as user authenticates, this will take to the home screen. but i dont want that, i want to wait on another piece of data from my api, say onboarded, if onboarded == true then HomeScreen otherwise OnboardingScreen.
So the tricky part is before that data comes in, i want to stay on the login screen. how do i have the user stay on the LoginScreen? it seems the best way is to have another stream listen to the onboardedLoading and combine these 2 streams?
Make a dart file auth.dart, in that, paste this line of code,
final FirebaseAuth auth = FirebaseAuth.instance;
Future<FirebaseUser> handleSignInEmail(String email, String password) async {
AuthResult result = await auth.signInWithEmailAndPassword(email: email, password: password);
final FirebaseUser user = result.user;
assert(user != null);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await auth.currentUser();
assert(user.uid == currentUser.uid);
print('signInEmail succeeded: $user');
return user;
}
Future<FirebaseUser> handleSignUp(email, password) async {
AuthResult result = await auth.createUserWithEmailAndPassword(email: email, password: password);
final FirebaseUser user = result.user;
assert (user != null);
assert (await user.getIdToken() != null);
return user;
}
In your login/ Sigup page, create an instance of my auth class:
var authHandler = new Auth();
In the onPressed () of your button
onPressed: () {
if(onboardedLoading==true){
authHandler.handleSignInEmail(emailController.text, passwordController.text)
.then((FirebaseUser user) {
Navigator.push(context, new MaterialPageRoute(builder: (context) => HomeScreen()));
}).catchError((e) => print(e));
}
}else{
//Show An Animation, such as CirclularProgressIndicator.
}
You can design a simple loading screen, then use Navigator.pushAndRemoveUntil() to whichever screen you need after getting AuthState.

How to do autologin with three diffrent userTypes in flutter and firebase?

I have this app that do login with firebase auth and firestore to get the userType, This code is written obviously in the login page, What I want to add is autologin ASA the app runs which firebase offers with the correct userType So the first proplem how to transfer the email value to the main.dart page as I search in the firestore with the email to get the userType, Second proplem is that When I tried to do auto login in the login page with three different userTypes It does login but not auto login
CODE :
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then(
(result) {
if (result != null) {
if (userType == 'userType1') {
Navigator.pushReplacementNamed(context, '/userType1page');
}
if (userType == 'userType2') {
Navigator.pushReplacementNamed(context, '/userType2page');
}
if (userType == 'userType3') {
Navigator.pushReplacementNamed(context, '/userType3page');
}
}
So Here It gets the user But no auto login, what I observed that When U remove the other IFs inside the big if and do 1 Navigation It works So don't know what to do, Please Help me I asked three questions before and didn't get an answer.
PS : NEW TO FLUTTER :)
#FLUTTER_FOREVER
Getting user Data from firestore:
void getUserData() async {
try {
firestoreInstance
.collection('Users')
.document(usernameController.text)
.get()
.then((value) {
setState(() {
email = (value.data)['email'];
password = (value.data)['password'];
gender = (value.data)['gender'];
username = (value.data)['username'];
userType = (value.data)['userType'];
});
});
} catch (e) {
print(e.toString);
}
}
Logining in :
void login() async {
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
firebaseAuth
.signInWithEmailAndPassword(
email: emailController.text, password: passwordController.text)
.then((result) {
{
if (userType == 'Student') {
Navigator.pushReplacementNamed(context, '/StudentsPage');
} else if (userType == 'Teacher') {
Navigator.pushReplacementNamed(context, '/TeacherPage');
} else if (userType == 'Admin') {
Navigator.pushReplacementNamed(context, '/AdminPage');
} else {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Error'),
content: Text(
'Please make sure that you have an internet connection '),
actions: [
FlatButton(
child: Text("Ok"),
onPressed: () {
Navigator.of(context).pop();
},
)
],
);
},
);
}
}
I found the answer I must identify the var userType inside the initState and It worked by using the getUserData() function but I had a problem I can't use usernameController because It's a var and I didn't defined it so any idea how to get the userType with the document reference usernameController Which I can't identify X-( 'R.I.P #omardeveloper'