save map locally and use it elsewhere - flutter

I'm converting a map to a string in order to save it to the device memory
_read() async {
try {
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/answers.txt');
String text = await file.readAsString();
print(text);
} catch (e) {
print("Couldn't read file");
}
}
_save() async {
final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/answers.txt');
await file.writeAsString(answers.toString());
print('saved');
}
now I want to use it as a map to access the data on the map. is there a way to do it?
my map looks like this {Everyone should read...: Harry Potter, Two truths and a lie...: something, I can quote every line from...: the alchemist}

What you want is a JSON file.
JSON is a special syntax that can be used to store maps and lists in a file.
There is a catch though: You may only store maps and lists of primitive values like string, int or bool, a custom class, for example, cannot be stored in a JSON file. You would have to convert it into a map first.
In order to turn a JSON string into a map, you can use the jsonDecode function. Similarly, the jsonEncode function will return a string from a map.
Here goes the code:
Future<Map<String, dynamic>> _read() async {
final file = File(filePath);
final jsonStr = await file.readAsString()
return jsonDecode(jsonStr) as Map<String, dynamic>>;
}
Future<void> _write(Map<String, dynamic> map) async {
final jsonStr = jsonEncode(map);
final file = File(filePath);
await file.writeAsString(jsonStr);
}
In my code I skipped the try-catch block and the Directory thing, that's just to make the example simpler.

Related

What is the best way to work with files in Flutter?

I'm a junior working with flutter and hit a problem.
I need to open a file, read and compare some data everytime the app opens and then change some of that data as the app progress. We tried using .txt files to read and write some text, but when we had to look for something in the file was too complicated to change it and the file is not accessibe only on the device running the app. We also thought of using xml files but I don't know if is a good idea.
What would be a pratical solution for this situation, as the file needs to be opened all the time. Thanks.
Let's say our JSON looks like this:
{
title: 'some text',
values: [1,5,2,4,1,3],
}
And we want to make a UI that allows us to add values and to edit the title. First let's write a method to write and read from the JSON file:
Future<void> _write(Map<String, dynamic> value) async {
File f = File(_fileLocation);
String jsonStr = jsonEncode(value);
await f.writeAsString(jsonStr);
}
Future<Map<String, dynamic>> _read() async {
File f = File(_fileLocation); // maybe move this into a getter
final jsonStr = await f.readAsString();
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
This way, the rest of the app should be trivial, but let's add a method to update the title and a method to add a new number:
Future<void> _updateTitle(String title) async {
var values = await _read();
values['title'] = title;
await _write(values);
}
Future<void> _addNumber(int number) async {
var values = await _read();
values['values'].push(number);
await _write(values);
}
Types with JSON and Dart can be a bit weird, so it is possible you need to use the as keyword when reading from the list:
Future<void> _addNumber(int number) async {
var values = await _read();
var valueList = (values['values'] as List<int>);
valueList.push(number);
values['values'] = valueList;
await _write(values);
}
Hopefully, this example helps

Unhandled Exception: type 'List<Set<Future<File>>>' is not a subtype of type 'List<File>' in type cast

I would compress a list of image file before uploading, but i have some doubts to convert type after using map a list of file, it return type List<Set<Future>>, not type List as I expect. How can I convert/cast to List of File in this situation, the compressImage function worked well with only one file. Thank you so much.
import 'package:image/image.dart' as Im;
import 'package:uuid/uuid.dart';
List<File> compressImageList(List<File> tempfileList) async {
List<Future<File>> compressedFileList =
tempfileList.map((file) => {compressImage(file)}).toList();
return compressedFileList ;
}
Future<File> compressImage(File tempFile) async {
String postId = Uuid().v4();
final tempDir = await getTemporaryDirectory();
final path = tempDir.path;
Im.Image? imageFile = Im.decodeImage(tempFile.readAsBytesSync());
final compressedImageFile = File('$path/img_$postId.jpg')
..writeAsBytesSync(Im.encodeJpg(imageFile!, quality: 85));
tempFile = compressedImageFile;
return tempFile;
}
i edit my function as below and it works , thank you.
Future<List<File>> compressImageList(List<File> tempfileList) async {
List<File> compressedFileList = [];
await Future.wait(tempfileList.map((file) async {
var compressImage2 = await compressImage(file);
compressedFileList.add(compressImage2);
}).toList());
return compressedFileList;
}
Not sure, haven't tested it, but try removing the {} on the 6th line (as shown above). These brackets signify that it is a type set, which is being returned by =>. If using => for a single statement you don't need to use brackets.
Do it like this;
List<File> compressImageList(List<File> tempfileList) {
List<File> compressedFiles = [];
tempfileList.forEach((file) async {
final compressedFile = await compressImage(file);
compressedFiles = [...compressedFiles, compressedFile];
});
return compressedFiles;
}

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.

Why employing getters instead of methods for internal class function in Dart?

I am reading Flutter documentation for Read and write Files, and in this very example I don't really "get" the use of a getter.
(I am new to dart)
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
return File('$path/counter.txt');
}
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file.
return file.writeAsString('$counter');
}
Are _localPath and _localFile getters or methods ?
Why not write it this way :
Future<String> _localPath() async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> _localFile() async {
final path = await _localPath;
return File('$path/counter.txt');
}
Future<File> writeCounter(int counter) async {
final file = await _localFile();
// Write the file.
return file.writeAsString('$counter');
}
getter is a method that doesn't accept any arguments. And that's why is accessed like any other field and not a method. Basically, it can always be replaced with a method with zero arguments but a getter may be a more elegant way in some cases. For example: your class have a field that holds a date of birth. And you create a getter age instead of a method calculateAge(). You don't really have to, but you can if you prefer

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.