I have this function:
Future<String> load(SharedPreferences prefs, String fileName) async {
prefs = await SharedPreferences.getInstance();
String jsonString = prefs.getString(fileName) ?? "";
if (jsonString.isNotEmpty) {
return jsonString;
}else{
return ...
}
}
What should I return in the else case? I tried with "" but it doesn't work.
Shared Preferences
In Flutter, Shared Preferences are used to store primitive data (int, double, bool, string, and stringList). This data is associated with the app, so when the user uninstalls your app, the data will also be deleted.
Get the plugin
The shared_preferences plugin from pub is a wrapper around Android SharedPreferences and iOS NSUserDefaults. You can get this plugin by adding the shared_preferences line to your pubspec.yaml file in the dependencies section.
dependencies:
shared_preferences: '>=0.5.12+2 <2.0.0'
You can change the version number to whatever the current one is, but anything less than 2.0 should be compatible.
Import the package
In whichever file you need the Shared Preferences, add the following import:
import 'package:shared_preferences/shared_preferences.dart';
Reading and writing data
To get the shared preferences object you can do the following:
final prefs = await SharedPreferences.getInstance();
This will be used for all of the following examples.
int
read: final myInt = prefs.getInt('my_int_key') ?? 0;
write: prefs.setInt('my_int_key', 42);
double
read: final myDouble = prefs.getDouble('my_double_key') ?? 0.0;
write: prefs.setDouble('my_double_key', 3.14);
bool
read: final myBool = prefs.getBool('my_bool_key') ?? false;
write: prefs.setBool('my_bool_key', true);
string
read: final myString = prefs.getString('my_string_key') ?? '';
write: prefs.setString('my_string_key', 'hello');
stringList
read: final myStringList = prefs.getStringList('my_string_list_key') ?? [];
write: prefs.setStringList('my_string_list_key', ['horse', 'cow', 'sheep']);
Removing data
You can remove any saved data by supplying the key name:
prefs.remove('my_int_key');
I rarely find a need to do that, though. I just overwrite the old data or ignore it. You shouldn't store any sensitive data in Shared Preferences.
See also
Shared Preferences Service in Flutter for Code Maintainability
Documentation: Storing key-value data on disk
What are the ?? double question marks in Dart?
How to create an empty list in Dart
The answer is "it depends". Namely, it depends on what exactly you are doing with the result of this function, and what a good empty default value means in that context.
Assuming you're decoding the returned JSON string into a Map<String, dynamic>, then a good default value might be the empty map. In that case, you could reformulate your function as follows:
Future<String> loadJSON(final String fileName) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String jsonString = prefs.getString(fileName);
if (jsonString != null && jsonString.isNotEmpty) {
return jsonString;
}
return "{}"; // default value
}
final String jsonString = await loadJSON("test.json");
final Map<String, dynamic> jsonData = json.decode(jsonString);
However, it probably makes more sense to reformulate this procedure as a slightly higher-level function returning actual map values:
Future<Map<String, dynamic>> loadData(final String fileName) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String jsonString = prefs.getString(fileName);
if (jsonString != null && jsonString.isNotEmpty) {
return json.decode(jsonString);
}
return Map(); // default value
}
final Map<String, dynamic> jsonData = await loadData("test.json");
Related
I will like to store a list of values from an API locally. Does shared preference allow storing List because each time i try to save my values in shared preference, i get " type 'List' is not a subtype of type 'List'"
Future fetchAllProduct() async{
try{
for(int j = 1; j < 3; j++){
final response = await
http.get('https://website/api/?page=1',
);
List result = json.decode(response.body);
products.addAll(result);
//Saving fetched product list
SharedPreferences preferences = await SharedPreferences.getInstance();
preferences.setStringList('prds', products);
//final prds = preferences.getStringList('prd');
}
}catch (ex){
print('$ex');
}
print(products);
}
I'am expecting to see a list like this
[{ProductID: 155, Name: Multi-vit, Description: Multi-vit, CostPrice: 0.0, SalePrice: 80, EatOutPrice: 80, CategoryID: 976, Barcode: , TaxRateID: null, }]
According to shared_preference's repo it should be possible to store a List. Their test case have a slightly different syntax than yours:
preferences.setStringList('List', kTestValues2['flutter.List']) which is different from your approach.
I assume you have defined products as a List.
EDIT:
Why are you storing a list inside a list? What happens if you make your result a String?
An easy way would be to encode the List as a JSON and save that as a string.
import 'dart:convert' show json;
import 'package:shared_preferences/shared_preferences.dart';
void setList(String key, List<dynamic> value) async {
await setString(key, json.encode(value));
}
setList('key', []);
Hi I'm having issue where getting the stored value from Shared Preference and displaying it. It giving Future doesn't contain length instance error. My code below.
Save Shared Preference Value Code
Future<String> saveSearchQuery(String squery) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
if(prefs.getStringList("searchhistory") == null){
final List<String> recentSearch = [];
recentSearch.insert(0, squery);
prefs.setStringList("searchhistory", recentSearch);
}else{
final recentSearch = prefs.getStringList("searchhistory");
if(recentSearch.contains(squery)){
recentSearch.forEach((e) => print(e));
}else{
recentSearch.insert(0, squery);
}
prefs.setStringList("searchhistory", recentSearch);
}
return prefs.commit().toString();
}
Get Shared Preference Value Code
Future<dynamic> getSearchHistory() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List searchHistory = prefs.getStringList("searchhistory");
return searchHistory.toList();
}
Please help. Thank you.
So basically i have this piece of working code:
List<User> users = List();
await Future.forEach(querySnapshot.documents, (doc) async {
final snapshot = await doc['user'].get();
users.add(User(id: snapshot["id"], name: snapshot["mail"]));
});
return users;
It's working fine and does exactly what I need but I was wondering if there was a way to somehow change it to a map, such as:
return querySnapshot.documents.map((doc) async {
final snapshot = await doc['user'].get();
User(id: snapshot["id"], name: snapshot["mail"]);
}).toList{growable: true};
The problem when I do that is that it says: a value of type List< Future< Null>> can't be assigned to a variable of type List< User>.
So I was wondering if it was possible or if the only way is with a Future.forEach
To turn a list of Futures into a single Future, use Future.wait from dart:async. (Also don't forget to return the user object).
final List<User> = await Future.wait(querySnapshot.documents.map((doc) async {
final snapshot = await doc['user'].get();
return User(id: snapshot["id"], name: snapshot["mail"]);
}));
from the flutter doc:
class CounterStorage {
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<int> readCounter() async {
try {
final file = await _localFile;
// Read the file
String contents = await file.readAsString();
return int.parse(contents);
} catch (e) {
// If we encounter an error, return 0
return 0;
}
}
Future<File> writeCounter(int counter) async {
final file = await _localFile;
// Write the file
return file.writeAsString('$counter');
}
}
Both readCounter() and writeCounter() call the _localPath getter each time they're called.
My question is :
isn't this a little wasteful? Wouldn't it be better to wait for the _localFile in the constructor of CounterStorage, and store it in a class member, as opposed to getting the _localPath and _localPath each and every time?
Can someone please suggest such an implementation?
It depends what you mean by wasteful, and the contract of getApplicationDocumentsDirectory.
For example, if it is possible for getApplicationDocumentsDirectory() to return a different path the next time it is called (for example, if a new user logs in, possibly - I'm not sure of the details) then this is completely correct.
If it is guaranteed this value will never change, it is possible to optimize further, but showing optimizations is probably not the goal of sample documentation. If you're interested, two ideas I can think of are:
Create a static final field:
class CounterStorage {
// Static fields in Dart are lazy; this won't get sent until used.
static final _localPath = getApplicationDocumentsDirectory().then((p) => p.path);
// ...
}
This is my preference if CounterStorage has other methods or fields that are uesful without waiting for _localPath to be resolved. In the above example, there are none, so I would prefer:
Create a static async method to create CounterStorage
import 'package:meta/meta.dart';
class CounterStorage {
// You could even combine this with the above example, and make this a
// static final field.
static Future<CounterStorage> resolve() async {
final localPath = await getApplicationDocumentsDirectory();
return new CounterStorage(new File(this.localPath));
}
final File _file;
// In a test you might want to use a temporary directory instead.
#visibleForTesting
CounterStorage(this._file);
Future<int> readCount() async {
try {
final contents = await _file.readAsString();
return int.parse(contents);
} catch (_) {
return 0;
}
}
}
This makes the process of retrieving the File happen potentially once per app.
I have a list of models that I need to create a mini reflective system.
I analyzed the Serializable package and understood how to create one generated file per file, however, I couldn't find how can I create one file for a bulk of files.
So, how to dynamically generate one file, using source_gen, for a list of files?
Example:
Files
user.dart
category.dart
Generated:
info.dart (containg information from user.dart and category.dart)
Found out how to do it with the help of people in Gitter.
You must have one file, even if empty, to call the generator. In my example, it is lib/batch.dart.
source_gen: ^0.5.8
Here is the working code:
The tool/build.dart
import 'package:build_runner/build_runner.dart';
import 'package:raoni_global/phase.dart';
main() async {
PhaseGroup pg = new PhaseGroup()
..addPhase(batchModelablePhase(const ['lib/batch.dart']));
await build(pg,
deleteFilesByDefault: true);
}
The phase:
batchModelablePhase([Iterable<String> globs =
const ['bin/**.dart', 'web/**.dart', 'lib/**.dart']]) {
return new Phase()
..addAction(
new GeneratorBuilder(const
[const BatchGenerator()], isStandalone: true
),
new InputSet(new PackageGraph.forThisPackage().root.name, globs));
}
The generator:
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
import 'package:glob/glob.dart';
import 'package:build_runner/build_runner.dart';
class BatchGenerator extends Generator {
final String path;
const BatchGenerator({this.path: 'lib/models/*.dart'});
#override
Future<String> generate(Element element, BuildStep buildStep) async {
// this makes sure we parse one time only
if (element is! LibraryElement)
return null;
String libraryName = 'raoni_global', filePath = 'lib/src/model.dart';
String className = 'Modelable';
// find the files at the path designed
var l = buildStep.findAssets(new Glob(path));
// get the type of annotation that we will use to search classes
var resolver = await buildStep.resolver;
var assetWithAnnotationClass = new AssetId(libraryName, filePath);
var annotationLibrary = resolver.getLibrary(assetWithAnnotationClass);
var exposed = annotationLibrary.getType(className).type;
// the caller library' name
String libName = new PackageGraph.forThisPackage().root.name;
await Future.forEach(l.toList(), (AssetId aid) async {
LibraryElement lib;
try {
lib = resolver.getLibrary(aid);
} catch (e) {}
if (lib != null && Utils.isNotEmpty(lib.name)) {
// all objects within the file
lib.units.forEach((CompilationUnitElement unit) {
// only the types, not methods
unit.types.forEach((ClassElement el) {
// only the ones annotated
if (el.metadata.any((ElementAnnotation ea) =>
ea.computeConstantValue().type == exposed)) {
// use it
}
});
});
}
});
return '''
$libName
''';
}
}
It seems what you want is what this issue is about How to generate one output from many inputs (aggregate builder)?
[Günter]'s answer helped me somewhat.
Buried in that thread is another thread which links to a good example of an aggregating builder:
1https://github.com/matanlurey/build/blob/147083da9b6a6c70c46eb910a3e046239a2a0a6e/docs/writing_an_aggregate_builder.md
The gist is this:
import 'package:build/build.dart';
import 'package:glob/glob.dart';
class AggregatingBuilder implements Builder {
/// Glob of all input files
static final inputFiles = new Glob('lib/**');
#override
Map<String, List<String>> get buildExtensions {
/// '$lib$' is a synthetic input that is used to
/// force the builder to build only once.
return const {'\$lib$': const ['all_files.txt']};
}
#override
Future<void> build(BuildStep buildStep) async {
/// Do some operation on the files
final files = <String>[];
await for (final input in buildStep.findAssets(inputFiles)) {
files.add(input.path);
}
String fileContent = files.join('\n');
/// Write to the file
final outputFile = AssetId(buildStep.inputId.package,'lib/all_files.txt');
return buildStep.writeAsString(outputFile, fileContent);
}
}