Does the following Flutter reading / writing files document have wasteful implementation? - dart

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.

Related

how to make singleton class with some initialization code?

I have tried the answers in here How do you build a Singleton in Dart?
but I can't achieve what I want. so basically I want to make a Shared Preference Service as a singleton class. currently my code is like this. this is just a regular class, not a singleton.
class SharedPreferenceService {
late SharedPreferences _prefs;
SharedPreferenceService() {
SharedPreferences.getInstance().then((value) => _prefs = value);
}
Future<void> setIntroPagesHaveBeenViewed() async {
await _prefs.setBool(SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED, true);
}
Future<bool> checkIfIntroPagesHaveBeenViewed() async {
return _prefs.getBool(SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED) ?? false;
}
}
I need a singleton class, but when the instance is initialize for the first time, I also need to initialize _pref , so then I can access that _pref on the methods
Your problem is that initialization is asynchronous.
That means that the first time the singleton instance is accessed, that access needs to be asynchronous too (and so does any further access which happens before the initialization completes). However, the usage pattern of a singleton like this is such that you don't know which access is the first. So you have to make every access asynchronous.
Example:
class SharedPreferenceService {
static final Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
Future<void> setIntroPagesHaveBeenViewed() async {
await (await _prefs).setBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED, true);
}
Future<bool> checkIfIntroPagesHaveBeenViewed() async {
return (await _prefs).getBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED) ?? false;
}
}
If all the methods are asynchronous anyway, that extra delay is not going to be a problem.
If you really, really only want to do that extra await if absolutely necessary,
you can cache the value, like you try to here:
class SharedPreferenceService {
static final Future<SharedPreferences> _prefsFuture = SharedPreferences.getInstance();
static SharedPreferences? _prefs;
Future<void> setIntroPagesHaveBeenViewed() async {
var prefs = _prefs ??= await _prefsFuture;
await _prefs.setBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED, true);
}
Future<bool> checkIfIntroPagesHaveBeenViewed() async {
var prefs = _prefs ??= await _prefsFuture;
return _prefs.getBool(
SharedPreferenceKey.INTRODUCTION_PAGES_HAVE_BEEN_VIEWED) ?? false;
}
}

dart await on constructor

What pattern should I use in this example to load and process some data. As value returns a value, it's not acceptable to have d as a Future. How can I get the constructor to wait until load has completed before continuing?
void main() {
var data = new Data(); // load data
print(data.value()); // data.d is still null
}
class Data {
String d;
Data() {
load();
}
Future<void> load() async {
d = await fn(); // some expensive function (e.g. loading a database)
}
String value() {
return d;
}
}
You cannot make a constructor asynchronous.
An asynchronous function needs to return a Future, and a constructor needs to return an instance of the class itself. Unless the class is a future, the constructor cannot be asynchronous (and even then, it's not really the same thing, and you can't use async/await).
So, if your class needs asynchronous set-up, you should provide the user with a static factory method instead of a constructor. I'd usually hide the constructor then.
class Data {
String _d;
Data._();
static Future<Data> create() async {
var data = Data._();
await data._load();
return data;
}
Future<void> _load() async {
_d = await fn();
}
String get value => _d;
}
As an alternative design, I wouldn't even put the load method on the class, just do the operation in the static factory method:
class Data {
String _d;
Data._(this._d);
static Future<Data> create() async => Data._(await fn());
String get value => _d;
}
Obviously other constraints might require that load has access to the object.

Generate one file for a list of parsed files using source_gen in dart

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

What are the pros and cons of async, when to and when not to use it and what other alternatives to callback are there?

