美文网首页Flutter
Flutter shared_preferences的基本使用、

Flutter shared_preferences的基本使用、

作者: Jimi | 来源:发表于2021-09-28 12:43 被阅读0次

    前言

    本文是基于官方最新稳定版本^2.0.8进行开发

    源代码及视频教程地址

    源代码地址

    视频教程地址

    目的

    本文主要对shared_preferences: ^2.0.8的作用以及基本使用来进行源码分析,最终会封装一个比较通用的类库,因为2.0以上版本是空安全,所以后面讲的所有代码以及封装都是基于空安全的。

    shared_preferences介绍

    shared_preferences主要的作用是用于将数据异步持久化到磁盘,因为持久化数据只是存储到临时目录,当app删除时该存储的数据就是消失,web开发时清除浏览器存储的数据也将消失。

    支持存储类型:

    • bool
    • int
    • double
    • string
    • stringList

    shared_preferences应用场景

    主要用于持久化数据,如持久化用户信息、列表数据等。

    持久化用户信息

    因为用户信息基本是不改变的,而在一个应用程序中常常会有多个页面需要展示用户信息,我们不可能每次都去获取接口,那么本地持久化就会变得很方便。

    持久化列表数据

    为了给用户更好的体验,在获取列表数据时我们常常会先展示旧数据,带给用户更好的体验,不至于一打开页面就是空白的,当我们采用持久化列表数据后,可以直接先展示本地数据,当网络数据请求回来后在进行数据更新。

    shared_preferences使用的对应类库

    我们知道每个平台持久化数据的方式都不一样,而shared_preferences针对不同的平台封装了一个通用的类库,接下来我们看看不同平台下他们使用的库:

    • iOS: NSUserDefaults
    • Android: SharedPreferences
    • Web: localStorage
    • Linux: FileSystem(保存数据到本地系统文件库中)
    • Mac OS: FileSystem(保存数据到本地系统文件库中)
    • Windows: FileSystem(保存数据到本地系统文件库中)

    shared_preferences基本使用

    pubspec.yaml导入依赖

    shared_preferences: ^2.0.8
    

    导入头文件

    import 'package:shared_preferences/shared_preferences.dart';
    

    获取实例对象

    SharedPreferences? sharedPreferences = await SharedPreferences.getInstance();
    

    设置持久化数据

    我们可以通过sharedPreferences的实例化对象调用对应的set方法设置持久化数据

    SharedPreferences? sharedPreferences;
    
    // 设置持久化数据
    void _setData() async {
      // 实例化
      sharedPreferences = await SharedPreferences.getInstance();
    
      // 设置string类型
      await sharedPreferences?.setString("name", "Jimi");
    
      // 设置int类型
      await sharedPreferences?.setInt("age", 18);
    
      // 设置bool类型
      await sharedPreferences?.setBool("isTeacher", true);
    
      // 设置double类型
      await sharedPreferences?.setDouble("height", 1.88);
    
      // 设置string类型的数组
      await sharedPreferences?.setStringList("action", ["吃饭", "睡觉", "打豆豆"]);
    
      setState(() {});
    }
    

    读取持久化数据

    我们可以通过sharedPreferences的实例化对象调用对应的get方法读取持久化数据

    Text("名字: ${sharedPreferences?.getString("name") ?? ""}",
         style: TextStyle(
           color: Colors.blue,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("年龄: ${sharedPreferences?.getInt("age") ?? ""}",
         style: TextStyle(
           color: Colors.red,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("是老师吗?: ${sharedPreferences?.getBool("isTeacher") ?? ""}",
         style: TextStyle(
           color: Colors.orange,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("身高: ${sharedPreferences?.getDouble("height") ?? ""}",
         style: TextStyle(
           color: Colors.pink,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("我正在: ${sharedPreferences?.getStringList("action") ?? ""}",
         style: TextStyle(
           color: Colors.purple,
           fontSize: 20
         ),
        ),
    

    完整代码

    import 'package:flutter/material.dart';
    import 'package:shared_preferences/shared_preferences.dart';
    
    class SharedPreferencesExample extends StatefulWidget {
      @override
      _SharedPreferencesExampleState createState() => _SharedPreferencesExampleState();
    }
    
    class _SharedPreferencesExampleState extends State<SharedPreferencesExample> {
    
    
      SharedPreferences? sharedPreferences;
    
      // 设置持久化数据
      void _setData() async {
        // 实例化
        sharedPreferences = await SharedPreferences.getInstance();
    
        // 设置string类型
        await sharedPreferences?.setString("name", "Jimi");
    
        // 设置int类型
        await sharedPreferences?.setInt("age", 18);
    
        // 设置bool类型
        await sharedPreferences?.setBool("isTeacher", true);
    
        // 设置double类型
        await sharedPreferences?.setDouble("height", 1.88);
    
        // 设置string类型的数组
        await sharedPreferences?.setStringList("action", ["吃饭", "睡觉", "打豆豆"]);
    
        setState(() {});
      }
    
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text("SharedPreferences"),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: _setData,
            child: Icon(Icons.add),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                Text("名字: ${sharedPreferences?.getString("name") ?? ""}",
                     style: TextStyle(
                       color: Colors.blue,
                       fontSize: 20
                     ),
                    ),
                SizedBox(height: 20,),
                Text("年龄: ${sharedPreferences?.getInt("age") ?? ""}",
                     style: TextStyle(
                       color: Colors.red,
                       fontSize: 20
                     ),
                    ),
                SizedBox(height: 20,),
                Text("是老师吗?: ${sharedPreferences?.getBool("isTeacher") ?? ""}",
                     style: TextStyle(
                       color: Colors.orange,
                       fontSize: 20
                     ),
                    ),
                SizedBox(height: 20,),
                Text("身高: ${sharedPreferences?.getDouble("height") ?? ""}",
                     style: TextStyle(
                       color: Colors.pink,
                       fontSize: 20
                     ),
                    ),
                SizedBox(height: 20,),
                Text("我正在: ${sharedPreferences?.getStringList("action") ?? ""}",
                     style: TextStyle(
                       color: Colors.purple,
                       fontSize: 20
                     ),
                    ),
              ],
            ),
          ),
        );
      }
    }
    

    效果展示

    image

    shared_preferences辅助操作

    获取持久化数据中所有存入的key

    List<String> keys = sharedPreferences?.getKeys().toList() ?? [];
    print(keys);
    
    // 控制台输出
    [name, age, isTeacher, height, action]
    

    判断持久化数据中是否包含某个key

    bool isContainKey = sharedPreferences?.containsKey("name") ?? false;
    print(isContainKey);
    
    // 控制台输出
    flutter: true
    

    删除持久化数据中某个key

    bool isRemoveKey = await sharedPreferences?.remove("name") ?? false;
    print(isRemoveKey);
    
    // 控制台输出
    flutter: true
    

    清除所有持久化数据

    bool isClearAllKey = await sharedPreferences?.clear() ?? false;
    print(isClearAllKey);
    
    // 控制台输出
    flutter: true
    

    重新加载所有数据(仅重载运行时)

    await sharedPreferences?.reload();
    

    shared_preferences源码分析

    实例化对象源码分析

    接下来我们来对shared_preferences进行分析,我们在使用的时候需要通过getInstance实例化一个对象,接下来我们看下这里面它都做了什么操作。

    静态变量分析

    我们先来看下它定义了三个静态变量:

    • _prefix: 设置持久化数据和读取持久化数据时统一设置前缀(flutter.)
    • _completer: 持久化数据异步通知,就是当shared_preferences实例化完成后通过completer.future来返回结果
    • _manualDartRegistrationNeeded: 是否需要手动注册,因为涉及到LinuxWindowsMac Os的持久化数据时,是需要手动进行注册的,默认为true
    static const String _prefix = 'flutter.';
    static Completer<SharedPreferences>? _completer;
    static bool _manualDartRegistrationNeeded = true;
    

    getInstance()源码分析

    当我们获取实例化对象时先判断_completer是否为空,如果不为空则直接返回它的的future结果,否则它会实例化一个SharedPreferencesCompleter对象,然后通过_getSharedPreferencesMap来获取持久化的map对象,获取到map对象后,通过completer.complete(SharedPreferences._(preferencesMap))Map结果返回出去,代码如下:

    static Future<SharedPreferences> getInstance() async {
      if (_completer == null) {
        final completer = Completer<SharedPreferences>();
        try {
          final Map<String, Object> preferencesMap =
            await _getSharedPreferencesMap();
          completer.complete(SharedPreferences._(preferencesMap));
        } on Exception catch (e) {
          // If there's an error, explicitly return the future with an error.
          // then set the completer to null so we can retry.
          completer.completeError(e);
          final Future<SharedPreferences> sharedPrefsFuture = completer.future;
          _completer = null;
          return sharedPrefsFuture;
        }
        _completer = completer;
      }
      return _completer!.future;
    }
    

    _getSharedPreferencesMap()源码分析

    在我们调用getInstance()方法里,会调用_getSharedPreferencesMap()来获取持久化的Map数据,我们接下来看看它是如何获取的,首先它通过_store.getAll()就可以直接获取到本地的所有持久化数据,当我们调用_store时,它会判断是否需要手动注册

    不需要手动注册时:

    iOSAndroid等平台中使用不需要手动注册,所以它直接就返回的对应的实例对象

    需要手动注册时:

    先判断是否是web,如果是就返回localStorage,否则判断是Linux还是Windows,然后根据平台的不同返回其对应的实例。

    static Future<Map<String, Object>> _getSharedPreferencesMap() async {
      final Map<String, Object> fromSystem = await _store.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]!;
      }
      return preferencesMap;
    }
    
    static SharedPreferencesStorePlatform get _store {
      // TODO(egarciad): Remove once auto registration lands on Flutter stable.
      // https://github.com/flutter/flutter/issues/81421.
      if (_manualDartRegistrationNeeded) {
        // Only do the initial registration if it hasn't already been overridden
        // with a non-default instance.
        if (!kIsWeb &&
            SharedPreferencesStorePlatform.instance
            is MethodChannelSharedPreferencesStore) {
          if (Platform.isLinux) {
            SharedPreferencesStorePlatform.instance = SharedPreferencesLinux();
          } else if (Platform.isWindows) {
            SharedPreferencesStorePlatform.instance = SharedPreferencesWindows();
          }
        }
        _manualDartRegistrationNeeded = false;
      }
    
      return SharedPreferencesStorePlatform.instance;
    }
    

    _setValue()源码分析

    不管我们是存储什么内容的数据,最终都会调用_setValue()来进行存储,

    1. 首先它会检查存入的value是否为空,如果为空就抛出异常,否则就用_prefix + key来作为存入的key值。

    2. 判断存入的值是不是List<String>,如果是先把value通过toList()方法转换,然后在存入,否则直接存入,这步存入操作只是存入缓存中,当应用程序退出时将消失

    3. 最后通过_store来异步写入到磁盘中

    Future<bool> _setValue(String valueType, String key, Object value) {
      ArgumentError.checkNotNull(value, 'value');
      final String prefixedKey = '$_prefix$key';
      if (value is List<String>) {
        // Make a copy of the list so that later mutations won't propagate
        _preferenceCache[key] = value.toList();
      } else {
        _preferenceCache[key] = value;
      }
      return _store.setValue(valueType, prefixedKey, value);
    }
    

    shared_preferences封装

    我们在使用shared_preferences时每次都需要去获取它的实例,如果多个地方用到,那么每次都要实例化一次。这样代码的可读性差,后期的维护成本也变得很高,而且还不支持存储Map类型,所以接下来我们对shared_preferences来封装一个通用而且使用更简单的库。

    使用单例模式进行shared_preferences封装

    因为我们获取的都是同一个实例,所以采用单例模式来进行封装最好,而且获取实例是异步的,所以我们在应用程序启动时先初始化,这样使用起来更加的方便。

    创建单例类

    因为我们代码都是采用了空安全,所以空安全里面有个非常重要的属性late来延迟加载我们实例,如下:

    class JSpUtil {
    
      JSpUtil._internal();
      
      factory JSpUtil() => _instance;
    
      static late final JSpUtil _instance = JSpUtil._internal();
    }
    

    初始化shared_preferences

    因为采用单例模式,所以在获取唯一实例的时候我们在入口统一获取一次即可。

    static late SharedPreferences _preferences;
    
    static Future<JSpUtil> getInstance() async {
      _preferences = await SharedPreferences.getInstance();
      return _instance;
    }
    

    封装方式一:对应get、set方法

    /// 根据key存储int类型
    static Future<bool> setInt(String key, int value) {
      return _preferences.setInt(key, value);
    }
    
    /// 根据key获取int类型
    static int? getInt(String key, {int defaultValue = 0}) {
      return _preferences.getInt(key) ?? defaultValue;
    }
    
    /// 根据key存储double类型
    static Future<bool> setDouble(String key, double value) {
      return _preferences.setDouble(key, value);
    }
    
    /// 根据key获取double类型
    static double? getDouble(String key, {double defaultValue = 0.0}) {
      return _preferences.getDouble(key) ?? defaultValue;
    }
    
    /// 根据key存储字符串类型
    static Future<bool> setString(String key, String value) {
      return _preferences.setString(key, value);
    }
    
    /// 根据key获取字符串类型
    static String? getString(String key, {String defaultValue = ""}) {
      return _preferences.getString(key) ?? defaultValue;
    }
    
    /// 根据key存储布尔类型
    static Future<bool> setBool(String key, bool value) {
      return _preferences.setBool(key, value);
    }
    
    /// 根据key获取布尔类型
    static bool? getBool(String key, {bool defaultValue = false}) {
      return _preferences.getBool(key) ?? defaultValue;
    }
    
    /// 根据key存储字符串类型数组
    static Future<bool> setStringList(String key, List<String> value) {
      return _preferences.setStringList(key, value);
    }
    
    /// 根据key获取字符串类型数组
    static List<String> getStringList(String key, {List<String> defaultValue = const []}) {
      return _preferences.getStringList(key) ?? defaultValue;
    }
    
    /// 根据key存储Map类型
    static Future<bool> setMap(String key, Map value) {
      return _preferences.setString(key, json.encode(value));
    }
    
    /// 根据key获取Map类型
    static Map getMap(String key) {
      String jsonStr = _preferences.getString(key) ?? "";
      return jsonStr.isEmpty ? Map : json.decode(jsonStr);
    }
    

    封装方式二:统一set、get方法

    /// 通用设置持久化数据
    static setLocalStorage<T>(String key, T value) {
      String type = value.runtimeType.toString();
    
      switch (type) {
        case "String":
          setString(key, value as String);
          break;
        case "int":
          setInt(key, value as int);
          break;
        case "bool":
          setBool(key, value as bool);
          break;
        case "double":
          setDouble(key, value as double);
          break;
        case "List<String>":
          setStringList(key, value as List<String>);
          break;
        case "_InternalLinkedHashMap<String, String>":
          setMap(key, value as Map);
          break;
      }
    }
    
    /// 获取持久化数据
    static dynamic getLocalStorage<T>(String key) {
      dynamic value = _preferences.get(key);
      if (value.runtimeType.toString() == "String") {
        if (_isJson(value)) {
          return json.decode(value);
        }
      }
      return value;
    }
    

    其他辅助方法封装

    /// 获取持久化数据中所有存入的key
    static Set<String> getKeys() {
      return _preferences.getKeys();
    }
    
    /// 获取持久化数据中是否包含某个key
    static bool containsKey(String key) {
      return _preferences.containsKey(key);
    }
    
    /// 删除持久化数据中某个key
    static Future<bool> remove(String key) async {
      return await _preferences.remove(key);
    }
    
    /// 清除所有持久化数据
    static Future<bool> clear() async {
      return await _preferences.clear();
    }
    
    /// 重新加载所有数据,仅重载运行时
    static Future<void> reload() async {
      return await _preferences.reload();
    }
    
    /// 判断是否是json字符串
    static _isJson(String value) {
      try {
        JsonDecoder().convert(value);
        return true;
      } catch(e) {
        return false;
      }
    }
    

    两种封装的使用方式

    // 设置String类型
    await JSpUtil.setString("name", "Jimi");
    
    // 设置int类型
    await JSpUtil.setInt("age", 18);
    
    // 设置bool类型
    await JSpUtil.setBool("isTeacher", true);
    
    // 设置double类型
    await JSpUtil.setDouble("height", 1.88);
    
    // 设置string类型的数组
    await JSpUtil.setStringList("action", ["吃饭", "睡觉", "打豆豆"]);
    
    // 设置Map类型
    await JSpUtil.setMap("weight", {"weight": 112});
    
    JSpUtil.setLocalStorage("name", "Jimi");
    JSpUtil.setLocalStorage("age", 18);
    JSpUtil.setLocalStorage("isTeacher", true);
    JSpUtil.setLocalStorage("height", 1.88);
    JSpUtil.setLocalStorage("action", ["吃饭", "睡觉", "打豆豆"]);
    JSpUtil.setLocalStorage("weight", {"weight": "112"});
    
    JSpUtil.getLocalStorage("name");
    JSpUtil.getLocalStorage("age");
    JSpUtil.getLocalStorage("isTeacher");
    JSpUtil.getLocalStorage("height");
    JSpUtil.getLocalStorage("action");
    JSpUtil.getLocalStorage("weight");
    
    
    // 获取磁盘中所有存入的key
    List<String> keys = JSpUtil.getKeys().toList();
    print(keys);
    
    // 持久化数据中是否包含某个key
    bool isContainKey = JSpUtil.containsKey("name");
    print(isContainKey);
    
    // 删除持久化数据中某个key
    bool isRemoveKey = await JSpUtil.remove("name");
    print(isRemoveKey);
    
    // 清除所有持久化数据
    bool isClearAllKey = await JSpUtil.clear();
    print(isClearAllKey);
    
    // 重新加载所有数据,仅重载运行时
    await JSpUtil.reload();
    

    获取值的方式

    Text("名字: ${JSpUtil.getString("name")}",
         style: TextStyle(
           color: Colors.blue,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("年龄: ${JSpUtil.getInt("age")}",
         style: TextStyle(
           color: Colors.red,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("是老师吗?: ${JSpUtil.getBool("isTeacher")}",
         style: TextStyle(
           color: Colors.orange,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("身高: ${JSpUtil.getDouble("height")}",
         style: TextStyle(
           color: Colors.pink,
           fontSize: 20
         ),
        ),
    SizedBox(height: 20,),
    Text("我正在: ${JSpUtil.getStringList("action")}",
         style: TextStyle(
           color: Colors.purple,
           fontSize: 20
         ),
        ),
    

    总结

    当我们需要对数据进行持久化储存的时候,我们可以采用shared_preferences来进行储存到磁盘,这样app启动时可访问储存后的数据,而且支持存储多种类型。

    相关文章

      网友评论

        本文标题:Flutter shared_preferences的基本使用、

        本文链接:https://www.haomeiwen.com/subject/dlalnltx.html