My flutter app logs out after hot restart,Cannot make app stay logged in even after restart - flutter

i implemented login after onboard screen and logged in but unfortunately each time i restart my app i am brought back to the welcome page.i also want to implement a splash screen but don't know how to add it in the MyApp material root widget since i have returned the introduction/on boarding page and set the welcome page to come after it.I also want to find a way to check auth state after one time introduction/on boarding screen. I'll appreciate
int initScreen;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
//on Board Screen Intialization
SharedPreferences prefs = await SharedPreferences.getInstance();
initScreen = prefs.getInt('initScreen');
await prefs.setInt("initScreen", 1);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Introduction(),
initialRoute: initScreen == 0 || initScreen == null ? 'onboard' : 'home',
routes: {
'home': (context) => WelcomeScreen(),
'onboard': (context) => Introduction()
},
);
}
}
class Login extends StatefulWidget {
const Body({Key key}) : super(key: key);
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
TextEditingController _email = TextEditingController();
TextEditingController _password = TextEditingController();
isLoading = false;
#override
Widget build(BuildContext context) {
Size size = MediaQuery.of(context).size;
return Scaffold(
body: Background(
child: isLoading == false
? SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"LOGIN",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 20),
),
SizedBox(height: size.height * 0.03),
SvgPicture.asset(
"assets/icons/login.svg",
height: size.height * 0.35,
),
SizedBox(height: size.height * 0.03),
TextFieldContainer(
child: TextFormField(
controller: _email,
cursorColor: kPrimaryColor,
decoration: InputDecoration(
icon: Icon(
Icons.email,
color: kPrimaryColor,
),
hintText: "Email",
border: InputBorder.none,
),
),
),
TextFieldContainer(
child: TextFormField(
controller: _password,
obscureText: true,
cursorColor: kPrimaryColor,
decoration: InputDecoration(
hintText: "Password",
icon: Icon(
Icons.lock,
color: kPrimaryColor,
),
suffixIcon: Icon(
Icons.visibility,
color: kPrimaryColor,
),
border: InputBorder.none,
),
),
),
RoundedButton(
text: "LOGIN",
press: () {
setState(() {
isLoading = true;
});
AuthClass()
.SignIn(
_email.text.trim(), _password.text.trim())
.then((value) {
if (value == 'Welcome') {
setState(() {
isLoading = false;
});
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: (context) => Home()),
(route) => false);
} else {
setState(() {
isLoading = false;
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(value)));
}
});
},
),
SizedBox(height: size.height * 0.03),
AlreadyHaveAnAccountCheck(
press: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return SignUpScreen();
},
),
);
},
),
],
),
)
: Center(
child: CircularProgressIndicator(),
)));
}
}
class AuthClass {
FirebaseAuth _auth = FirebaseAuth.instance;
//Create account
Future<String> createAcount(String email, String password) async {
try {
await _auth.createUserWithEmailAndPassword(
email: email, password: password);
return "Account Successifuly Created";
} on FirebaseAuthException catch (e) {
if (e.code == 'weak-password') {
return 'The password provided is too weak.';
} else if (e.code == 'email-already-in-use') {
return 'The account already exists for that email';
}
} catch (e) {
return 'Error ocurred';
}
}
//SignIn User
Future<String> SignIn(String email, String password) async {
try {
await _auth.signInWithEmailAndPassword(email: email, password: password);
return 'Welcome';
} on FirebaseAuthException catch (e) {
if (e.code == 'user-not-found') {
return 'No user found for that email.';
} else if (e.code == 'wrong-password') {
return 'Wrong password provided for that user.';
}
}}
//ResetPAssword
//SignOut
void signOut() {
_auth.signOut();
}
//try
Future<String> Reset(
String email,
) async {
try {
await _auth.sendPasswordResetEmail(
email: email,
);
return 'Email sent';
} catch (e) {
return 'Error Occured';
}
}
}

You need a "pre-login" screen, I usually call it Loading anyway, and on that screen I just bring a Circular Progress, just to give feedback to the user who is loading something.
Meanwhile, in initState, call a validate user method, where you check if you have any users logged in (of course, you should store this in SQLite, Shared Preferences or something like that). Depending on the return, you redirect to Login or to your Home.

Related

Flutter: How to Call a Function that is in a Stateful Widgets State Class, that depends on the initState, from another external Class

