How can i set loading screen when apps check login status? - flutter

I use SharedPreferences to keep login status. its works fine.
but after close my app when I open this. it's showing all my screen like flash screen then its stay on its right screen.
but I don't want this and this not good.
I want when apps check login status its shows a loading screen or anything then after completing its show apps right screen.
how can I do this?
Here is my login status check code
checkLoginStatus() async {
sharedPreferences = await SharedPreferences.getInstance();
if (sharedPreferences.getString("empid") == null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => Home()),
(Route<dynamic> route) => false);
}
}

Snippet
void main async {
bool isUserLogin = await User.isUserLogin();
if (isUserLogin) { // wait untill user details load
await User.currentUser.loadPastUserDetails();
}
runApp(MyApp(isUserLogin));
}
and
class MyApp extends StatelessWidget {
bool isUserLogin;
MyApp(this.isUserLogin);
#override
Widget build(BuildContext context) {
var defaultRoot = isUserLogin ? HomePage() : LoginScreen();
final material = MaterialApp(
debugShowCheckedModeBanner: false,
home: defaultRoot,
);
return material;
}
}
Hope this helps!

Personnally I like to use a variable _isLoading which I set to true at the beginning of my login method using a setState. Then using this bool you just have to change what is displayed in your Scaffold.
bool _isLoading = false;
checkLoginStatus() async {
setState() => _isLoading = true;
sharedPreferences = await SharedPreferences.getInstance();
if (sharedPreferences.getString("empid") == null) {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) => Home()),
(Route<dynamic> route) => false);
} else {
setState() => _isLoading = false;
}
}
EDIT: In your case this might be more relevant:
#override
Widget build(BuildContext context) {
switch (currentUser.status) {
case AuthStatus.notSignedIn:
return LoginPage();
case AuthStatus.signedIn:
return HomePage();
default:
return LoadingPage();
}
}
My currentUser is just a class that I've created containing an enum AuthStatus which can have the value notSignedIn or signedIn. If you set the status to null while loading you can display your loading screen.

Related

ChangeNotifierProvider does not update the model

i am quite new with flutter. I am trying to add a ChangeNotifierProvider into my app. I use flutter_azure_b2c to log in a user, in order to handle to login outcome I have the following code:
AzureB2C.registerCallback(B2COperationSource.POLICY_TRIGGER_INTERACTIVE,
(result) async {
if (result.reason == B2COperationState.SUCCESS) {
List<String>? subjects = await AzureB2C.getSubjects();
if (subjects != null && subjects.isNotEmpty) {
B2CAccessToken? token = await AzureB2C.getAccessToken(subjects[0]);
if (!mounted || token == null) return;
final encodedPayload = token.token.split('.')[1];
final payloadData =
utf8.fuse(base64).decode(base64.normalize(encodedPayload));
final claims = Claims.fromJson(jsonDecode(payloadData));
var m = Provider.of<LoginModel>(context);
m.logIn(claims);
}
}
});
The problem is that when it arrives to var m = Provider.of<LoginModel>(context); the execution stops with out errors without executing m.logIn(claims);, so the model is not changed and the consumer is not called.
Any idea?
This is my consumer:
class App extends StatelessWidget {
const App({super.key});
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => LoginModel(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
theme: appTheme,
home: Consumer<LoginModel>(
builder: (context, value, child) =>
value.claims != null ? const Home() : const Login(),
)),
);
}
}
class LoginModel extends ChangeNotifier {
Claims? _claims;
logIn(Claims claims) {
_claims = claims;
notifyListeners();
}
logOut() {
_claims = null;
notifyListeners();
}
Claims? get claims => _claims;
}
My LoginWidget:
class Login extends StatefulWidget {
const Login({super.key});
#override
LoginState createState() => LoginState();
}
class LoginState extends State<Login> {
B2CConfiguration? _configuration;
checkLogin(BuildContext context) async {
List<String>? subjects = await AzureB2C.getSubjects();
if (subjects != null && subjects.isNotEmpty) {
B2CAccessToken? token = await AzureB2C.getAccessToken(subjects[0]);
if (!mounted || token == null) return;
final encodedData = token.token.split('.')[1];
final data =
utf8.fuse(base64).decode(base64.normalize(encodedData));
final claims = Claims.fromJson(jsonDecode(data));
var m = Provider.of<LoginModel>(context, listen: true);
m.logIn(claims); //<-- debugger never reaches this line
}
}
#override
Widget build(BuildContext context) {
// It is possible to register callbacks in order to handle return values
// from asynchronous calls to the plugin
AzureB2C.registerCallback(B2COperationSource.INIT, (result) async {
if (result.reason == B2COperationState.SUCCESS) {
_configuration = await AzureB2C.getConfiguration();
if (!mounted) return;
await checkLogin(context);
}
});
AzureB2C.registerCallback(B2COperationSource.POLICY_TRIGGER_INTERACTIVE,
(result) async {
if (result.reason == B2COperationState.SUCCESS) {
if (!mounted) return;
await checkLogin(context);
}
});
// Important: Remeber to handle redirect states (if you want to support
// the web platform with redirect method) and init the AzureB2C plugin
// before the material app starts.
AzureB2C.handleRedirectFuture().then((_) => AzureB2C.init("auth_config"));
const String assetName = 'assets/images/logo.svg';
final Widget logo = SvgPicture.asset(
assetName,
);
return SafeArea(
child: //omitted,
);
}
}
I opened an issue as well, but it did not help me.
Try this
var m = Provider.of<LoginModel>(context, listen: false)._claims;
You are using the Provider syntax but not doing anything really with it. You need to set it like this Provider.of<LoginModel>(context, listen: false).login(claims) and call it like this Provider.of<LoginModel>(context, listen: false)._claims;
I fixed it, moving the callback registrations from the build method to the initState method.

