I was originally following the code for the answer found here:
Check if an application is on its first run with Flutter
And I was incorporating it into the introduction_screen package on pub.dev
I successfully have it loading the page for my onboarding page on first load. Then when I am done with the onboarding page I try to set the shared preference value to 'true' so when I reload the app it will skip the onboarding page, but it does not work when i test in my emulator in VS Code.
I check the value on first book here:
class _MyAppState extends State<MyApp> {
bool isLoggedIn = false;
_MyAppState() {
MySharedPreferences.instance
.getBooleanValue("isfirstRun")
.then((value) => setState(() {
isLoggedIn = value;
}));
}
I load the onboarding screen if false here:
home: isLoggedIn ? MainPage() : OnBoard(),
My Shared Pref dart file is:
import 'package:shared_preferences/shared_preferences.dart';
class MySharedPreferences {
MySharedPreferences._privateConstructor();
static final MySharedPreferences instance =
MySharedPreferences._privateConstructor();
setBooleanValue(String key, bool value) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
myPrefs.setBool(key, value);
}
Future<bool> getBooleanValue(String key) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
return myPrefs.getBool(key) ?? false;
}
}
When the onboarding is complete I run this:
MySharedPreferences.instance.setBooleanValue("loggedin", true);
//replace with main page
Route route = MaterialPageRoute(builder: (context) => MainPage());
Navigator.pushReplacement(context, route);
If I hot reload in VS everything is ok, but if I restart with the the app it runs the onboarding screen each time.
You should check and change the isLoggedIn value in initstate function
For example
class _MyAppState extends State<MyApp> {
bool isLoggedIn = false;
#override
void initState() {
MySharedPreferences.instance
.getBooleanValue("isfirstRun")
.then((value) => setState(() {
isLoggedIn = value;
}));
super.initState();
}
#override
Widget build(BuildContext context) {
return Something...
}
Use async and await in a separate function and then use it in initState:
void verityFirstRun() async {
final verification = await SharedPreferences.getInstance();
isLoggedIn = verification.getBool("isfirstRun") ?? false;
}
#override
void initState() {
verityFirstRun();
super.initState();
}
Use this way of calling the SharedPreferences instance:
final verification = await SharedPreferences.getInstance();
Related
When user logs into the app I need to set 'PWD' in the shared_preference variable. I need to get that value in splashcreen of my app so that when user opens the app again it need redirect to only password entering page. How can I do it in flutter.
onPressed: () async {
SharedPreferences prefs = await SharedPreferences.getInstance();
appdata.loginmode = prefs.setString('LOGIN_MODE', 'PWD');
Navigator.push(
context,
MaterialPageRoute(builder: (context) => BottomNavigation()),
);
print('Shared....');
print(prefs.getString('LOGIN_MODE'));
},
This what I am doing when user click login it will set to 'PWD', then I need to call the prefs in splashscree.
Short Answer
Not for splash screen but I am using the same logic for the onboard screen. I hope this answer will help. So, on your main.dart file, create a nullable int onBoardCount, outside of any class, you're gonna need this on your splash screen. Also, instantiate SharedPreferences in main and pass it with onboardcount to you MyApp();
int? onBoardCount;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
// Get onboard count from prefs, if it already exists,if not it will return null
onBoardCount = prefs.getInt('onBoardKey');
runApp(MyApp(prefs,onBoardCount));
}
Now, your MyApp file should be something like
class MyApp extends StatefulWidget {
late SharedPreferences prefs;
....
MyApp(this.prefs,this.onBoardCount, ...);
Now in your splash_screen.dart use the following logic.
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
await prefs.setInt('onBoardKey', 0);
// Some route logic like route.push("/home");
}
Long Answer
I am using Go Router for routing and Provider for state management so, here's my app's code.
Main.dart
int? onBoardCount;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences prefs = await SharedPreferences.getInstance();
onBoardCount = prefs.getInt('onBoardKey');
....
runApp(MyApp(prefs, onBoardCount));
}
I have a separate MyApp file to reduce congestion.
my_app.dart
class MyApp extends StatefulWidget {
late SharedPreferences prefs;
int? onBoardCount;
MyApp(this.prefs, this.onBoardCount,..... {Key? key})
: super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// The appstate provider is handling app level state
late AppStateProvider appStateProvider;
#override
void didChangeDependencies() {
super.didChangeDependencies();
appStateProvider = AppStateProvider(
widget.onBoardCount, widget.prefs,....);
}
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
....
ChangeNotifierProvider(
create: (context) => AppStateProvider(
widget.onBoardCount,
widget.prefs,...)),
Provider(
create: (context) => AppRouter(
appStateProvider: appStateProvider,
onBoardCount: widget.onBoardCount,
prefs: widget.prefs,
),
),
],
child: Builder(
builder: ((context) {
final GoRouter router = Provider.of<AppRouter>(context).router;
return MaterialApp.router(
routeInformationParser: router.routeInformationParser,
routerDelegate: router.routerDelegate);
}),
),
);
}
}
App State Provider File
Create a function to update onboard logic and notify listeners.
class AppStateProvider with ChangeNotifier {
AppStateProvider(this.onBoardCount, this.prefs,..);
int? onBoardCount;
late SharedPreferences prefs;
bool? _isOnboarded;
bool get isOnboard => _isOnboarded as bool;
void hasOnBoarded() async {
await prefs.setInt('onBoardKey', 0);
_isOnboarded = true;
notifyListeners();
}
}
On Router file
class AppRouter {
late AppStateProvider appStateProvider;
late SharedPreferences prefs;
int? onBoardCount;
AppRouter({
required this.appStateProvider,
required this.onBoardCount,
required this.prefs,
});
get router => _router;
late final _router = GoRouter(
refreshListenable: appStateProvider,
initialLocation: "/",
routes: [
...
],
redirect: (state) {
final String onboardLocation =
state.namedLocation("Your Route name");
bool isOnboarding = state.subloc == onboardLocation;
bool? toOnboard = prefs.containsKey('onBoardKey') ? false : true;
print("Is LoggedIn is $isLoggedIn");
if (toOnboard) {
return isOnboarding ? null : onboardLocation;
}
return null;
});
}
Since the router is listening to appStateProvider, it will change once you call hasOnBoarded() on your onboard screen.
OnBoardScreen
void onSubmitDone(AppStateProvider stateProvider, BuildContext context) {
stateProvider.hasOnBoarded();
GoRouter.of(context).go("/");
}
I hope this will help please leave comments. FYI, ... is some other codes that I feel it's not important for this topic.
Recently I am developing first ever app in Flutter and I know the basics only.
So here's the problem which I am facing currently.
I have to Navigate One of the two screens, either Register or Home page according to old/new user, So what I did is here.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'Register.dart';
import 'HomePage.dart';
class Authenticate extends StatefulWidget {
#override
_AuthenticateState createState() => _AuthenticateState();
}
class _AuthenticateState extends State<Authenticate> {
String _userName = "";
#override
void initState() {
super.initState();
_getUserName();
}
_getUserName() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
_userName = pref.getString('userName') ?? "";
});
}
Widget build(BuildContext context) {
if (_userName == "") {
print('name : $_userName , NEW user'); // ERROR - why it's printing on console for old User ?
return Register();
} else {
return HomePage(_userName);
}
}
}
So the problem is, even if I am opening app through old user, it is printing the debug code written for new user and there's around 0.3 sec screen visibility of Register screen.
So what should I do to fix this?
The print is happening because this line of code:
SharedPreferences pref = await SharedPreferences.getInstance();
is asynchronous, which means that it might invoke in some later point in time. Because of that the build method is called first, then your _getUserName method finished, which causes a setState and invokes a build method again.
You might want to show some kind of loader screen until sharedPrefernces is initialized and then decide wheter to show Register or Home page.
Example code:
class _AuthenticateState extends State<Authenticate> {
String _userName = "";
bool _isLoading = true;
#override
void initState() {
super.initState();
_getUserName();
}
_getUserName() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
_userName = pref.getString('userName') ?? "";
_isLoading = false;
});
}
Widget build(BuildContext context) {
if (_isLoading) {
return Center(child: CupertinoActivityIndicator());
} else if (_userName == "") {
print('name : $_userName , NEW user'); // ERROR - why it's printing on console for old User ?
return Register();
} else {
return HomePage(_userName);
}
}
}
Try this:
class _AuthenticateState extends State<Authenticate> {
String _userName;
#override
void initState() {
super.initState();
_getUserName();
}
_getUserName() async {
SharedPreferences pref = await SharedPreferences.getInstance();
setState(() {
_userName = pref.getString('userName') ?? '';
});
}
Widget build(BuildContext context) {
if (_userName == null) {
return Center(child: CircularProgressIndicator());
} else if (_userName == '') {
print('name : $_userName , NEW user');
return Register();
} else {
return HomePage(_userName);
}
}
}
In this case, _userName is null initially and will only be assigned a value after the _getUserName() returns either an empty String or an old userName. When null, the widget will build a progress indicator instead. If you don't want that, just return a Container().
I store bool isDarkTheme variable in my General provider class, I can acces it whenever I want.
The thing I want to do is to save that theme preference of user and whenever user opens app again, instead of again isDarkThem = false I want it to load from preference that I stored in SharedPreferences.
Here is my code of General provider: (I guess it is readable)
import 'package:shared_preferences/shared_preferences.dart';
class General with ChangeNotifier {
bool isDarkTheme = false;
General() {
loadDefaultTheme();
}
void loadDefaultTheme() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
isDarkTheme = prefs.getBool("is_dark_theme") ?? false;
}
void reverseTheme() {
isDarkTheme = !isDarkTheme;
notifyListeners();
saveThemePreference(isDarkTheme);
}
void saveThemePreference(bool isDarkTheme) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool("is_dark_theme", isDarkTheme);
}
}
Dart does not support async constructors so I think we should take another approach here. I usually create a splash screen (or loading screen, whatever you call it) to load all basic data right after the app is opened.
But if you only want to fetch theme data, you can use the async/await pair in main method:
void main() async {
WidgetsFlutterBinding.ensureInitialized(); // this line is needed to use async/await in main()
final prefs = await SharedPreferences.getInstance();
final isDarkTheme = prefs.getBool("is_dark_theme") ?? false;
runApp(MyApp(isDarkTheme));
}
After that, we can pass that piece of theme data to the General constructor:
class MyApp extends StatelessWidget {
final bool isDarkTheme;
MyApp(this.isDarkTheme);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (_) => General(isDarkTheme), // pass it here
child: MaterialApp(
home: YourScreen(),
),
);
}
}
We should change a bit in the General class as well, the loadDefaultTheme method is left out.
class General with ChangeNotifier {
bool isDarkTheme;
General(this.isDarkTheme);
// ...
}
I use method, that I call from InitState() where load SP with await.
But Widget is constructing before SP is loaded and have got empty SP values.
void getSP() async {
var prefs = await SharedPreferences.getInstance();
_todoItems = prefs.getStringList("key") ?? _todoItems;
}
Full code: https://pastebin.com/EnxfKgPH
there are many options, one i like is to use boolean variable like this
bool isLoaded = false;
#override
void initState() {
getSP();
super.initState();
}
void getSP() async {
var prefs = await SharedPreferences.getInstance();
_todoItems = prefs.getStringList("key") ?? _todoItems;
setState(() => isLoaded = true);
}
then check it to determine if build tree should load or not, like this..
#override
Widget build(BuildContext context) {
return !isLoaded ? CircularProgressIndicator() : Scaffold(...);
}
I am trying to save the value of Switch in SharedPreferences. Here is my code :
bool isDarkTheme;
static const String KEY_DARK_THEME = "dark";
void setTheme(bool value) async {
SharedPreferences pref = await SharedPreferences.getInstance();
isDarkTheme = value;
pref.setBool(KEY_DARK_THEME, isDarkTheme);
print("DARKSet? $isDarkTheme");
}
void getTheme() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
isDarkTheme = sharedPreferences.getBool(KEY_DARK_THEME);
print("dark? $isDarkTheme");
}
#override
void initState() {
// TODO: implement initState
super.initState();
print("MYINIT");
getTheme();
}
And inside Build method...
#override
Widget build(BuildContext context) {
print("BUILD $isDarkTheme");
...
...
ListTile(
title: Text("Dark Theme"),
trailing: Switch(
value: isDarkTheme ?? false,
onChanged: (val) {
setState(() {
setTheme(val);
});
},
),
),
...
...
}
Though I get the correct value inside debug console, but Switch widget is not changed accordingly. I found build() method is run before getting the value from SharedPrefernces, as a result Switch widget is not getting value from SharedPreferences. How to solve this problem of receiving Future value?
You have two option
1). I think when you get value from SharedPreference at that time you just call setState() method
void getTheme() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
setState(() {
isDarkTheme = sharedPreferences.getBool(KEY_DARK_THEME);
print("dark? $isDarkTheme");
});}
2) You can use Provider for StateManagement so, when isDarkTheme value is changed notifyListener is called and your build method is rebuild and you see the change
Your main issue is that you're retrieving the SharedPreferences instance whenever you want to store or retrieve a value, beside the fact you're also using two instances of it (pref and sharedPreferences) :
retrieve a single SharedPreferences instance using a separate function:
SharedPreferences pref ;
Future loadPreferences() async {
pref = await SharedPreferences.getInstance();
}
Then modify getTheme and setTheme :
void setTheme(bool value) async {
isDarkTheme = value;
pref.setBool(KEY_DARK_THEME, isDarkTheme);
print("DARKSet? $isDarkTheme");
}
void getTheme() async {
isDarkTheme = pref.getBool(KEY_DARK_THEME);
print("dark? $isDarkTheme");
}
Also, call loadPreferences during initialization, so pref will be loaded by the time build is called:
#override
void initState() {
super.initState();
print("MYINIT");
loadPreferences().then((_){
getTheme();
});
}