Reputation: 18728
It seems that for some reason my SharedPreferences
don't get stored properly.
SharedPreferences p = await SharedPreferences.getInstance();
p.setInt('____debug1', 1);
Future.delayed(Duration(seconds: 3)).then((_) async {
SharedPreferences p2 = await SharedPreferences.getInstance();
print('____debug1: ${p2.getInt('____debug1')}' );
});
results in
____debug1: null
while I was expecting it to print 1
.
What could be causing this? I'm using SharedPreferences 0.5.3+4 and Flutter Doctor says
[✓] Flutter (Channel stable, v1.9.1+hotfix.2, on Mac OS X 10.14.5 18F132, locale en-SE)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] Xcode - develop for iOS and macOS (Xcode 10.3)
[✓] Android Studio (version 3.3)
[✓] IntelliJ IDEA Ultimate Edition (version 2018.3.4)
[✓] Connected device (1 available)
• No issues found!
EDIT
If I change the code to look like this
SharedPreferences p = await SharedPreferences.getInstance();
await p.setString('____debug1', '6');
print('____debug1 (1): ${p.getString('____debug1')}' );
Future.delayed(Duration(seconds: 3)).then((_) async {
SharedPreferences p2 = await SharedPreferences.getInstance();
print('____debug1 (2): ${p2.getString('____debug1')}' );
});
and update the value from 1
, 2
, 3
etc. on each execution, I get the following results:
____debug1 (1): 6
____debug1 (2): 5
so it seems that reading the value outside the Future
works as expected, but when reading it 3 seconds later inside the Future
I get a stale value...
EDIT 2 - Minimal reproducible example
_MyHomePageState
:@override
void initState() {
initAsync();
}
initAsync() async {
scheduleMicrotask(() async {
SharedPreferences p = await SharedPreferences.getInstance();
});
String x = '1'; // <-- Change me between runs!
SharedPreferences p = await SharedPreferences.getInstance();
await p.setString('x', x);
print('x (1): ${p.getString('x')}' );
Future.delayed(Duration(seconds: 3)).then((_) async {
SharedPreferences p2 = await SharedPreferences.getInstance();
print('x (2): ${p2.getString('x')}' );
});
}
x
between each run.The result that I'm getting is
First run
x (1): 1
x (2): null
Second run
x (1): 2
x (2): 1
Third run
x (1): 3
x (2): 2
... etc ...
Upvotes: 2
Views: 366
Reputation: 10953
Is not about the shared preferences but the initialization of it's singleton
The Problem
Since the code is executed on the initState() the singleton of the shared preferences is null and need to be initialized. But calling SharedPreferences.getInstance() in the microtask and in the initAsync, both asynchronous calls enter in the if (_instance == null) due to how Dart handles microtasks and events queues (Futures - Isolates - Event Loop), and the last who creates the instance will be the microtask, so the instance used in the initAsync to use setString won't update the memory cache of the shared preferences
Here is an example to understand the problem:
@override
void initState() {
initAsync();
super.initState();
}
initAsync() async {
scheduleMicrotask(() async {
print("microtask: START ");
TestClass microtask = await TestClass.getInstance("microtask");
print("microtask: FINISH (instance: ${microtask.hashCode})");
});
print("initAsync_1: START");
TestClass initAsync_1 = await TestClass.getInstance("initAsync_1");
print("initAsync_1: FINISH (instance: ${initAsync_1.hashCode})");
Future.delayed(Duration(seconds: 3)).then((_) async {
print("initAsync_2: START");
TestClass initAsync_2 = await TestClass.getInstance("initAsync_2");
print("initAsync_2: FINISH (instance: ${initAsync_2.hashCode})");
});
}
class TestClass {
static TestClass _instance;
static Future<TestClass> getInstance(String call) async {
if (_instance == null) {
print("$call: without instance before");
await Future.delayed(Duration(seconds: 3));
_instance = TestClass();
print("$call: without instance after");
} else {
print("$call: with instance");
}
return _instance;
}
}
This is the output:
initAsync_1: START
initAsync_1: without instance before
microtask: START
microtask: without instance before
initAsync_1: without instance after
initAsync_1: FINISH (instance: 674207757)
microtask: without instance after
microtask: FINISH (instance: 1059788553)
initAsync_2: START
initAsync_2: with instance
initAsync_2: FINISH (instance: 1059788553)
The Solution
You could initialize the singleton before calling the scheduleMicrotask(), just by adding a getInstance() call before that
await TestClass.getInstance("init");
And this would be the output for this example
init: without instance before
init: without instance after
initAsync_1: START
initAsync_1: with instance
microtask: START
microtask: with instance
initAsync_1: FINISH (instance: 797421450)
microtask: FINISH (instance: 797421450)
initAsync_2: START
initAsync_2: with instance
initAsync_2: FINISH (instance: 797421450)
Upvotes: 2