Magnus
Magnus

Reputation: 18728

SharedPreferences not getting stored between calls

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

  1. Create a new Flutter app (the "Press-a-button-counter" will do).
  2. Add the following code to _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')}' );
  });
}
  1. Change the value of 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

Answers (1)

Pablo Barrera
Pablo Barrera

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

Related Questions