How to save prefs to different scopes in Flutter? - flutter

I am trying to use shared prefs to store App Data and User Data differently, like this :
{
"APP_DATA": {
"banners": "{\"success\":true,\"data\":{}}",
"gameList": "{\"success\":true,\"data\":{}}"
},
"USER_DATA":
{
"emailVerified": "N",
"userName": "97144554455",
"playerToken": "W_8zUx3UCmFPeECjhhjhHhnnsajknHxAM0",
"idVerified": "UPLOADED",
"playerId": "412904",
"bankList": "{\"84\":\"African Bank\",\"5\":\"FNB : First National Bank\"}",
"unreadMsgCount": "0"
}
}
But whenever I update any value in "APP_DATA" or "USER_DATA", it deletes the previous value and updates new value. Example : After adding "banners" and "gamesList" to my APP_DATA, I only see "gamesList" because it is overriding the "banners" value.
MySharedPreferences.instance.setAppStringValue(
"banners", jsonEncode(bannersModel),
);
MySharedPreferences.instance.setAppStringValue(
"gamesList", jsonEncode(gamesListModel),
);
This is the function I'm using
setAppStringValue(String key, String value) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
myPrefs.setString("APP_DATA", jsonEncode({key: value}));
}
How to solve this issue?

What is happening is that you are overriding the APP_DATA key with a whole new value (encoded String) which results in the previous data to be lost.
If you want to keep the same structure, this is, having a key for APP_DATA and other for USER_DATA, both having their own data, you'll need to fetch the previous stored data and append the new one into it so you make sure you don't lose it.
setAppStringValue(String key, String value) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
final String storedData = myPrefs.get('APP_DATA');
final Map newData = {key: value};
try {
// Has previous data, decode it and append into the new one
if (storedData != null) {
newData.addAll(jsonDecode(storedData));
}
} catch (ex) {
print('Couldn\'t parse the stored data: $ex');
}
myPrefs.setString('APP_DATA', jsonEncode(newData));
}

When using shared preferences, it uses the unique key to recognise the value of the corresponding data. When you are writing data to the key 'APP_DATA' it will overwrite the entire string value. To change one value without overwriting the rest, you should save it in different keys. Like so,
setAppStringValue(String key, String value) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
myPrefs.setString("banner", jsonEncode({key: value}));
}
setAppStringValue(String key, String value) async {
SharedPreferences myPrefs = await SharedPreferences.getInstance();
myPrefs.setString("gamesList", jsonEncode({key: value}));
}

Related

Store data in dart/flutter

I am coding a to-do list app in flutter but every time I close the app, all my to-do's are gone, and none of them are stored. How do I stop them from disappearing every time I close the app?
Use sqlite or files. Please refer documentation on cookbooks for either approach.
https://docs.flutter.dev/cookbook/persistence
Your other option is to use an external database over the internet
To persist your data (todo list) you can either
store data on the users device
You can do this by using a local databases like sqflite, sqfentity, shared_preferences etc
or store the data on the server
Using this option you can either spin up your own server or use some quick serverless solutions like supabase or cloud firestore from firebase.
I recommend hive it’s very easy to use and it’s lightweigh.
In addition with all the other propositions, you can try Isar, which is a NoSQL local database that can be used for all platforms:
https://isar.dev/tutorials/quickstart.html
https://pub.dev/packages/isar
Apps generally store data in temporary storage which destroyed every time yo close the app. in order to save data permanently, you could use sqflite database or shared_preferences database
if you want use shared_preferences, you can do this:
first make class that call StorageManager:
import 'package:shared_preferences/shared_preferences.dart';
class StorageManager {
static Future<bool> saveData(String key, dynamic value) async {
final prefs = await SharedPreferences.getInstance();
await prefs.reload();
if (value is int) {
prefs.setInt(key, value);
return true;
} else if (value is String) {
prefs.setString(key, value);
return true;
} else if (value is bool) {
prefs.setBool(key, value);
return true;
} else {
return false;
}
}
static Future<dynamic> readData(String key) async {
final prefs = await SharedPreferences.getInstance();
await prefs.reload();
dynamic obj = prefs.get(key);
return obj;
}
static Future<bool> deleteData(String key) async {
final prefs = await SharedPreferences.getInstance();
return prefs.remove(key);
}
static Future<void> reloadSharedPreferences() async {
final prefs = await SharedPreferences.getInstance();
await prefs.reload();
}
}
then use it like this:
when you want save some thing in storage call this:
await StorageManager.saveData('some unique key', your int or string or bool value);
when you want read from storage:
var result = await StorageManager.readData('some unique key');
if (result != null) {
// use your value
} else {
// this means there is no result
}