I am unsure if this question has been asked before, but I was unable to find an answer to it.
I have a stateful widget for my signin page, which uses the flutter_toast package for error handling (incorrect password, email already in use, etc.). When using flutter_toast, in order to make a custom toast, I have to use the initState function of the signin page.
I also have another class, in a separate file, in which I am trying to move all of my Firebase Auth services into (this is where I am needing to call the showToast function, which is declared in the SigninState class).
I have been having trouble figuring out how to get the showToast function to be defined in the AuthService class, so that I will be able to call it there.
Here is my Signin stateful widget class:
class Signin extends StatefulWidget {
const Signin({Key? key}) : super(key: key);
#override
_SigninState createState() => _SigninState();
}
class _SigninState extends State<Signin> {
FToast? fToast;
AuthService authService = AuthService();
#override
void initState() {
super.initState();
fToast = FToast();
fToast!.init(context);
}
...
// build function{
// in here, I call "authService.signInAction()", which needs to call "showToast()"
// }
...
}
showToast(String msg, context) {
bool smallPhone = false;
double width = MediaQuery.of(context).size.width;
if (width <= 370) smallPhone = true;
Widget toast = Container(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
color: Colors.redAccent,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
width: 10.0,
),
Text(
msg,
style:
TextStyle(fontSize: smallPhone ? 13 : 15, color: Colors.white),
),
],
),
);
fToast!.showToast(
child: toast,
gravity: ToastGravity.CENTER,
toastDuration: const Duration(seconds: 2),
);
}
...
...
} // closing tag for _SigninState class
Here is my AuthService class:
class AuthService {
FirebaseAuth auth = FirebaseAuth.instance;
Signin signin = const Signin();
UserModel _userFromFirebaseUser(User user) {
return UserModel(id: user.uid);
}
Stream<UserModel> get user {
return auth.authStateChanges().map((user) => _userFromFirebaseUser(user!));
}
Future signInAction(
String email, String password, BuildContext context) async {
try {
User user = (await auth.signInWithEmailAndPassword(
email: email, password: password)) as User;
_userFromFirebaseUser(user);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const MyHomePage(title: "Twitter")),
);
} on FirebaseAuthException catch (e) {
if (e.code == 'wrong-password') {
signin.showToast("The password provided is incorrect.", context); // <-- This does not work
} else if (e.code == 'user-not-found') {
signin.showToast("An account with that email does not exist.", context); // <-- This does not work
}
} catch (e) {
signin.showToast("An unexpected error has occurred.", context); // <-- This does not work
}
}
}
This is the error that I am getting:
The method 'showToast' isn't defined for the type 'Signin'.
Try correcting the name to the name of an existing method, or defining a method named 'showToast'
it may possible but you can easy different make it instead of your approach.
set a static String message = "";
and..
on FirebaseAuthException catch (e) {
if (e.code == 'wrong-password') {
message = "The password provided is incorrect.";
}
finally you can use it in Auth button..
showToast() {
bool smallPhone = false;
double width = MediaQuery.of(context).size.width;
if (width <= 370) smallPhone = true;
Widget toast = Container(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(25.0),
color: Colors.redAccent,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(
width: 10.0,
),
Text(
AuthService.message,
style:
TextStyle(fontSize: smallPhone ? 13 : 15, color: Colors.white),
),
],
),
);
maybe it will help
You can use callback but i don't think this is an elegant solution.
signInAction(
String email, String password, {required void Function(String message) onError}) async {
try {
User user = (await auth.signInWithEmailAndPassword(
email: email, password: password)) as User;
_userFromFirebaseUser(user);
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const MyHomePage(title: "Twitter")),
);
} on FirebaseAuthException catch (e) {
if (e.code == 'wrong-password') {
onError("The password provided is incorrect.");
} else if (e.code == 'user-not-found') {
onError("An account with that email does not exist.");
}
} catch (e) {
onError("An unexpected error has occurred.");
}
}
and then you can call
authService.signInAction(email, password,
onError:(message){
showToast(message, context);
}
)

Snackbar showing twice due to stacked screen Flutter, How can i avoid it?

