23

Problem: Shared preference bool value is null on startup even though I have given it a value if prefs.getBool('myBool') returns null (though my shared preferences value should already be set and saved). It does, however, work by the time I press a button (I assume because it has finished running the async code).

Question: How can I force shared preferences to load on startup (so my value is not null) without having to press the print button?

Example Code:

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  createState() => new MyAppState();
}

class MyAppState extends State<MyApp> {
  final padding = const EdgeInsets.all(50.0);

  @override
  void initState() {
    super.initState();

    MySharedPreferences.load();
    MySharedPreferences.printMyBool();
  }

  @override
    Widget build(BuildContext context) {
      return new MaterialApp(
        home: new Scaffold(
          body: new Padding(
            padding: padding,
            child: new Column(
              children: <Widget>[
                new Padding(
                  padding: padding,
                  child: new RaisedButton(
                    child: new Text('Save True'),
                    onPressed: () => MySharedPreferences.save(myBool: true),
                  ),
                ),
                new Padding(
                  padding: padding,
                  child: new RaisedButton(
                    child: new Text('Save False'),
                    onPressed: () => MySharedPreferences.save(myBool: false),
                  ),
                ),
                new Padding(
                  padding: padding,
                  child: new RaisedButton(
                    child: new Text('Print myBool'),
                    onPressed: () => MySharedPreferences.printMyBool(),
                ),
              ),
            ],
          ),
        ), 
      ),
    );
  }
}

class MySharedPreferences {
  static bool _myBool;

  static void load() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    _myBool = prefs.getBool('myBool') ?? false;
  }

  static void save({myBool: bool}) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    _myBool = myBool;
    await prefs.setBool('myBool', _myBool);
  }

  static void printMyBool() {
    print('myBool: ${_myBool.toString()}');
  }
}

Results: On startup, myBool: null is printed. Once the button is pressed, myBool: false/true is then printed.

PityTheFool
  • 298
  • 1
  • 3
  • 11

5 Answers5

15

Your problem is that you call load() and printMyBool() in quick succession. Because load() is async calling it hasn't executed any of its code, it has only scheduled it. So, printMyBool executes before the body of load.

There's no need to put static functions in a class - just declare them as top level functions. Also, you don't really want _myBool to be a global - it should be part of a Widget's state. That way when you update it, Flutter knows what parts of your tree to redraw.

I've restructured your code to remove the redundant statics.

import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget {
  MyApp({Key key}) : super(key: key);

  @override
  createState() => new MyAppState();
}

const EdgeInsets pad20 = const EdgeInsets.all(20.0);
const String spKey = 'myBool';

class MyAppState extends State<MyApp> {
  SharedPreferences sharedPreferences;

  bool _testValue;

  @override
  void initState() {
    super.initState();

    SharedPreferences.getInstance().then((SharedPreferences sp) {
      sharedPreferences = sp;
      _testValue = sharedPreferences.getBool(spKey);
      // will be null if never previously saved
      if (_testValue == null) {
        _testValue = false;
        persist(_testValue); // set an initial value
      }
      setState(() {});
    });
  }

  void persist(bool value) {
    setState(() {
      _testValue = value;
    });
    sharedPreferences?.setBool(spKey, value);
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(
        body: new Center(
          child: new Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              new Padding(
                padding: pad20,
                child: new Text(
                    _testValue == null ? 'not ready' : _testValue.toString()),
              ),
              new Padding(
                padding: pad20,
                child: new RaisedButton(
                  child: new Text('Save True'),
                  onPressed: () => persist(true),
                ),
              ),
              new Padding(
                padding: pad20,
                child: new RaisedButton(
                  child: new Text('Save False'),
                  onPressed: () => persist(false),
                ),
              ),
              new Padding(
                padding: pad20,
                child: new RaisedButton(
                  child: new Text('Print myBool'),
                  onPressed: () => print(_testValue),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
Richard Heap
  • 48,344
  • 9
  • 130
  • 112
  • 1
    I have the same problem - my sharedPreferences return null on startup. Why can they not make sharedPreferences synchronous like Android sharedPreferences instead of async? Async just causes problems. – Simon May 20 '18 at 09:42
  • 1
    it's a noisy problem? I have bottom navigation item that inflate right after build but sharedpreference return null when the first navigation item show? Really big problem here. – kemdo Jul 09 '18 at 01:40
  • Check this answer for more information https://stackoverflow.com/questions/51215064/flutter-access-stored-sharedpreference-value-from-other-pages/51228189#51228189 – Richard Heap Jul 09 '18 at 01:53
  • None of those answers helped me. My [Shared_Preferences do not persist](https://stackoverflow.com/questions/54523760/flutter-sharedpreference-do-not-persist). – Jérémy Feb 05 '19 at 21:58
  • This solution doesn't work. You can't set the state before the widget loaded. – Ruan Apr 12 '20 at 13:02
  • @Ruan the `setState` is in the `then`, so shouldn't be executed immediately (except in unusual circumstances). – Richard Heap Apr 12 '20 at 20:13
  • @RichardHeap It's still a race condition that you shouldn't have in your code. Because my getInstance() executed faster than normally, it kept breaking. – Ruan Apr 12 '20 at 21:02
  • @Sid I used a package that waits for the layout to load https://pub.dev/packages/after_layout – Ruan Dec 21 '20 at 11:38
8

Add condition ?? when you get value from preference.

int intValue = prefs.getInt('intValue') ?? 0;
Jai Khambhayta
  • 4,198
  • 2
  • 22
  • 29
2

Use conditional operator(??) to assign values if shared preference returning null

bool _testValue;

@override
  void initState() {
    super.initState();
    SharedPreferences.getInstance().then((prefValue) => {
      setState(() {
        _name = prefValue.getString('name')?? false;
        _controller = new TextEditingController(text: _name);
      })
    });
  }
Jitesh Mohite
  • 31,138
  • 12
  • 157
  • 147
0

For any one still experiencing this issue, it's because there is still a race condition in the accepted answer.

To fix it, use this package to wait for the layout to load first

Ruan
  • 3,969
  • 10
  • 60
  • 87
-1

You can use FutureBuilder to make async operations.

CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
zinwa_lin
  • 11
  • 1