Problem in receiving Future data (SharedPreferences) in Flutter - flutter

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

Related

Flutter Shared Preferences, check if first run not working

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

I have a question about using async function in initstate

I want to initialize'LikesList' by calling the init function I implemented in initstate and render the related widgets accordingly. However, when it is actually executed, LikesList cannot be initialized, and, curiously, once hot reload is performed, LikesList is initialized. How do I get LikesList to be initialized normally when I first run it as I want?
List<String> LikesList = [];
int ListIndex = 0;
Future<List<String>> init() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
LikesList = await prefs.getStringList('LikesList');
ListIndex = await LikesList.length;
print("${LikesList}");
}
#override
void initState() {
// TODO: implement initState
// Future.delayed(Duration(milliseconds: 100)).then((_) {
// init();
// });
Timer.run(() {
init();//This is the function I implemented.
});
super.initState();
print("++++++++${LikesList}");
}
As #whatamelon suggested you can use FutureBuilder to serve the purpose.
Future<List<String>> init() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.getStringList('LikesList');
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: init(),
builder: (ctx, snapshot) {
if(snapshot.hasData) {
print(snapshot.data);
return Text("${snapshot.data}");
}
else
return Text("loading");
}
);
}
How about using Future method?
Using Future and Future builder makes you can get data in initialized state.

Problem implementing shared_preferrences in flutter

The following code is part of my login screen. I am using shared_preferences to save the remember me checkbox and the user name. The checkbox value is working, but the user name is not being set. I know it is being saved because I can see the value using print. But I guess it is being set too late, as my TextField is blank. Any ideas?
class RmoLogin extends StatefulWidget {
static const String id = 'login_screen';
#override
_RmoLoginState createState() => _RmoLoginState();
}
class _RmoLoginState extends State<RmoLogin> {
final TextStyle style = TextStyle(fontFamily: 'Montserrat', fontSize: 20.0);
final TextEditingController usernameController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
bool showSpinner = false;
bool rememberMe = false;
String userName = '';
_saveRememberUser() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString('username', usernameController.text);
prefs.setBool('remember', rememberMe);
}
_getRememberUser() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
userName = prefs.getString('username') ?? '';
rememberMe = prefs.getBool('remember') ?? false;
}
#override
void initState() {
super.initState();
_getRememberUser();
if (rememberMe) {
usernameController.text = userName;
}
}
#override
Widget build(BuildContext context) {
final userField = UserTextField(style: style, usernameController: usernameController);
final passwordField = PasswordTextField(style: style, passwordController: passwordController);
final rememberMeCheckbox = Checkbox(
value: rememberMe,
onChanged: (newValue) {
setState(() {
rememberMe = newValue;
});
},
);
Because the SharedPreferences instantiation is asynchronous and the build method itself is not, by the time the instance is created and the value is accessed, the build method would have already been called and your widgets built.
However, For things like SharedPreferences and databases, the best way to implement them is to use the Singleton approach, i.e, one instance that you can use across the entire application.
In this case instead of creating a new SharedPreferences instance whenever either method is called, it will be best to create a single instance of it and use that instance to update the set and get the values.
You could also create a service for the SharedPreferences which you could use across the entire application.
class SharedPreferencesService {
final SharedPreferences _prefs;
SharedPreferencesService(this._prefs);
// define methods here
}
Then in your main method, you can create an instance of SharedPreferences and use it to initialize the SharedPreferencesService.
eg.
SharedPreferencesService service;
Future<void> main() async {
// flutter will complain if this isn't present
WidgetsFlutterBinding.ensureInitialized();
final prefs = await SharedPreferences.getInstance();
service = SharedPreferencesService(prefs);
runApp(YourApp());
}
Then wherever you want to use it in your app, you can call the method name on the service. It is best to use Dependency Injection for the SharedPreferencesService though, you can try the get_it library.
Just a brief look.... but
You are calling _getRememberUser synchronously within initState() when it is an async method
initState() isn't the correct place to be calling it as initState() itself is a synchronus #override.
My solution would be to use a FutureBuilder in your build() method, and call _getRememberUser there.
You just need to update the state that's it check the below solution:
bool rememberMe = false;
String userName = '';
#override
void initState() {
// TODO: implement initState
super.initState();
_getRememberUser();
}
_getRememberUser() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
setState(() {
userName = prefs.getString('username') ?? '';
rememberMe = prefs.getBool('remember')?? false;
});
if (rememberMe) {
usernameController.text = userName;
}
print(userName +'-->'+rememberMe.toString());
}
Happy Coding!!

Flutter - How to pause application while shared preferences is loading?

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

State variable is not updated after getting data from SharedPreferences

I have the following problem: there's a state class:
class _HomeScreenState extends State<HomeScreen> {
String _name = "David";
#override
void initState() {
super.initState();
getNameFromPreferences();
}
#override
Widget build(BuildContext context) {
...
child: Text(
"Good Morning, \n"+_name,
style: TextStyle(color: Colors.black, fontSize: 28, fontWeight: FontWeight.bold),
),
)
}
...........................
void getNameFromPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_name = prefs.getString(Strings.nameKey);
log(_name);
}
So, here you can see that I'm trying to get data from preferences and set it to variable in state. I watched in logs, that this data is really exists and that it sets to _name almost in the moment. But on the screen I still see default value David but not my data from SharedPreferences. So, why such situation happens?
Just change your code as follow,
getNameFromPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_name = prefs.getString(Strings.nameKey);
log(_name);
setState(() {});
}
This will solve your issue.
Because sharedprefs package in async manner. so it takes some time to configure your data. till then your build method gets built. So, you have to use setState again to rebuild your view to render ui changes.
You didn't call setState().
void getNameFromPreferences() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_name = prefs.getString(Strings.nameKey);
log(_name);
setState(() {}); // add this
}