简体   繁体   English

Flutter:如何同步使用SharedPreferences?

[英]Flutter: How to use SharedPreferences synchronously?

I am using Shared Preferences in my Flutter app and what I would like to do is store SharedPreferences as a field on startup and then use it synchronously in the app.我在我的 Flutter 应用程序中使用共享首选项,我想做的是在启动时将SharedPreferences存储为一个字段,然后在应用程序中同步使用它。 However I'm not sure if I'm not missing anything.但是我不确定我是否没有遗漏任何东西。 What I want to achieve is instead of:我想要实现的是:

method1() async {
  SharedPreferences sp = await SharedPreferences.getInstance();
  return sp.getString('someKey');
}

to

SharedPreferences sp;
//I would probably pass SharedPreferences in constructor, but the idea is the same
someInitMethod() async {
  sp = await SharedPreferences.getInstance();
}
method1() {
  return sp.getString('someKey');
}
method2() {
  return sp.getString('someKey2');
}
method3() {
  return sp.getString('someKey3');
}

In that way I would achieve synchronous access to sharedPrefs.这样我就可以实现对 sharedPrefs 的同步访问。 Is it bad solution?这是不好的解决方案吗?

EDIT:编辑:
What is worth mentioning is that getInstance method will only check for instance and if there is any than it returns it, so as I see it, is that async is only needed to initialize instance.值得一提的是, getInstance方法将只检查实例,如果有则返回它,所以在我看来, async只需要初始化实例。 And both set and get methods are sync anyway. setget方法无论如何都是同步的。

static Future<SharedPreferences> getInstance() async {
  if (_instance == null) {
    final Map<String, Object> fromSystem =
        await _kChannel.invokeMethod('getAll');
    assert(fromSystem != null);
    // Strip the flutter. prefix from the returned preferences.
    final Map<String, Object> preferencesMap = <String, Object>{};
    for (String key in fromSystem.keys) {
      assert(key.startsWith(_prefix));
      preferencesMap[key.substring(_prefix.length)] = fromSystem[key];
    }
    _instance = new SharedPreferences._(preferencesMap);
  }
  return _instance;
}

I use the same approach as the original poster suggests ie I have a global variable (actually a static field in a class that I use for all such variables) which I initialise to the shared preferences something like this: 我使用的方法与原始发布者建议的方法相同,即,我有一个全局变量(实际上是我用于所有此类变量的类中的静态字段),并初始化为共享首选项,如下所示:

in globals.dart : globals.dart

class App {      
  static SharedPreferences localStorage;
  static Future init() async {
    localStorage = await SharedPreferences.getInstance();
  }
}

in main.dart : main.dart

void main() {
  start();
}

Async.Future start() async {
  await App.init();
  localStorage.set('userName','Bob');
  print('User name is: ${localStorage.get('userName)'}');  //prints 'Bob'
}