Run Function / Get Data when loading new screen in Flutter

I am trying to run a function that gets data via an API call that needs to initialize before / while loading the screen. I have tried using a future builder, and other methods, and perhaps i am not implementing this correctly but i cannot figure out how to do this. I have also called an async method that uses setState within my initstate method which works, but it initially loads and returns an error and what I assume is that after the state rebuilds it displays correctly.Ideally this should get the data before actually loading the screen to avoid errors.
Code for my main function in the main.dart file
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]
);
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
final SharedPreferences prefs = await _prefs;
late String initialRoute;
var accessToken = prefs.getString('access_token');
var refreshToken = prefs.getString('refresh_token');
if (accessToken != null && refreshToken != null) {
var response = await sessionRefresh(accessToken);
if (response.statusCode == 200) {
initialRoute = kDashboardID;
}
else if (response.statusCode == 401) {
var refreshResponse = await tokenRefresh(refreshToken);
if (refreshResponse.statusCode == 200) {
prefs.setString('access_token', jsonDecode(refreshResponse.body)["access_token"]);
initialRoute = kDashboardID;
}
else {
initialRoute = kLoginScreenID;
}
}
}
else {
initialRoute = kLoginScreenID;
}
runApp(MyApp(initialRoute: initialRoute,));
}
class MyApp extends StatelessWidget {
//const MyApp({Key? key}) : super(key: key);
final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
String initialRoute;
MyApp({required this.initialRoute});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (context) => UserData(),
child: MaterialApp(
debugShowCheckedModeBanner: false,
routes: {
kLaunchScreenID: (context) => LaunchScreen(),
kLoginScreenID: (context) => LoginScreen(),
kUserRegistrationID: (context) => UserRegistration(),
kAccountVerificationID: (context) => OTPVerification(),
kDashboardID: (context) => Dashboard(),
kAccountsID: (context) => Accounts(),
},
initialRoute: initialRoute,
),
);
}
}
The ideal result would be when the initialRoute equals "kDashboardID" (user dashboard) i need to run a function that gets data so that the data can be used to populate certain widgets (text widgets, etc.) on the dashboard screen.
Not sure how to best to accomplish this.

Go back to login when logged out from the drawer, no matter what