I don't want to show Snackbar on stacked screen. When user taps on Signup from LoginScreen. Then, SignUpScreen stacked over LoginScreen. but the problem is both implements same ProviderListener and therefore, It shows Snackbar multiple times. How can I avoid it? , How can I make sure that, If current route is SignUp then show Snackbar
UserAuthService.dart
import 'package:notifications/domain/repository/firebase_repository/firebase_user_repo.dart';
import 'package:notifications/domain/services/auth_service/all_auth_builder.dart';
import 'package:notifications/export.dart';
import 'package:notifications/resources/local/local_storage.dart';
enum AuthenticationStatus {
loading,
error,
success,
}
class UserAuthService extends ChangeNotifier {
final authBuilder = AllTypeAuthBuilder();
String? _errorMsg, _sessionID;
AuthenticationStatus _status = AuthenticationStatus.loading;
EmailLinkAuthenticationRepo? _repo;
String? get errorMsg => this._errorMsg;
void get _setDefault {
_errorMsg = null;
//_sessionID = null;
}
String? get sessionID {
return LocallyStoredData.getSessionID();
}
void logOut() {
return LocallyStoredData.deleteUserKey();
}
Future<bool> userExists(String userID) async {
final isExists = await authBuilder.checkUserExists(userID);
return isExists ? true : false;
}
Future<bool> login() async {
_setDefault;
try {
await authBuilder.login();
return true;
} on BaseException catch (e) {
log("Exception $e");
_errorMsg = e.msg;
notifyListeners();
return false;
}
}
Future<bool> register(String username, String email, String password) async {
_setDefault;
try {
await authBuilder.register(username, email, password);
return true;
} on BaseException catch (e) {
log("Exception $e");
_errorMsg = e.msg;
notifyListeners();
return false;
}
}
Future<bool> signIn(String userID, String password) async {
_setDefault;
try {
await authBuilder.signIn(userID, password);
return true;
} on BaseException catch (e) {
log("Exception ${e.msg}");
_errorMsg = e.msg;
notifyListeners();
}
return false;
}
void loginWithEmail(String email) async {
_setDefault;
try {
_repo = await authBuilder.loginWithEmail(email);
_repo!.onLinkListener(
onSuccess: _onSuccess,
onError: _onError,
);
} on BaseException catch (e) {
log("Exception ${e.msg}");
_errorMsg = e.msg;
}
notifyListeners();
}
Future<bool> _onSuccess(PendingDynamicLinkData? linkData) async {
_setDefault;
try {
log("OnLinkAuthenticate");
await _repo!.onLinkAuthenticate(linkData);
return true;
} on BaseException catch (e) {
log("Error onSucess: $e");
_errorMsg = e.msg;
notifyListeners();
}
return false;
}
Future<dynamic> _onError(OnLinkErrorException? error) async {
log("Error $error in Link");
}
Future<void> tryTo(Function callback) async {
try {
await callback();
} on BaseException catch (e) {
_errorMsg = e.msg;
}
}
}
LoginScreen.dart
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _userIDController = TextEditingController(),
_passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool? isAuthenticated;
#override
initState() {
super.initState();
}
#override
void dispose() {
_userIDController.dispose();
_passwordController.dispose();
super.dispose();
}
_onGoogleLogin() async {
context.read(loginPod).login();
}
_onLoginButtonTap() {
networkCheckCallback(context, () async {
if (_formKey.currentState!.validate()) {
WidgetUtils.showLoaderIndicator(context, 'Loading...');
final isSignedIn = await context
.read(loginPod)
.signIn(_userIDController.text, _passwordController.text);
Navigator.pop(context);
if (isSignedIn) Beamer.of(context).beamToNamed(Routes.home);
}
});
}
_resetAuthenticateState() {
if (isAuthenticated != null)
setState(() {
isAuthenticated = null;
});
}
onUsernameChange(String? value) async {
final error = await hasNetworkError();
if (_userIDController.text.isNotEmpty && error == null) {
isAuthenticated = await context.read(loginPod).userExists(value!);
setState(() {});
return;
}
_resetAuthenticateState();
}
onPasswordChange(String? value) {
//Code goes here....
}
loginWith(BuildContext context, LoginType type) {
switch (type) {
case LoginType.emailLink:
Beamer.of(context).beamToNamed(Routes.email_link_auth);
break;
case LoginType.idPassword:
Beamer.of(context).beamToNamed(Routes.login_id_pass);
break;
case LoginType.googleAuth:
Beamer.of(context).beamToNamed(Routes.login_with_google);
break;
case LoginType.unknown:
Beamer.of(context).beamToNamed(Routes.register);
break;
}
}
Future<bool> _onBackPress(_) async {
return await showDialog<bool>(
context: _,
builder: (context) {
return AlertDialog(
title: Text("Do you want to exit?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("OK")),
TextButton(
onPressed: () {
Beamer.of(_).popRoute();
},
child: Text("Cancel"))
],
);
}) ??
false;
}
_onLoginStatus(BuildContext _, UserAuthService service) {
if (service.errorMsg != null)
_.showErrorBar(
content: Text(
"WithLogin" + service.errorMsg!,
style: TextStyle(fontSize: 12.sp),
));
}
#override
Widget build(BuildContext _) {
return ProviderListener(
onChange: _onLoginStatus,
provider: loginPod,
child: WillPopScope(
onWillPop: () => _onBackPress(_),
child: Scaffold(
body: SingleChildScrollView(
child: SizedBox(height: 1.sh, child: _buildLoginScreen())),
),
),
);
}
Widget _buildLoginScreen() {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
//_buildVrtSpacer(60),
_buildHeading(),
//_buildVrtSpacer(30),
_buildForm(),
//_buildVrtSpacer(30),
_buildIconButtons(),
_buildSignUpButton(),
],
);
}
BoldHeadingWidget _buildHeading() =>
BoldHeadingWidget(heading: AppStrings.login);
ResponsiveVrtSpacer _buildVrtSpacer(double value) =>
ResponsiveVrtSpacer(space: value);
Widget _buildForm() {
return CustomForm(
formKey: _formKey,
child: Column(
children: [
_buildUsernameField(),
_buildVrtSpacer(10),
_buildPasswordField(),
_buildForgetPassword(),
_buildLoginButton(),
],
),
);
}
Widget _buildPasswordField() {
return CustomTextFieldWithLabeled(
controller: _passwordController,
label: AppStrings.password,
hintText: AppStrings.password,
onValidate: (String? value) =>
(value!.isEmpty) ? AppStrings.emptyPasswordMsg : null,
obscureText: true,
onChange: onPasswordChange,
icon: CupertinoIcons.lock);
}
Widget _buildUsernameField() {
return CustomTextFieldWithLabeled(
controller: _userIDController,
label: AppStrings.usernameOrEmail,
hintText: AppStrings.usernameOrEmail1,
icon: CupertinoIcons.person,
onChange: onUsernameChange,
onValidate: (String? value) =>
(value!.isEmpty) ? AppStrings.emptyUserIDMsg : null,
suffixIcon: isAuthenticated == null
? null
: (isAuthenticated!
? CupertinoIcons.checkmark_alt_circle_fill
: CupertinoIcons.clear_circled_solid),
suffixColor: isAuthenticated == null
? null
: (isAuthenticated! ? Colors.green : Styles.defaultColor));
}
Widget _buildIconButtons() {
return Column(
children: [
Text("Or", style: TextStyle(fontSize: 14.sp)),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: 10),
_buildIconButton(
iconPath: 'assets/icons/email-icon.svg', onTap: () {}),
const SizedBox(width: 8),
_buildIconButton(
iconPath: 'assets/icons/icons8-google.svg',
onTap: _onGoogleLogin),
],
),
],
);
}
Widget _buildIconButton(
{required String iconPath, required VoidCallback onTap}) {
return GestureDetector(
onTap: onTap,
child: SvgPicture.asset(
iconPath,
height: 30.sp,
width: 30.sp,
));
}
Widget _buildSignUpButton() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
AppStrings.dontHaveAccount,
style: TextStyle(color: Colors.black54, fontSize: 14.sp),
),
const SizedBox(height: 5),
CustomTextButton(
title: "Sign Up",
onPressed: () => Beamer.of(context).beamToNamed(Routes.register)),
],
);
}
Widget _buildLoginButton() {
return DefaultElevatedButton(
onPressed: _onLoginButtonTap,
title: AppStrings.login,
);
}
Widget _buildForgetPassword() {
return Align(
alignment: Alignment.centerRight,
child: TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.all(Color(0x11000000)),
foregroundColor: MaterialStateProperty.all(Color(0x55000000)),
),
onPressed: () {},
child: Text("Forget Password?"),
),
);
}
}
SignUpScreen.dart
class SignUp extends StatefulWidget {
const SignUp({Key? key}) : super(key: key);
#override
_SignUpState createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController(text: "hammad11"),
_emailController = TextEditingController(text: "mason#gmail.com"),
_passwordController = TextEditingController(text: "ha11"),
_confirmPassController = TextEditingController(text: "ha11");
_onValidate(String? value, ValidationType type) {
switch (type) {
case ValidationType.username:
if (value!.isNotEmpty && value.length < 8)
return "Username Must Be 8 characters long";
else if (value.isEmpty) return "Username required";
return null;
case ValidationType.email:
if (value!.isEmpty)
return "Email required";
else if (!value.isEmail) return "Please enter a Valid Email";
return null;
case ValidationType.password:
if (value!.isEmpty)
return "Password required";
else if (value.isAlphabetOnly || value.isNumericOnly)
return "Password must be AlphaNumeric";
return null;
case ValidationType.confirmPassword:
if (value!.isEmpty)
return "Confirm Password required";
else if (value != _passwordController.text)
return "Password doesn't match";
return null;
}
}
_onRegister() async {
//Clears any snackbar opened due to Error or Multiple clicks
//ScaffoldMessenger.of(context).clearSnackBars();
log("SignUp -> _onRegisterTap ");
if (_formKey.currentState!.validate()) {
WidgetUtils.showLoaderIndicator(context, "Please wait! Loading.....");
final isLoggedIn = await context.read(loginPod).register(
_usernameController.text,
_emailController.text,
_passwordController.text,
);
await Beamer.of(context).popRoute();
if (isLoggedIn) Beamer.of(context).beamToNamed(Routes.login);
} else
log("Form Input Invalid");
}
_onChanged(_, UserAuthService service) async {
if (service.errorMsg != null) WidgetUtils.snackBar(_, service.errorMsg!);
// if (!service.isLoading) await Beamer.of(context).popRoute();
// if (service.taskCompleted) {
// log("User Added Successfully");
// Beamer.of(context).popToNamed(
// Routes.login_id_pass,
// replaceCurrent: true,
// );
// } else {
// WidgetUtils.snackBar(context, service.errorMsg!);
// }
}
_alreadyHaveAccount() => Beamer.of(context).popToNamed(Routes.main);
#override
Widget build(BuildContext context) {
return ProviderListener(
provider: loginPod,
onChange: _onChanged,
child: Scaffold(
body: LayoutBuilder(builder: (context, constraints) {
return SingleChildScrollView(
child: SizedBox(
height: 1.sh,
child: Column(
children: [
_buildSpacer(50),
BoldHeadingWidget(heading: "Sign Up"),
CustomForm(
child: Column(
children: [
CustomTextFieldWithLabeled(
label: "Username",
hintText: "Type Username",
onValidate: (value) =>
_onValidate(value, ValidationType.username),
controller: _usernameController,
icon: CupertinoIcons.person),
CustomTextFieldWithLabeled(
label: "Email",
hintText: "Type Email",
controller: _emailController,
onValidate: (value) =>
_onValidate(value, ValidationType.email),
icon: CupertinoIcons.envelope),
CustomTextFieldWithLabeled(
label: "Password",
hintText: "Type Password",
controller: _passwordController,
onValidate: (value) =>
_onValidate(value, ValidationType.password),
icon: CupertinoIcons.lock),
CustomTextFieldWithLabeled(
label: "Confirm Password",
hintText: "Type Confirm Password",
onValidate: (value) => _onValidate(
value, ValidationType.confirmPassword),
controller: _confirmPassController,
icon: CupertinoIcons.lock),
Row(
children: [
Checkbox(
value: true,
onChanged: (value) {},
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
),
Flexible(
child: Text(
"I accept Terms & Conditions and the Privacy Policy",
style: TextStyle(fontSize: 13.sp),
),
),
],
),
_buildSpacer(10),
DefaultElevatedButton(
title: "Sign Up",
onPressed: _onRegister,
),
],
),
formKey: _formKey,
),
const SizedBox(height: 10),
CustomTextButton(
onPressed: () => Beamer.of(context)
.popToNamed(Routes.login, stacked: false),
title: AppStrings.alreadyHaveAccount,
),
// Spacer(flex: 2),
],
),
),
);
}),
),
);
}
ResponsiveVrtSpacer _buildSpacer(double value) =>
ResponsiveVrtSpacer(space: value.h);
}
showsnackbar(String message, context) {
final snackbar = SnackBar(
content: Text(message),
duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(snackbar);
}
Use this function to call snackbar it will remove the current snackbar and will only show the latest.

Failed assertion: line 5070 pos 12: '<optimized out>': is not true

After a user successfully logins in and saving the state, I want to hide the login screen and just load the home screen, but I end up with the error
The following assertion was thrown building
Navigator-[GlobalObjectKey
_WidgetsAppState#6686e](dirty, dependencies: [UnmanagedRestorationScope, HeroControllerScope], state:
NavigatorState#c7e9f(tickers: tracking 1 ticker)):
'package:flutter/src/widgets/navigator.dart': Failed assertion: line
5070 pos 12: '': is not true.
What is the right way of hiding the login screen when the token is still valid, and just load the home screen?
my code
Main.dart
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'What',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
scaffoldBackgroundColor: Palette.scaffold,
),
// home: SignIn(),
routes: {
//Homepage and being controled by PagesProvider
'/': (context) => SignIn(),
'nav': (context) => NavScreen(),
// add all routes with names here
},
);
}
}
my signin.dart
class SignIn extends StatefulWidget {
const SignIn({Key key}) : super(key: key);
#override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
ProgressDialog progressDialog;
MsalMobile msal;
bool isSignedIn = false;
bool isLoading = true;
#override
void initState() {
super.initState();
MsalMobile.create('assets/auth_config.json', authority).then((client) {
setState(() {
msal = client;
});
refreshSignedInStatus();
});
}
/// Updates the signed in state
refreshSignedInStatus() async {
bool loggedIn = await msal.getSignedIn();
if (loggedIn) {
isSignedIn = loggedIn;
if (isSignedIn) {
dynamic data = await handleGetAccount();
dynamic token = await handleGetTokenSilently();
dynamic result = token;
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
sharedPreferences.get("username");
sharedPreferences.get("token");
print('access token (truncated): ${result.accessToken}');
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => NavScreen(),
),
);
}
// Remaining code for navigation
}
}
/// Gets a token silently.
Future<dynamic> handleGetTokenSilently() async {
String authority = "https://login.microsoftonline.com/$TENANT_ID";
final result = await msal.acquireTokenSilent([SCOPE], authority);
if (result != null) {
// print('access token (truncated): ${result.accessToken}');
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
sharedPreferences.setString("token", result.accessToken);
return result;
} else {
print('no access token');
return null;
}
}
/// Signs a user in
handleSignIn() async {
await msal.signIn(null, [SCOPE]).then((result) {
// ignore: unnecessary_statements
refreshSignedInStatus();
}).catchError((exception) {
if (exception is MsalMobileException) {
logMsalMobileError(exception);
} else {
final ex = exception as Exception;
print('exception occurred');
print(ex.toString());
}
});
}
logMsalMobileError(MsalMobileException exception) {
print('${exception.errorCode}: ${exception.message}');
if (exception.innerException != null) {
print(
'inner exception = ${exception.innerException.errorCode}: ${exception.innerException.message}');
}
}
/// Signs a user out.
handleSignOut() async {
try {
print('signing out');
await msal.signOut();
print('signout done');
refreshSignedInStatus();
} on MsalMobileException catch (exception) {
logMsalMobileError(exception);
}
}
/// Gets the current and prior accounts.
Future<dynamic> handleGetAccount() async {
// <-- Replace dynamic with type of currentAccount
final result = await msal.getAccount();
if (result.currentAccount != null) {
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
sharedPreferences.setString("username", result.currentAccount.username);
//print(result.currentAccount.username);
return result.currentAccount;
} else {
print('no account found');
return null;
}
}
#override
Widget build(BuildContext context) {
progressDialog = ProgressDialog(context, type:ProgressDialogType.Normal, isDismissible: false, );
return MaterialApp(
home: new Scaffold(
body: Builder(
builder: (context) => Stack(
fit: StackFit.expand,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Image.asset('assets/landing.webp',
fit: BoxFit.fill,
color: Color.fromRGBO(255, 255, 255, 0.6),
colorBlendMode: BlendMode.modulate),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: 10.0),
Container(
width: 130.0,
child: Align(
alignment: Alignment.center,
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)),
color: Color(0xffffffff),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
FontAwesomeIcons.microsoft,
color: Color(0xFF01A6F0),
),
// Visibility(
// visible: !isSignedIn,
SizedBox(width: 10.0),
Text(
'Sign in',
style: TextStyle(
color: Colors.black, fontSize: 18.0),
),
// child: RaisedButton(
// child: Text("Sign In"),
// onPressed: handleSignIn,
// ),
// ),
],
),
onPressed: () => {
progressDialog.show(),
handleSignIn(),
progressDialog.hide()
})),
)
],
),
],
),
),
));
}
}
you should Navigate to the homePage only if the login is successful
because Naviagation.Pop is Equal to the Back button and user can do it manually
here is a better approach :
in main.dart add this :
routes: {
Homepage and being controled by PagesProvider
'nav': (context) => NavScreen(),
'home': (context) => HomePage(),
// add all routes with names here
},
in your refreshSignedInStatus() :
remove this :
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => NavScreen(),
),
);
add this :
Navigator.pushNamed(context, 'nav');