The above worked fine but I found that if I tried to use App.localStorage from another dart file eg settings.dart it would not work because App.localStorage was null but I could not understand how it had become null. 上面的工作正常,但我发现如果我尝试从另一个dart文件(例如settings.dart使用App.localStorage ,则将无法正常工作,因为App.localStorage为null,但我无法理解它如何变为null。

Turns out the problem was that the import statement in settings.dart was import 'package:<packagename>/src/globals.dart'; 原来的问题是settings.dart中的import语句是import 'package:<packagename>/src/globals.dart'; when it should have been import 'globals.dart; 什么时候应该import 'globals.dart; .

I made a simple way to using this PrefUtil class:我做了一个简单的方法来使用这个 PrefUtil class:

import 'package:shared_preferences/shared_preferences.dart';

class PrefUtil {
  static late final SharedPreferences preferences;
  static bool _init = false;
  static Future init() async {
    if (_init) return;
    preferences = await SharedPreferences.getInstance();
    _init = true;
    return preferences;
  }

  static setValue(String key, Object value) {
    switch (value.runtimeType) {
      case String:
        preferences.setString(key, value as String);
        break;
      case bool:
        preferences.setBool(key, value as bool);
        break;
      case int:
        preferences.setInt(key, value as int);
        break;
      default:
    }
  }

static Object getValue(String key, Object defaultValue) {
    switch (defaultValue.runtimeType) {
      case String:
        return preferences.getString(key) ?? "";
      case bool:
        return preferences.getBool(key) ?? false;
      case int:
        return preferences.getInt(key) ?? 0;
      default:
        return defaultValue;
    }
  }
}

In main.dart :main.dart

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  PrefUtil.init();
  .....

Save it like:像这样保存:

PrefUtil.setValue("isLogin", true);

Get the value like:获取如下值:

PrefUtil.getValue("isLogin", false) as bool

By this, it will initialize only once and get it where ever you need.通过这种方式,它只会初始化一次并在您需要的任何地方获取它。

You can use FutureBuilder to render the loading screen while waiting for SharedPreferences to be intialized for the first time in a singleton-like class. After that, you can access it synchronously inside the children.您可以使用FutureBuilder渲染加载屏幕,同时等待SharedPreferences在单例 class 中首次初始化。之后,您可以在孩子内部同步访问它。

local_storage.dart local_storage.dart

class LocalStorage {
  static late final SharedPreferences instance;

  static bool _init = false;
  static Future init() async {
    if (_init) return;
    instance = await SharedPreferences.getInstance();
    _init = true;
    return instance;
  }
}

app_page.dart app_page.dart

final Future _storageFuture = LocalStorage.init();

@override
Widget build(BuildContext context) {
  return FutureBuilder(
    future: _storageFuture,
    builder: (context, snapshot) {
      Widget child;
      if (snapshot.connectionState == ConnectionState.done) {
        child = MyPage();
      } else if (snapshot.hasError) {
        child = Text('Error: ${snapshot.error}');
      } else {
        child = Text('Loading...');
      }
      return Scaffold(
        body: Center(child: child),
      );
    },
  );
}

my_page.dart我的页面.dart

return Text(LocalStorage.instance.getString(kUserToken) ?? 'Empty');
  1. call shared prefs on startup of a stateful main app (we call ours a initState() override of a StatefulWidget after super.initState()) 在有状态的主应用程序启动时调用共享的首选项(在super.initState()之后,我们将我们的StatefulWidget的initState()覆盖称为)
  2. after shared prefs inits, set the value to a field on main (ex: String _someKey) 在共享首选项init之后,将该值设置为main上的一个字段(例如:String _someKey)
  3. inject this field into any child component 注入这一领域到任何子组件
  4. You can the call setState() on _someKey at you leisure and it will persist to children injected with your field 您可以在闲暇时在_someKey上调用setState(),它将持续注入您的字段的孩子

@iBob101 's answer is good, but still, you have to wait before you use the SharedPreferences for the first time. @ iBob101的答案很好,但是仍然必须等待第一次使用SharedPreferences。

The whole point is NOT to await for your SharedPreferences and be sure that it will always be NOT NULL. 重点不是要await您的SharedPreferences,并确保它始终为非NULL。

Since you'll have to wait anyway let's do it in the main() method: 由于您仍然必须等待,因此请在main()方法中进行操作:

class App {      
  static SharedPreferences localStorage;
  static Future init() async {
    localStorage = await SharedPreferences.getInstance();
  }
}

And the main method: 和主要方法:

void main() async{
  await SharedPref.initSharedPref();
  runApp(MyApp());
}

the line await SharedPref.initSharedPref(); 该行await SharedPref.initSharedPref(); takes ~100ms to execute. 大约需要100毫秒才能执行。 This is the only drawback as far as I can see. 据我所知,这是唯一的缺点。

But you definitely know that in every place in the app your sharedPreferenes instance in NOT NULL and ready for accessing it: 但是您肯定知道,在应用程序的每个位置,您的sharedPreferenes实例都为NOT NULL并可以访问它:

String s = App.localStorage.getString(PREF_MY_STRING_VALUE);

I think it's worthwhile 我认为值得

The cleanest way is to retrieve SharedPreferences in main method and pass it to MyApp as a dependency: 最干净的方法是在main方法中检索SharedPreferences并将其作为依赖项传递给MyApp

void main() async {
  // Takes ~50ms to get in iOS Simulator.
  final SharedPreferences sharedPreferences =
      await SharedPreferences.getInstance();

  runApp(MyApp(sharedPreferences: sharedPreferences));
}

class MyApp extends StatefulWidget {
  final SharedPreferences sharedPreferences;

  const MyApp({Key key, this.sharedPreferences})
      : assert(sharedPreferences != null),
        super(key: key);

  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  @override
  Widget build(BuildContext context) {
    // You can access shared preferences via widget.sharedPreferences
    return ...
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM