手撸一个Flutter插件实现跨苹果全家桶云同步持久化Key Value数据

前言

作为一个客户端开发者,后端开发一直是我的弱项。虽然GPT的横空出世,让我对后端的开发有一点眉目。但是现实是,能不触碰就不触碰,因为人的精力是有限,如何在有限的时间里发挥最大的作用一直是我的一个追求。所以回到本次主题,我自己上线的一个产品,目前已经成功上线了iOS以及Mac端,如何在不开发后端的情况下实现同步轻量级数据呢?答案是利用Cloud Kit。Cloud Kit是苹果官方API,用于同步同意iCloud账号下设备的数据,包括Key Value Storage、云dataBase、云document等等。通过翻阅pub类似的库,我发现了有以下几个pub包是可以参考的cloud_kiticloud_storage,但是他们都不是我想要的,要么是不支持Mac、要么是只能同步存储文件,但是我想要同步的其实只是轻量级持久化数据(也是就是iOS下的UserDefault,基于键值存储)。所以没办法了,肝了一晚上我手撸了一个Flutter插件icloud_kv_storage,完美的支持了跨苹果设备iOS、Mac、iPad等的轻量级Key Value数据同步。
请添加图片描述

开发过程全解析

  • 创建Flutter插件项目,指定支持iOS、Mac平台

如何创建插件虽然已经很简单,这里我还是点一下。

flutter create icloud_kv_storage -t plugin
cd ./icloud_kv_storage

//指定platforms

flutter create ./ -t plugin --platforms ios
flutter create ./ -t plugin --platforms macos

  • 先设计Flutter Channel接口

由于是肝了一晚上的产品,非常赶,第一版先只支持同步String数据,后续再支持别的基础数据,如int、double、bool等。


///获取真正的Key,我内部Key都加了flutter前缀,如果要拿真实的Key可以使用此方法
String getRealKey(String key) {
    throw UnimplementedError('getRealKey()) has not been implemented.');
  }

  ///原生的CallBack 用于实时刷新数据(如果多台苹果设备都在线,支持实时同步
  void setNativeCallBack({required GetNativeCallBackFuture onCallBack}) {
    
  }

 ///保存数据接口,这里用范型方便后续拓展,虽然第一版只支持String
  Future<void> write<T>({required String key,required T value}) {
    throw UnimplementedError('write({required String key,required String value}) has not been implemented.');
  }
  
  ///读取一个Key的数据接口,同理也是范型
  Future<T?> read<T>({required String key}) {
    throw UnimplementedError('read({required String key}) has not been implemented.');
  }


  ///删除一个key
  Future<void> delete({required String key}) {
    throw UnimplementedError('delete({required String key}) has not been implemented.');
  }

  • 原生Swift接口的实现

iOS和Mac实际都是共用Foundation框架,所以我们只需要编写同一套Swift实现,即可同时满足iOS以及Mac。

1.首先定义Channel实现协议


enum CKCommandType: String {
    case DELETE_VALUE
    case GET_VALUE
    case SAVE_VALUE
    case EMPTY = ""
}

protocol CKCommandHandlerProtocol {
    var COMMAND_NAME: CKCommandType { get }
    func evaluateExecution(command: String) -> Bool
    func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) -> Void
}

2.实现增删改查对应methodName的协议

查询一个key对应的协议实现


class CKGetValueHandler: CKCommandHandlerProtocol {
    
    var COMMAND_NAME: CKCommandType = .GET_VALUE
    
    func evaluateExecution(command: String) -> Bool {
        return CKCommandType(rawValue: command) == COMMAND_NAME
    }
    
    func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) {
        if (!evaluateExecution(command: command)) {
            return
        }
        
        if let key = arguments["key"] as? String {
            let store = NSUbiquitousKeyValueStore.default
            result(store.object(forKey: key))
         } else {
            result(FlutterError.init(code: "Error", message: "Cannot pass key and value parameter", details: nil))
         }
    }
    
    
}

删除一个Key的实现


class CKDeleteValueHandler: CKCommandHandlerProtocol {
    
    var COMMAND_NAME: CKCommandType = .DELETE_VALUE
    
    func evaluateExecution(command: String) -> Bool {
        return CKCommandType(rawValue: command) == COMMAND_NAME
    }
    
    func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) {
        if (!evaluateExecution(command: command)) {
            return
        }
        
        if let key = arguments["key"] as? String {
            let store = NSUbiquitousKeyValueStore.default
            store.removeObject(forKey: key)
            result(true)
        } else {
            result(FlutterError.init(code: "Error", message: "Cannot pass key parameter", details: nil))
        }
    }
    
    
}