returning a String when getting error: type 'Future<dynamic>' is not a subtype of type 'String'

I can't work out how to return a string from a function in Dart (a Flutter app).
I am using SharedPreferences to capture input from the user. I have two functions, one to save preferences:
save(key, value) async {
final prefs = await SharedPreferences.getInstance();
prefs.setString(key, value);
print('saved $value');
}
and one to read preferences:
read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(key) ?? 0;
print('$value');
}
This is working, but when I try to replace the print line with a return:
read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(key) ?? 0;
return('$value');
}
to return a string for the value, it throws an error:
type 'Future' is not a subtype of type 'String'
I have tried calling it many MANY different ways, but can't figure out what I assume is an incredibly basic problem. I noticed in some posts that this is a suggested solution, which works to print out the value, but I don't want to print it, i want it as a String variable:
read(mykey).then((value) => '$value');
I need to combine the value with other some other string values and make some minor manipulations (so printing it isn't helpful)
UPDATE
I have defined the function as #Stijn2210 suggested, but am still having problems getting the output i need.
Future<String> read(key) async {
final prefs = await SharedPreferences.getInstance();
final value = await prefs.getString(key) ?? '';
return value;
}
When I call this function from my app (this is a simplified snippet):
void onDragEnd(DraggableDetails details, User user) {
final minimumDrag = 100;
Future<String> myvalue;
if (details.offset.dx > minimumDrag) {
user.isSwipedOff = true;
save(user.imgUrl, 'Dog');
}
myvalue = read(user.imgUrl);
print(myvalue);
It's printing :
Instance of 'Future'
Whereas I want myvalue to be 'Dog'... Appreciate any insights!!
Really appreciate your answer #Stijn2202
Solution was to edit the method definition:
Future<void> onDragEnd(DraggableDetails details, User user) async
and then call the read function from the method with this:
final String myvalue = await read(user.imgUrl);
getString is a Future, which you can handle by using await or as you are doing, using then
However, in my opinion using await is your better option. This would look like this:
Future<String> getMyString() async {
final prefs = await SharedPreferences.getInstance();
final value = await prefs.getString(key) ?? '';
// Don't use 0, since it isnt an int what you want to return
return value;
}
EDIT:
based on your code snippet, this is how you should call your read method:
Future<void> onDragEnd(DraggableDetails details, User user) async {
final minimumDrag = 100;
if (details.offset.dx > minimumDrag) {
user.isSwipedOff = true;
save(user.imgUrl, 'Dog');
}
final String myvalue = await read(user.imgUrl);
print(myvalue);
}
Now I'm not sure if onDragEnd is actually allowed to be Future<void>, but let me know if it isn't
Just await for the value. It will return Dog and not instance of Future.
String someName=await myvalue;
As the value is Future, await keyword will wait until the task finishes and return the value

how can i store multiple data in sharedpreferences?

I am getting user information like the username , profile pic and name .I want to store all that info inside Sharedpreferences so that i wont have to call firebase every time I need them.
here is how i am getting the data ,how can i store this data so that later on i can get user's name and its profilepic by checking it through its username ?
storeUsersInfo()async{
print('STORE CALLED');
QuerySnapshot querySnapshot = await DatabaseMethods().getUsers();
var length = querySnapshot.docs.length ;
print(length);
int i = 0 ;
while ( i < length ) {
print(name = "${querySnapshot.docs[i]["name"]}");
print(profilePicUrl = "${querySnapshot.docs[i]["profileURL"]}");
i++;
}
}
here is the firebase call
Future<QuerySnapshot> getUsers() async {
return await FirebaseFirestore.instance
.collection("users")
.get();
}
and if anyone needs anything else please ask .
You can store all the information in SharePreference by encoding picture objects to Base64String before storing them.
How you can encode it:
Future<String> encodeImageToBase64String(String imageUrl) async {
final response = await client.get(Uri.parse(imageUrl));
final base64 = base64Encode(response.bodyBytes);
return base64;
}
After Encoding the image, you can cache it to sharedPreference using
SharedPreferences pref = await SharedPreferences.getInstance();
//Save string to SharedPreference
pref.setString('image', encodedImageString);
How to Decode and use Image Later
//Get Encoded Image String from SharedPreferences
final base64String = pref.getString('image');
///Decodes Images file encoded to Base64String to Image
Uint8List decodeImageFromBase64String(String base64String) {
return base64Decode(base64String);
}
Finally, you can use this in your Image Widget like so
...
Image(image: MemoryImage(decodeImageFromBase64String))
Assuming you want to cache name, username and image gotten from firebase
//Create a model for the firebase data
class UserModel{
final String name;
final String username;
final String encodedImage;
UserModel(this.name, this.username, this.encodedImage);
String toJson(){
Map<String, dynamic> userMap = {
'name': name,
'username': username,
'image': encodedImage,
};
return json.encode(userMap);
}
}
//Encode the image HERE
encodeImageToBase64String(imageUrl);
//Pass in the parameters to the UserModel() constructor and Call //the toJson(), then Cache the Resulting String
String stringToCache = UserModel(nameString, usernameString, encodedImageString).toJson();
SharedPreferences takes a key and the data.
use this in an async funtion.
This syntax is sharedPreferences.setString(key, value)
So in a function,
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
sharedPreferences.setString("token", jsonResponse['access_token'].toString());
sharedPreferences.setString("userId", jsonResponse['customer_id'].toString());
You can get the stored data by sharedPreferences.getString(key).Like
var token = sharedPreferences.getString("token");
You can use a cache like https://pub.dev/packages/firestore_cache which does that for you.

Flutter save shared preferences of multiple users

I have two types of settings which I want to save on the users apps device. First is filter settings, second is if user is opening the app the very first time.
For filter I have multiple bools to save, for the first open app value I just need a single bool. Now my problem is if I log into the app with another account, the values of the first account are choosen. I need to make a set for each account, but I dont know how, since im changing single values all the time.
these are my functions for getting and setting a filter:
void putShared(String key, bool val) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(key, val);
}
Future getShared(String key) async {
final prefs = await SharedPreferences.getInstance();
bool val = prefs.getBool(key) == null ? false : (prefs.getBool(key));
return val;
}
So how to change the code to make it work for multiple accounts?
You can add a unique user id as a prefix to all the keys. Like..
void putShared(String key, bool val) async {
String userSpecificKey = userID + key;
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool(userSpecificKey, val);
}
Future getShared(String key) async {
String userSpecificKey = userID + key;
final prefs = await SharedPreferences.getInstance();
bool val = prefs.getBool(userSpecificKey) == null ? false : (prefs.getBool(userSpecificKey));
return val;
}
What I suggest is while saving the values in the sharedprefrences you rather use a map where the key must be unique like id and value of what you need. Then just encode the map which will convert it to string and then you can save it as string in sharedPrefrences and then when you retrive you just decode the map and just check if the loggedin user id matches the id which you have logged in if not do not fetch the values else fetch them.

Saving DateTime type in Local File in Flutter

I have a form with a TextField and a submit button that is able to save and read the data.
class Storage {
Future<String> get localPath async {
final dir = await getApplicationDocumentsDirectory();
return dir.path;
}
Future<File> get localFile async {
final path = await localPath;
return File('$path/db.txt');
}
Future<String> readData() async {
try {
final file = await localFile;
String body = await file.readAsString();
return body;
} catch (e) {
return e.toString();
}
}
Future<File> writeData(String data) async {
final file = await localFile;
return file.writeAsString('$data');
}
}
#override
void initState() {
super.initState();
widget.storage.readData().then((String value) {
setState(() {
name = value;
});
});
}
Future<File> writeData() async {
setState(() {
name = oneController.text;
oneController.text = '';
});
}
With this I was able to save data with String values. I tried doing the same thing for DateTime and I get this error:
"The argument type 'Null Function(DateTime)' can't be assigned to the parameter type 'FutureOr Function(String)'"
Does saving to local file only work for String Data?
The error you are getting seems to be because you are trying to do this:
widget.storage.readData().then((DateTime value) {
setState(() {
name = value;
});
});
Using DateTime as an argument, but it seems you forgot to change the return type of readData() to Future<DateTime>. Anyway, that's seems to be the error.
But as #Adrian mentioned, you could store the int property millisecondsSinceEpoch instead of DateTime, and then you could do this:
DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(timestampSaved);
To answer the other issue, you have to save strings to files. An object must be converted to a string. A typical way to save DateTime is to do something like millisecondsSinceEpoch and save that in your file.
Rather than reinvent what's been done before, I suggest looking into packages/plugins for persistent storage. One promising one is hive. You can search for more at pubdev. There you can find persistent storage options like shared_preferences, sqflite, and sembast. A couple of these (sembast and hive) are basically object stores within files. Kind of like what you're trying to do here.