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;
}
}
Related
Let's assume that an initialization of MyComponent in Dart requires sending an HttpRequest to the server. Is it possible to construct an object synchronously and defer a 'real' initialization till the response come back?
In the example below, the _init() function is not called until "done" is printed. Is it possible to fix this?
import 'dart:async';
import 'dart:io';
class MyComponent{
MyComponent() {
_init();
}
Future _init() async {
print("init");
}
}
void main() {
var c = new MyComponent();
sleep(const Duration(seconds: 1));
print("done");
}
Output:
done
init
Probably the best way to handle this is with a factory function, which calls a private constructor.
In Dart, private methods start with an underscore, and "additional" constructors require a name in the form ClassName.constructorName, since Dart doesn't support function overloading. This means that private constructors require a name, which starts with an underscore (MyComponent._create in the below example).
import 'dart:async';
import 'dart:io';
class MyComponent{
/// Private constructor
MyComponent._create() {
print("_create() (private constructor)");
// Do most of your initialization here, that's what a constructor is for
//...
}
/// Public factory
static Future<MyComponent> create() async {
print("create() (public factory)");
// Call the private constructor
var component = MyComponent._create();
// Do initialization that requires async
//await component._complexAsyncInit();
// Return the fully initialized object
return component;
}
}
void main() async {
var c = await MyComponent.create();
print("done");
}
This way, it's impossible to accidentally create an improperly initialized object out of the class. The only available constructor is private, so the only way to create an object is with the factory, which performs proper initialization.
A constructor can only return an instance of the class it is a constructor of (MyComponent). Your requirement would require a constructor to return Future<MyComponent> which is not supported.
You either need to make an explicit initialization method that needs to be called by the user of your class like:
class MyComponent{
MyComponent();
Future init() async {
print("init");
}
}
void main() async {
var c = new MyComponent();
await c.init();
print("done");
}
or you start initialization in the consturctor and allow the user of the component to wait for initialization to be done.
class MyComponent{
Future _doneFuture;
MyComponent() {
_doneFuture = _init();
}
Future _init() async {
print("init");
}
Future get initializationDone => _doneFuture
}
void main() async {
var c = new MyComponent();
await c.initializationDone;
print("done");
}
When _doneFuture was already completed await c.initializationDone returns immediately otherwise it waits for the future to complete first.
I agree, an asynchronous factory function would help Dart devs with this problem. #kankaristo has IMHO given the best answer, a static async method that returns a fully constructed and initialized object. You have to deal with the async somehow, and breaking the init in two will lead to bugs.
I'm currently migrating an App's logic code from C# to Dart and I'm looking for a similiar collection type in Dart to C#s BlockingCollection. I basically want a queue where i can iterate infinitely. If the queue is empty it just waits until a new element is added.
Is that possible in Dart?
Best
You can use a StreamController.
Here I translated the first C# example for BlockingCollection
using System;
using System.Collections.Concurrent;
using System.Threading;
using System.Threading.Tasks;
class BlockingCollectionDemo
{
static async Task Main()
{
await AddTakeDemo.BC_AddTakeCompleteAdding();
}
}
class AddTakeDemo
{
// Demonstrates:
// BlockingCollection<T>.Add()
// BlockingCollection<T>.Take()
// BlockingCollection<T>.CompleteAdding()
public static async Task BC_AddTakeCompleteAdding()
{
using (BlockingCollection<int> bc = new BlockingCollection<int>())
{
// Spin up a Task to populate the BlockingCollection
Task t1 = Task.Run(() =>
{
bc.Add(1);
bc.Add(2);
bc.Add(3);
bc.CompleteAdding();
});
// Spin up a Task to consume the BlockingCollection
Task t2 = Task.Run(() =>
{
try
{
// Consume consume the BlockingCollection
while (true) Console.WriteLine(bc.Take());
}
catch (InvalidOperationException)
{
// An InvalidOperationException means that Take() was called on a completed collection
Console.WriteLine("That's All!");
}
});
await Task.WhenAll(t1, t2);
}
}
}
to dart using a StreamController instead of BlockingCollection, and Future instead of Task.
import 'dart:async';
Future<void> main() async {
await addTakeCompleteAdding();
}
// Demonstrates:
// StreamController<T>.add()
// StreamController<T>.stream
// StreamController<T>.close()
Future<void> addTakeCompleteAdding() async {
StreamController<int> bc = StreamController<int>();
// Spin up a Future to populate the StreamController
Future<void> t1 = Future(() {
bc.add(1);
bc.add(2);
bc.add(3);
bc.close();
});
// Spin up a Future to consume the StreamController
Future<void> t2 = Future(() async {
// Consume consume the StreamController
await for (final element in bc.stream) {
print(element);
}
// Exits the loop when the stream is completed/closed
print("That's All!");
});
await Future.wait([t1, t2]);
}
That said, the StreamController differs a bit from BlockingCollection in that it is not a queue. A Stream in dart by default, can only have one subscription, unless you create a broadcast stream. Stream is more like an async enumerable in C#.
If you really need a queue data structure you can use the async package, which has a StreamQueue class that you can use to wrap the stream from the StreamController.
Here is the above code modified to use a StreamQueue:
import 'dart:async';
import 'package:async/async.dart';
Future<void> main() async {
await addTakeCompleteAdding();
}
// Demonstrates:
// StreamController<T>.add()
// StreamController<T>.stream
// StreamController<T>.close()
// StreamQueue<T>.next
Future<void> addTakeCompleteAdding() async {
StreamController<int> bc = StreamController<int>();
StreamQueue<int> queue = StreamQueue<int>(bc.stream);
// Spin up a Future to populate the StreamController
Future<void> t1 = Future(() {
bc.add(1);
bc.add(2);
bc.add(3);
bc.close();
});
// Spin up a Future to consume the StreamQueue
Future<void> t2 = Future(() async {
try {
while (true) {
// Consume consume the StreamQueue
print(await queue.next);
}
} on StateError catch (e) {
// A StateError means that next was called on a completed collection
print("That's all!");
}
});
await Future.wait([t1, t2]);
}
You can also write your own queue, based on futures instead of a stream:
import "dart:async" show Completer;
import "dart:collection" show Queue;
abstract class BlockingQueue<T> {
factory BlockingQueue() = _BlockingQueue;
Future<T> removeNext();
void add(T value);
}
class _BlockingQueue<T> implements BlockingQueue<T> {
final Queue<T> _writes = Queue();
final Queue<Completer<T>> _reads = Queue();
Future<T> removeNext() {
if (_writes.isNotEmpty) return Future.value(_writes.removeFirst());
var completer = Completer<T>();
_reads.add(completer);
return completer.future;
}
void add(T value) {
if (_reads.isNotEmpty) {
_reads.removeFirst().complete(value);
} else {
_writes.add(value);
}
}
}
You can also consider a double-blocking queue, where the add method also "blocks" if there is no-one to accept the value yet. It's not even that hard,.
import "dart:async" show Completer;
import "dart:collection" show Queue;
abstract class BlockingQueue<T> {
factory BlockingQueue() = _BlockingQueue;
Future<T> removeNext();
Future<void> add(T value);
}
class _BlockingQueue<T> implements BlockingQueue<T> {
final Queue<T> _writes = Queue();
final Queue<Completer<T>> _completers = Queue();
Future<T> removeNext() {
if (_writes.isNotEmpty) {
assert(_completers.isNotEmpty);
var completer = _completers.removeFirst();
completer.complete(_writes.removeFirst());
return completer.future;
}
var completer = Completer<T>();
_completers.add(completer);
return completer.future;
}
Future<void> add(T value) {
if (_writes.isEmpty && _completers.isNotEmpty) {
var completer = _completers.removeFirst();
completer.complete(value);
return completer.future;
}
var completer = Completer<T>();
_completers.add(completer);
_writes.add(value);
return completer.future;
}
}
That said, if you want to use a for (... in ...)-like loop, you probably do want to go with a Stream and use await for (... in theStream).
I am still a beginner on dart flutter, now I am trying to retrieve data from the REST API and socket.IO. at this time I have a confusing problem, I have tried searching on the internet for 3 days, but there is no solution. I have async and await scripts, but the function I added await doesn't give any response and still pause.
it is assumed that I have two different files, the first is the main file and the second is the helper file.
main.dart
Future<List<ChatTile>> fetchChat(socketutil,id) async {
socketutil.join(id); //STACK IN HERE
SharedPreferences prefs = await SharedPreferences.getInstance();
String messagePrefs = prefs.getString('messagePrefs');
print("DUA");
return await compute(parseListChat, messagePrefs);
}
helper.dart
Future<void> join(String id_room) async {
String jsonData ='{"room_id" : "$id_room","user_id" : "5a91687811138e74009839c9","user_name" : "Denis Muhammad Ramdan","user_photo" : "photo.jpg","user_status" : "1"}';
socketIO.sendMessage("join", jsonData, null);
//subscribe event
return await socketIO.subscribe("updateMessageList", (result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('messagePrefs', result);
print('SATU');
return await result;
});
}
my question is there something wrong with my code, and how is the best way?
many thanks,
I suggest you to add await_only_futures to your analyzer config
analysis_options.yaml
lint:
rules:
- await_only_futures
You also don't need to do return await something since your function already return a future, this is redondant.
And from what I see of the socketio subscribe method, it does not return the result like you expect but use a callback and does not return it (https://pub.dartlang.org/documentation/flutter_socket_io/latest/flutter_socket_io/SocketIO/subscribe.html)
to handle this you should use a Completer
final completer = Completer<String>()
socketIO.subscribe("updateMessageList", (result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('messagePrefs', result);
socketIO.unSubscribe("updateMessageList");
completer.complete(result);
});
return completer.future;
you probably want to handle error when there is using completer.completeError(error)
Update
You can alos convert the subscription to a Dart Stream to handle more case.
StreamController<String> controller;
Stream<String> get onUpdateMessageList {
if (controller != null) return controller.stream;
constroller = StreamController<String>.broadcast(
onCancel: () => socketIO.unSubscribe("updateMessageList"),
);
socketIO.subscribe("updateMessageList", constroller.add);
return controller.stream;
}
Future<StreamSubscription> join(String id_room) async {
...
return onUpdateMessageList.listen((result) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('messagePrefs', result);
});
}
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.
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.