Empty set state what is the point? - flutter

I want to know the point behind calling setState without setting a new value to the variables.
readLocal() async {
prefs = await SharedPreferences.getInstance();
id = prefs.getString('id') ?? '';
if (id.hashCode <= peerId.hashCode) {
groupChatId = '$id-$peerId';
} else {
groupChatId = '$peerId-$id';
}
setState(() {});
}

I would say it's just a convention. The above can be re-written as
readLocal() async {
prefs = await SharedPreferences.getInstance();
setState(() {
id = prefs.getString('id') ?? '';
if (id.hashCode <= peerId.hashCode) {
groupChatId = '$id-$peerId';
} else {
groupChatId = '$peerId-$id';
}
});
}
Both will do the same thing. Calling setState(() {}) after mutating the state variable looks neat and reabable.
As per the implementation section of setState, it will below things in order.
Assertions. If any assert fails, throws exception and stops there.
Execute the callback function (final dynamic result = fn() as dynamic;)
Ask framework to rebuild(_element.markNeedsBuild();)

The documentation says [ https://docs.flutter.io/flutter/widgets/State/setState.html ]:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
The empty bracket { } is the empty callback (because you apparently don't need one):
The provided callback is immediately called synchronously. [...]
In short:
setState(() {});
is a way to tell the framework to build the state object anew, without using the possibility to pass a callback which would be called right after the build

Adding to the other answers, there is one difference.
When setState() is called on an unmounted (mounted == false) widget, it will fail. This means that whatever is wrapped inside the setState callback won't be called, whereas if you would run it outside setState it would be executed.

Related

Flutter - Waiting for an asynchronous function call return from multiple synchronous function calls

I have an async function which is called multiple times synchoronusly.
List response = await Future.wait([future, future])
Inside, it popups a form and waiting for it to be submitted or cancelled.
var val = await Navigator.push(
context,
MaterialPageRoute(builder : (context) => const TheForm())
);
The first served Future will popup the form first and waiting for the return. No problem with that. But I want the second Future to check first if the form is already popped up. If it is, it just waiting for it to conclude and receive the same returned value.
I'm aware that receiving same function return from two calls sounds crazy and impossible. I'm just looking for a way to hold the second Future call on and trigger to conclude it from somewhere else.
Kindly tell me what I was missing and I'll provide the required information.
I try to use ValueNotifier's. Unfortunately ValueNotifier.addListener() only accept a VoidCallback. As for now, this is my solution. Still looking for a better way to replace the loop.
Future future() async{
if(ison) await Future.doWhile(() async {
await Future.delayed(Duration(seconds: 1));
return ison;
});
else{
ison = true;
result = ... //Popup form
ison = false;
}
return await result;
}
It sounds like you want to coalesce multiple calls to an asynchronous operation. Make your asynchronous operation cache the Future it returns and make subsequent calls return that Future directly. For example:
Future<Result>? _pending;
Future<Result> foo() {
if (_pending != null) {
return _pending!;
}
Future<Result> doActualWork() async {
// Stuff goes here (such as showing a form).
}
return _pending = doActualWork();
}
Now, no matter how many times you do await foo();, doActualWork() will be executed at most once.
If you instead want to allow doActualWork() to be executed multiple times and just to coalesce concurrent calls, then make doActualWork set _pending = null; immediately before it returns.

How does testing for synchronous changes in an async function work in Dart's test package?

I'm following Reso Coder's video about unit tests in Flutter / Dart. In the video, he is testing a class that makes an async call to a remote service (mocked using Mockito):
Class being tested (not the full code):
Class ServiceChangeNotifier extends ChangeNotifier {
final Service _service;
ServiceChangeNotifier(Service this._service);
bool _isLoading = false;
bool get isLoading => _isLoading;
List<int> _res = [];
List<int> get res => _res;
Future<void> doService() async {
_isLoading = true;
notifyListeners();
_res = await _service.runService();
_isLoading = false;
notifyListeners();
}
Notice he's changing _isLoading to true before the service call, and then to false after.
Now, in the test, he wants to verify that the loading state gets updated correctly to true before the service call, and then to false after the service. To do so, he is setting the call to the async service method into a Future variable, then asserts the true change, then awaits on the future variable, and then asserts for false:
Unit test code
final ServiceChangeNotifier sut = ServiceChangeNotifier(serviceMockup);
test('....',
() async {
// test setup...
final future = sut.doService() // 1
expect(sut.isLoading, true); // 2
await future; // 3
expect(sut.isLoading, false); // 4
// ...
}
I understand why you can await the method before testing for true, because it will change to false before the expect(true) gets to run. What I don't understand is how the check for true (line 2 above) works. Is the logic of line 1 (future assignment) the one that triggers the execution of the doService method, or is it the await in line 3?
I suspect I'm missing something basic about how Future/await works and/or how expect() works in the test library. This question is somewhat related to this question, but here I'm not asking about the async part of the doSerivce method, but rather how the synchronous assignments to _isLoading is being evaluated in light of the asynchronous call.
When you execute an asynchronous function, as much of it as possible is executed synchronously; that is, its function body is executed until it reaches an await (which is syntactic sugar for returning a Future and registering appropriate completion callbacks).
So given:
final future = sut.doService() // 1
expect(sut.isLoading, true); // 2
await future; // 3
expect(sut.isLoading, false); // 3
sut.doService() is invoked immediately. doService's body immediately sets _isLoading = true, invokes notfifyListeners() [sic], invokes _service.runService(), and then returns a Future.
sut.isLoading at this point is true.
The Future returned by step 1 is awaited, so execution returns to the event loop. The Future from _service.runService() eventually completes, which executes the rest of doService()'s body, setting _isLoading = false and invoking notfifyListeners() again.
sut.isLoading at this point is false.

Flutter: What's a good practice for default values (concerning shared_preferences)

I have no problem with my code, it works flawlessly, so if you don't want to waste your time, don't read.
I just want to know from more experienced guys, what do you think is a better practice, so here's the thing:
_initPrefs() async {
if (prefs == null) prefs = await SharedPreferences.getInstance();
}
setSoundEnabled(bool value) async {
await _initPrefs();
print('setSoundEnabled($value)');
prefs.setBool(SharedPrefsKeys.soundEnabledKey, value);
}
//First alternative
Future<bool> isSoundEnabled() async {
await _initPrefs();
bool value = prefs.getBool(SharedPrefsKeys.soundEnabledKey) ?? true;
print('isSoundEnabled(): $value');
return value;
}
//Second alternative
Future<bool> isSoundEnabledAlternative() async {
await _initPrefs();
bool value = prefs.getBool(SharedPrefsKeys.soundEnabledKey);
if (value == null) {
value = true;
setSoundEnabled(value); //no need to await this
}
print('isSoundEnabled(): $value');
return value;
}
This is some of my code for app-settings, more specifically about wether or not sound should be enabled in the app.
In the first alternative:
In case there is no value for soundEnabledKey, I just return the default constant value true (without storing it in shared prefs).
This means, that if the user NEVER changes this setting, there will be NO value stored for soundEnabledKey and the method will always return that constant true.
In the second alternative:
In case there is no value for soundEnabledKey, I first save the default value true for that key, then I return that value.
This means, that no matter if the user ever changes this setting or not, the first time this method gets called, the app will store a value for the soundEnabledKey in shared prefs, and all subsequent calls will retrieve that value from shared prefs.
I think solution 1 is better, each method has only single responsibility, and is closed for modification because of this.
but you can initialize the soundEnabled value wherever you add your business initialization code, for the sake of consistency

How to wait for a method that is already being executed?

I'm developing a Flutter app which has some tabs inside, each of them depend on the database that is loaded on the first run. State is stored in a ScopedModel.
On every tab I have this code:
#override
void initState() {
super.initState();
loadData();
}
void loadData() async {
await MyModel.of(context).scopedLoadData();
_onCall = MyModel.of(context).onCall;
setState(() {
});
}
And this is the code snippet that matters for the ScopedModel:
Future<Null> scopedLoadData() async {
if (_isLoading) return;
_isLoading = true;
(...)
_isLoading = false;
}
If the user waits on the first tab for a few seconds everything is fine, since Database is loaded. However, if the user switches tabs right after app launch, the method scopedLoadData is still being executed so that I get a runtime error ("Unhandled Exception: NoSuchMethodError: The method 'ancestorWidgetOfExactType' was called on null.").
This exception happens because the scopedLoadData has not yet been completed. So I'm looking for a way to wait for a method that is still being executed.
Thanks!
Not sure without seeing your build method but I would start your build method with a guard clause.
if (_oncall == null) return Container(); // or Text("loading") or similar
use should be using a FutureBuilder on each tab to make sure the data is loaded before you try to build the widget... more code would be helpful
I solved the exception by getting rid of every:
setState(() { });
and implementing ScopedModelDescendant on every relevant tab on top of using notifyListeners() at the end of the database loading method.
This pulls the responsibility from the tabs for updating views and gives it to the scopedLoadData().

What is the point of adding a callback to setState and not just calling setState without one? [duplicate]

This question already has answers here:
Why does setState take a closure?
(2 answers)
Closed 4 years ago.
I am still a bit confused by the difference between these two
isBusy = false;
setState(() {
});
and
setState(() {
isBusy = true;
});
What is the difference between the two? I have read the API but unfortunately, I am still not clear on what difference does it make. I know setState calls the build method of the widget. The API states
Whenever you change the internal state of a State object, make the
change in a function that you pass to setState: setState(() { _myState
= newValue }); The provided callback is immediately called synchronously.
What exactly does this mean? can anyone give me a super simple example of when this would make a difference?
There's no difference between using setState callback or not actually.
What's the point then ?
This is made voluntarily to prevent mistakes in handling asynchronous data.
By using the callback, there's a mistake you cannot do:
function() async {
setState(() {});
myState = await future;
}
This causes a problem because if your future doesn't finish synchronously, build method will be called with an invalid state.
By using the callback you are forced to do the following:
function() async {
final value = await future;
setState(() {
myState = value;
});
}
This time, it doesn't cause problems because the future is awaited before the setState.
Can't I make an async callback and stil have the issue?
No.
Because setState method internally check that the callback does not return a future. And if it does, it will throw