I need to redirect user to login page when he clicks on logout button from drawer (wherever he is). The problem is that when I click on the logout button, the screen remains the same.
According to this post: Flutter provider state management, logout concept
I have:
void main() async {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Profile>(
create: (final BuildContext context) {
return Profile();
},
)
],
child: MyApp(),
),
);
}
MyApp:
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
initPlatformState();
}
/// Platform messages are asynchronous, so we initialize in an async method.
Future<void> initPlatformState() async {
if (!mounted) return;
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
initialRoute: '/',
navigatorKey: navigatorKey,
// ...
home: Consumer<Profile>(
builder: (context, profile, child){
return profile.isAuthenticated ? SplashScreen() : AuthScreen();
}
)
);
}
}
The part of the drawer where there is the logout button:
ListTile(
leading: Icon(Icons.logout),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () async {
SharedPreferences preferences =
await SharedPreferences.getInstance();
await preferences.clear();
final Profile profile =
Provider.of<Profile>(context, listen: false);
profile.isAuthenticated = false;
}),
As I said, when I click on the logout button from the drawer, the user is correctly logged out, but the screen remains the same.
UPDATE
This is the profile class:
class Profile with ChangeNotifier {
bool _isAuthenticated = false;
bool get isAuthenticated {
return this._isAuthenticated;
}
set isAuthenticated(bool newVal) {
this._isAuthenticated = newVal;
this.notifyListeners();
}
}
I think you are using provider class incorrectly.
use your profile class like this.
class Profile with ChangeNotifier {
bool _isAuthenticated = true;
bool get getIsAuthenticated => _isAuthenticated;
set setIsAuthenticated(bool isAuthenticated) {
_isAuthenticated = isAuthenticated;
notifyListeners();//you must call this method to inform lisners
}
}
in set method call notifyListners();
in your listTile
replace profile.isAuthenticated = false to profile.isAuthenticated = false;
Always use getters and setters for best practice.
I hope this is what you were looking for.
Add Navigator.of(context).pushReplacementNamed("/routeName") in LogOut onTap() Section.
For more information : https://api.flutter.dev/flutter/widgets/Navigator/pushReplacementNamed.html
Make sure to have logout route set in MyApp file, and i'd edit logout button file as such:
ListTile(
leading: Icon(Icons.logout),
title: Text(AppLocalizations.of(context)!.logout),
onTap: () async {
SharedPreferences preferences =
await SharedPreferences.getInstance();
await preferences.clear();
final Profile profile =
Provider.of<Profile>(context, listen: false);
profile.isAuthenticated = false;
// add login file route here using Navigator.pushReplacementNamed() ;
}),
Navigator push named -> logout route?

I have a question about navigating to the next page conditionally in initstate

I want to implement Auto Login with Shared preferences.
What I want to implement is that as soon as 'LoginPage' starts, it goes to the next page without rendering LoginPage according to the Flag value stored in Shared preferences.
However, there is a problem in not becoming Navigate even though implementing these functions and calling them from initstate. What is the problem?
//Login Page
void autoLogIn() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String userId = prefs.getString('username');
print("ddddddddddddddd");
SocketProvider provider = Provider.of<SocketProvider>(context);
Future.delayed(Duration(milliseconds: 100)).then((_) {**//I tried giving Delay but it still didn't work.**
Navigator.of(context).pushNamedAndRemoveUntil("/MainPage", (route) => false);
});
}
#override
void initState() {
// TODO: implement initState
loginBloc = BlocProvider.of<LoginBloc>(context);
if(!kReleaseMode){
_idController.text = "TESTTEST";
_passwordController.text = "1234123";
}
initBadgeList();
autoLogIn();**//This is the function in question.**
super.initState();
print("1111111111111111");
}
I don't think you should show LoginPage widget if user is already logged in and then navigate to main page.
I suggest you to use FutureBuilder and show either splash screen or loader while performing await SharedPreferences.getInstance(). In this case your App widget should look like this:
class App extends MaterialApp {
App()
: super(
title: 'MyApp',
...
home: FutureBuilder(
future: SharedPreferences.getInstance(),
builder: (context, snapshot) {
if (snapshot.data != null) {
final SharedPreferences prefs = snapshot.data;
final userId = prefs.getString('username');
...
return userId == null ?? LoginPage() : MainPage();
} else {
return SplashScreenOrLoader();
}
}));
}
But if you still want to show LoginPage first, just replace SplashScreenOrLoader() with LoginPage() in code above.

Show the introduction page only when the user logs in for the first time for a Flutter app

I have used the below code to show the introduction page only when the user logs in for the first time and second time when the user logs in the user should be taken directly to the homepage.
But this does not work after my first try. Now every time a new user logs in it goes directly to the homepage but not to the introduction page. Please let me know if I am doing anything wrong.
Here is my Code:
class OneTimeScreen extends StatefulWidget {
#override
OneTimeScreenState createState() => new OneTimeScreenState();
}
class OneTimeScreenState extends State<OneTimeScreen>
with AfterLayoutMixin<OneTimeScreen> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
Navigator.of(context)
.pushReplacement(MaterialPageRoute(builder: (context) => HomePage()));
} else {
await prefs.setBool('seen', true);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => IntroVideoPage()));
}
}
#override
void afterFirstLayout(BuildContext context) => checkFirstSeen();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: CircularProgressIndicator(),
),
);
}
}
This is because the scope of the boolean which you are storing in SharedPreferences as seen is app-wide. It does not differentiate if userA or userB logs in. It is a single boolean for both.
To make that boolean user specific, we can add a unique userID as a prefix to the key seen like..
bool _seen = (prefs.getBool(userID + 'seen') ?? false);
prefs.setBool(userID + 'seen', true);
This will ensure storing different boolean for each user.