What is my mistake with Flutter Provider Architecture, since it keeps rebuilding infinintly?

What am I mistaking with provider architecture?
My EntryPoint:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<AuthProvider>(
create: (context) => AuthProvider()),
ChangeNotifierProvider<UserProvider>(
create: (context) => UserProvider()),
],
child: BdopsApp(),
),
);
}
class BdopsApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.teal,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
initialRoute: LoginView.routeName,
routes: {
HomepageView.routeName: (context) => HomepageView(),
LoginView.routeName: (context) => LoginView(),
},
);
}
}
First I have logged in with my log in credentials.
class LoginView extends StatelessWidget {
static const routeName = 'LoginView';
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final User _user = User();
#override
Widget build(BuildContext context) {
return Consumer<AuthProvider>(
builder: (_, data, __) => Scaffold(
appBar: CustomAppBar.getAppBar(
title: 'BDOPS',
subTitle: 'LOG IN',
),
drawer: SidveNavDrawer(),
body: Form(
key: _formKey,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FormInputTextBox(
labelText: 'Email',
hintText: 'example#domain.com',
validator: (value) {
if (value == null) {
return 'Login email not provided';
}
if (!RegExp(xEmailRegx).hasMatch(value)) {
return 'Not a valid email address';
}
},
onSaved: (value) {
_user.email = value.trim();
},
),
FormInputTextBox(
labelText: 'Password',
hintText: '* * * * *',
isPassword: true,
validator: (value) {
if (value == null) {
return 'Login password not provided';
}
if (value.toString().length < 6) {
return 'Minmum length is six charector';
}
},
onSaved: (value) {
_user.password = value.trim();
},
),
Divider(
height: 30.0,
),
RaisedButton(
color: Colors.deepOrange,
padding: EdgeInsets.symmetric(vertical: 10.0),
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
_formKey.currentState.save();
var res = await data.loginUser(_user);
if (res == true) {
AlertDialog(
title: Text('Login Success'),
);
} else {
AlertDialog(
title: Text('Login Failed'),
);
}
Navigator.pushReplacementNamed(
context, HomepageView.routeName);
},
child: Text(
'SUBMIT',
style: xHeaderText,
),
),
],
),
),
),
),
);
}
}
Than I navigated to the home page where on page load the screen should grab Auth user from AuthUser Provider's getAuthUser and self trigger a method to fetch logged in user's detail from the API with the userID.
class HomepageView extends StatelessWidget {
static const routeName = 'HomePageView';
#override
Widget build(BuildContext context) {
final userId = Provider.of<AuthProvider>(context).getAuthUser.id;
return Consumer<UserProvider>(
builder: (_, dataProvider, __) {
dataProvider.fetchAUser(userId);
return Scaffold(
appBar: CustomAppBar.getAppBar(title: 'BDOPS', subTitle: 'Home'),
drawer: SidveNavDrawer(),
body: Text(dataProvider.getSingelUser.email),
);
},
);
}
}
Doing this, my app at first throws an error and than keeps rebuilding and calling the API over and over again.
My Provider Classes Are:
class AuthProvider with ChangeNotifier {
User _authUser;
String _errorMessage;
AuthTokens _tokens;
Future<bool> loginUser(User user) async {
if (user.email == null || user.password == null) {
_setErrorMessage('Provide login credentials');
}
var resp = await APIService().loginUser(
email: user.email,
password: user.password,
);
if (resp.statusCode == 200) {
_setAuthToken(authTokensFromJson(resp.body));
var userFetched =
await UserProvider().fetchAUser(decodeJwt(_tokens.access)['user_id']);
if (userFetched != null) {
_setAuthUser(userFetched);
return true;
}
}
_setErrorMessage('Failed to login');
return false;
}
void _setAuthToken(value) {
_tokens = value;
}
void _setAuthUser(value) {
_authUser = value;
notifyListeners();
}
User get getAuthUser {
return _authUser;
}
void _setErrorMessage(value) {
_errorMessage = value;
notifyListeners();
}
String get getErrorMessage {
return _errorMessage;
}
}
class UserProvider with ChangeNotifier {
User _user;
Future<User> fetchAUser(userId) async {
var response = await APIService().getAUser(userId: userId);
if (response.statusCode == 200) {
setUser(userFromJson(response.body));
print('Called from UserProvider');
return _user;
}
return null;
}
void setUser(value) {
_user = value;
notifyListeners();
}
User get getSingelUser {
return _user;
}
}