callbacks or asynchronous methods or other options
A solution to the callback plague is "await" and "async" or more specifacally 'dart:async' library.
Now, what is the cost of asynchrony?
When should we not use them?
What are the other alternatives?
The below is a badly coded non-polymer custom element that acts like a messageBox in desktop environment. It gives me less braces and parenthesis-es but requires the caller to be also async or use "show().then((v){print(v);});" pattern. Should I avoid the pattern like this?
Is callback better? Or there is an even smarter way?
Polling version
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method
///within the callback you can pass on calling append .
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_)=>e.hide());
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
this.hide();
_modal_returns = e.text;
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
this.hidden=false;
await wait_for_input();
print(_modal_returns);
return _modal_returns;
}
wait_for_input() async{
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
void hide(){
this.hidden=true;
}
Future delay() async{
return new Future.delayed(
new Duration(milliseconds: 100));
}
}
Non-polling version
In response to Günter Zöchbauer's wisdom(avoid polling), posting a version that uses a completer. Thanks you as always Günter Zöchbauer:
import 'dart:html';
import 'dart:async';
void init(){
document.registerElement('list-modal',ListModal);
}
class ListModal extends HtmlElement{
ListModal.created():super.created();
String _modal_returns="";
Completer _completer;
void set modal_returns(String v){
///use the modal_returns setter to
///implement a custom behaviour for
///the return value of the show method.
///Use this setter within the callback for
///append. Always call hide() after
///setting modal_returns.
_modal_returns=v;
}
factory ListModal(){
var e = new Element.tag('list-modal');
e.style..backgroundColor="olive"
..position="absolute"
..margin="auto"
..top="50%"
..verticalAlign="middle";
var close_b = new DivElement();
close_b.text = "X";
close_b.style..right="0"
..top="0"
..margin="0"
..verticalAlign="none"
..backgroundColor="blue"
..position="absolute";
close_b.onClick.listen((_){
e.hide();
});
e.append(close_b,(_){e.hide();});
e.hide();
return e;
}
#override
ListModal append(
HtmlElement e,
[Function clickHandler=null]
){
super.append(e);
if(clickHandler!=null) {
e.onClick.listen(clickHandler);
}else{
e.onClick.listen((_){
_modal_returns = e.text;
this.hide();
});
}
return this;
}
Future<String> show() async{
_modal_returns = '';
_completer = new Completer();
this.hidden=false;
return _completer.future;
}
void hide(){
hidden=true;
_completer?.complete(_modal_returns);
_completer=null;
}
}
Usually there is no question whether async should be used or not. Usually one would try to avoid it. As soon as you call an async API your code goes async without a possibility to choose if you want that or not.
There are situations where async execution is intentionally made async. For example to split up large computation in smaller chunks to not starve the event queue from being processed.
On the server side there are several API functions that allow to choose between sync and async versions. There was an extensive discussion about when to use which. I'll look it up and add the link.
The disadvantages of using async / await instead of .then() should be minimal.
minimal Dart SDK version with async / await support is 1.9.1
the VM needs to do some additional rewriting before the code is executed the first time, but this is usually neglectable.
Your code seems to do polling.
wait_for_input() async {
while(_modal_returns=="" && !this.hidden){
await delay();
}
}
This should be avoided if possible.
It would be better to let the modal manage its hidden state itself (by adding a hide() method for example), then it doesn't have to poll whether it was hidden from the outside.

Future is completed before function-call is done

I'm working with two functions, both of them should return a future. A third function gets called when both of them are done. Right now the future is returned too early, so that my third function is called before my second function is completed.
Function1:
static var getObjectDataCompleter = new Completer();
static var fillObjectCompleter = new Completer();
static Future getObjectData(List jsonMap) {
for (int i = 0; i < jsonMap.length; i++) {
fillObjectCompleter = new Completer();
var request = buildRequest("GET", resourceUrl);
request.send();
request.onLoadEnd.listen((event) => fillObject(request));
}
if(fillObjectCompleter.isCompleted) {
getObjectDataCompleter.complete(null);
}
return getObjectDataCompleter.future;
}
Function2:
static Future fillObject(HttpRequest request) {
String responseText = request.response;
List stringJson = JSON.decode(responseText);
fillObjectCompleter.complete(null);
return fillObjectCompleter.future;
}
Function1 is returning the future before the call "fillObject()" is completed.
What am I doing wrong?
The function1-future should be returned when the "for-loop" is done and all "fillObject-calls" are completed.
Async code is just scheduled for later execution and the sync code continues executing without waiting for the async code. The method you pass ot Future.then(...) is executed when the scheduled async code is finished. You find a lot of such questions and examples tagged [:dart-async:] here on StackOverflow.
I have a hard time figuring out what you actually try to accomplish. Can you please explain in prosa what you actually try to accomplish, then I can try to fix your code example to do what you want it to do.
Usually there is no need to use a Completer in custom async functions. You just have to ensure that nested async calls are properly chained by always returning the future of the call.
See these two lines of the following code as example. The returns are important for the example to work.
return async.Future.forEach(jsonMap, (item) {
return request.onLoadEnd.first.then((event) => fillObject(event.target));
The Future returned from getObjectData completes after the response of all requests are processed.
import 'dart:html' as dom;
import 'dart:async' as async;
import 'dart:convert' show JSON;
class Xxx {
static async.Future getObjectData(List jsonMap) {
return async.Future.forEach(jsonMap, (item) {
//var request = new dom.HttpRequest();
//request.open("GET", "https://www.googleapis.com/discovery/v1/apis?fields=");
var request = buildRequest("GET", resourceUrl);
request.send();
return request.onLoadEnd.first.then((event) => fillObject(event.target));
});
}
static fillObject(dom.HttpRequest request) {
print('fillObject');
String responseText = request.response;
List stringJson = JSON.decode(responseText);
}
}
void main() {
var json = ['a', 'b', 'c'];
Xxx.getObjectData(json).then((_) => print('done'));
}
See https://www.dartlang.org/articles/event-loop for more details about async execution.

Resources