写入保存一个Key的实现


class CKSaveValueHandler: CKCommandHandlerProtocol {
    
    var COMMAND_NAME: CKCommandType = .SAVE_VALUE
    
    func evaluateExecution(command: String) -> Bool {
        return CKCommandType(rawValue: command) == COMMAND_NAME
    }
    
    func handle(command: String, arguments: Dictionary<String, Any>, result: @escaping FlutterResult) {
        if (!evaluateExecution(command: command)) {
            return
        }
        
        if let key = arguments["key"] as? String, let value = arguments["value"] as? String{
            let store = NSUbiquitousKeyValueStore.default
            store.set(value, forKey: key)
            result(true)
         } else {
            result(FlutterError.init(code: "Error", message: "Cannot pass key and value parameter", details: nil))
         }
    }
    
    
}

  • 多设备实时刷新的实现

这里其实实现一下原生的通知监听,返回到Flutter就行,代码如下。

let keyValueStore = NSUbiquitousKeyValueStore.default
        // 监听数据变化
        NotificationCenter.default.addObserver(self, selector: #selector(keyValueStoreDidChange), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: keyValueStore)
        keyValueStore.synchronize()
        
@objc func keyValueStoreDidChange(notification: Notification) {
        // 处理数据变化
        if let userInfo = notification.userInfo as? [String: Any],
           let reasonForChange = userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] as? NSNumber {
           
           var reason = -1
           reason = reasonForChange.intValue
           
           if (reason == NSUbiquitousKeyValueStoreServerChange || reason == NSUbiquitousKeyValueStoreInitialSyncChange) {
              guard let changedKeys = userInfo[NSUbiquitousKeyValueStoreChangedKeysKey] as? [String] else {
                 return
              }
              let store = NSUbiquitousKeyValueStore.default
              for key in changedKeys {
                let value = store.object(forKey: key)
                self.channel?.invokeMethod("icloud_key_update", arguments: [key:value])
              }
           }
        }
    }


  • Plugin Channel通讯的处理

上面一个一个的协议实现完毕,我们只需要扔到Flutter Plugin handle的协议方法里就可。代码如下


public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    let callArguments: Dictionary<String, Any> = call.arguments as! Dictionary<String, Any>
    CKGetValueHandler().handle(command: call.method, arguments: callArguments, result: result)
    CKSaveValueHandler().handle(command: call.method, arguments: callArguments, result: result)
    CKDeleteValueHandler().handle(command: call.method, arguments: callArguments, result: result)
  }

就这样一个支持跨苹果全家桶同步持久化Key Value数据的Flutter插件就完全了,是不是很简单呢?

用法

首先我们要在iOS或Mac项目开启iCloud Key Value Storge 服务

在这里插入图片描述

然后导入icloud_kv_storage享受它吧~

flutter pub add icloud_kv_storage

dependencies:
  icloud_kv_storage: ^0.0.1

简单写法如下

import 'package:icloud_kv_storage/icloud_kv_storage.dart';

var iCloudStorage = CKKVStorage();

Update A Key

void _incrementCounter() {
    setState(() {
      _counter++;
      iCloudStorage.writeString(key: key, value: _counter.toString());
    });
  }

Read A Key

iCloudStorage.getString('k_storage_count').then((value) {
      if (value != null) {
        setState(() {
          _counter = int.parse(value);
        });
      }
    });

Delete A Key

void _clearCounter() {
    setState(() {
      _counter = 0;
      iCloudStorage.delete(key);
    });
  }

实时刷新CallBack

iCloudStorage.onCloudKitKVUpdateCallBack(
      onCallBack: (kvMap) {
        print('receive icloud_key_update map $kvMap');
        //if receive remove key will rec {flutter.k_storage_count: null}
        //if receive update key will rec {flutter.k_storage_count: 1}
        //because have prefix flutter. so need use my method to get real key.
        var key = iCloudStorage.getRealKey('k_storage_count');
        if (kvMap.containsKey(key)) {
          String? value = kvMap[key];
          setState(() {
            if (value != null) {
              _counter =
                int.parse(kvMap[iCloudStorage.getRealKey('k_storage_count')]);
            } else {
              _counter = 0;
            }
          });
        }
      },
    );

下载地址

Github

icloud_kv_storage

flutter pub

icloud_kv_storage

结尾

由于是1.0版本,非常肝。目前只支持同步String类型的数据,后续其他基础类型数据也会更新上。如果这个库对你有用,那就Star一个吧,感谢🙏

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值