Manually setting Flutter Validation Error

After validating a form and sending a request from flutter to the server backend: I want to set any potential error message from the server to be displayed in the original form. Preferably exactly like a validation error.
For instance:
Widget build(BuildContext context) {
...
TextFormField(
onFieldSubmitted: (value) => _signIn(),
validator: (input) {
if (input.length < 6)
return 'Your password is too short';
return null;
},
onSaved: (input) => _password = input,
decoration: InputDecoration(
labelText: 'Password',
),
obscureText: true,
)
...
}
Future<void> _signIn() async {
final formState = _formKey.currentState;
if (!formState.validate()) return;
formState.save();
try {
... // do fancy request stuff
} catch (e) {
// this is where I want to set the "validation" error
}
}
It's actually super simple and the validation error still works aswell.
String? _errorMsg;
Widget build(BuildContext context) {
...
TextFormField(
onFieldSubmitted: (value) => _signIn(),
validator: (input) {
if (input.length < 6)
// will set the errorText directly, no need for a variable here
return 'Your password is too short';
return null;
},
onSaved: (input) => _password = input,
decoration: InputDecoration(
labelText: 'Password',
errorText: _errorMsg,
),
obscureText: true,
)
...
}
Future<void> _signIn() async {
setState(() {
_errorMsg = null; // clear any existing errors
});
final formState = _formKey.currentState;
if (!formState.validate()) return;
formState.save();
try {
... // do fancy request stuff
} catch (e) {
setState(() {
_errorMsg = 'Wrong password.';
});
}
}
I suppose, I could think of a solution, but I think it's kind of ugly.
I could have an "error" variable, that is set when the request fails.
I would then call formState.validate() a second time, in there: check the error variable and return it if it's not null.
You can use flutter_form_bloc and use addError method of TextFieldBloc.
usernameField.addError('That username is taken. Try another.');
Keep in mind that you can also use asynchronous validators.
This is a complete example:
dependencies:
flutter:
sdk: flutter
flutter_bloc: ^0.21.0
form_bloc: ^0.5.0
flutter_form_bloc: ^0.4.1+1
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_form_bloc/flutter_form_bloc.dart';
import 'package:form_bloc/form_bloc.dart';
void main() {
runApp(MaterialApp(home: SignUpForm()));
}
class SignUpFormBloc extends FormBloc<String, String> {
final usernameField = TextFieldBloc();
final passwordField =
TextFieldBloc(validators: [Validators.passwordMin6Chars]);
#override
List<FieldBloc> get fieldBlocs => [usernameField, passwordField];
#override
Stream<FormBlocState<String, String>> onSubmitting() async* {
// Form logic...
try {
await _signUp(
throwException: true,
username: usernameField.value,
password: passwordField.value,
);
yield currentState.toSuccess();
} catch (e) {
// When get the error from the backend you can
// add the error to the field:
usernameField.addError('That username is taken. Try another.');
yield currentState
.toFailure('The error was added to the username field.');
}
}
Future<void> _signUp({
#required bool throwException,
#required String username,
#required String password,
}) async {
print(username);
print(password);
await Future<void>.delayed(Duration(seconds: 2));
if (throwException) throw Exception();
}
}
class SignUpForm extends StatelessWidget {
#override
Widget build(BuildContext context) {
return BlocProvider<SignUpFormBloc>(
builder: (context) => SignUpFormBloc(),
child: Builder(
builder: (context) {
final formBloc = BlocProvider.of<SignUpFormBloc>(context);
return Scaffold(
appBar: AppBar(title: Text('Sign Up Form')),
body: FormBlocListener<SignUpFormBloc, String, String>(
onSubmitting: (context, state) {
// Show the progress dialog
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => WillPopScope(
onWillPop: () async => false,
child: Center(
child: Card(
child: Container(
width: 80,
height: 80,
padding: EdgeInsets.all(12.0),
child: CircularProgressIndicator(),
),
),
),
),
);
},
onSuccess: (context, state) {
// Hide the progress dialog
Navigator.of(context).pop();
// Navigate to success screen
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => SuccessScreen()));
},
onFailure: (context, state) {
// Hide the progress dialog
Navigator.of(context).pop();
// Show snackbar with the error
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(state.failureResponse),
backgroundColor: Colors.red[300],
),
);
},
child: ListView(
children: <Widget>[
TextFieldBlocBuilder(
textFieldBloc: formBloc.usernameField,
decoration: InputDecoration(labelText: 'Username'),
),
TextFieldBlocBuilder(
textFieldBloc: formBloc.passwordField,
decoration: InputDecoration(labelText: 'Password'),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: RaisedButton(
onPressed: formBloc.submit,
child: Center(child: Text('SUBMIT')),
),
),
],
),
),
);
},
),
);
}
}
class SuccessScreen extends StatelessWidget {
const SuccessScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[300],
body: Center(
child: SingleChildScrollView(
child: Column(
children: <Widget>[
Icon(
Icons.sentiment_satisfied,
size: 100,
),
RaisedButton(
color: Colors.green[100],
child: Text('Sign out'),
onPressed: () => Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => SignUpForm())),
)
],
),
),
),
);
}
}
A simple solution:
Make a key for the widgets state:
GlobalKey<_CustomErrorTextField> _passwordTextFieldState = GlobalKey();
Set the Error message using the key:
_passwordTextFieldState.currentState.updateError(errorMsg);
Reset the error after 2 seconds:
Future.delayed(Duration(seconds: 2), () {
// Runs after duration sec
_passwordTextFieldState.currentState.updateError(null);
});
Set up the widget (be sure to set the key):
CustomErrorTextField(
key: _passwordTextFieldState,
label: "Password",
currentPassword: password,
validator: yourValidator,
callback: passwordCallback,
obscureText: hidePassword.value - a bool value show/hide password
)
Here is the Widget:
class CustomErrorTextField extends StatefulWidget {
CustomErrorTextField({
Key key,
this.label,
this.currentPassword,
this.validator,
this.callback,
this.obscureText = false
}): super(key: key);
final String label;
final String currentPassword;
final FormFieldValidator<String> validator;
final Function callback;
final obscureText;
#override
_CustomErrorTextField createState() => _CustomErrorTextField();
}
class _CustomErrorTextField extends State<CustomErrorTextField> {
String errorMsg;
updateError(String errorMsg){
setState(() {
this.errorMsg = errorMsg;
});
}
#override
Widget build(BuildContext context) {
return TextFormField(
decoration: InputDecoration(
labelText: widget.label,
errorText: errorMsg
),
initialValue: widget.currentPassword,
keyboardType: TextInputType.visiblePassword,
validator: widget.validator,
onSaved: (String val) {
widget.callback(val);
},
obscureText: widget.obscureText,
);
}
}