如何在SwiftUI Widget中访问Flutter存储的shared_preferences?
核心问题说明
Flutter默认的shared_preferences会将数据存储在**主应用私有UserDefaults**中,而WidgetKit的Widget是独立进程,无法直接访问主应用的私有存储。必须通过App Group实现跨进程数据共享,同时需要修改Flutter端的存储逻辑,将数据写入App Group的UserDefaults。
第一步:配置App Group(主应用+Widget)
- 在Xcode中打开Flutter项目的iOS工程,分别给主应用Runner target和Widget target添加App Groups能力:
- 进入
Signing & Capabilities页面,点击+ Capability,选择App Groups - 创建或勾选同一个App Group ID(格式如
group.com.yourcompany.yourapp)
- 进入
- 在主应用的
ios/Runner/Info.plist中添加App Group ID配置(方便后续代码读取):
<key>AppGroupID</key> <string>group.com.yourcompany.yourapp</string>
- 同样在Widget的
Info.plist中添加相同的AppGroupID配置。
第二步:修改Flutter端,将数据写入App Group
默认的shared_preferences不支持写入App Group,需要通过MethodChannel调用原生代码实现:
Flutter端代码
创建工具类封装调用:
import 'package:flutter/services.dart'; class AppGroupPrefs { static const _channel = MethodChannel('app_group_prefs'); static Future<void> saveString(String key, String value) async { await _channel.invokeMethod('saveString', {'key': key, 'value': value}); } static Future<String?> getString(String key) async { return await _channel.invokeMethod('getString', {'key': key}); } }
之后替换原有的shared_preferences调用,用AppGroupPrefs.saveString()来存储需要共享的数据。
iOS原生端代码
在Runner/AppDelegate.swift中注册MethodChannel处理逻辑:
import UIKit import Flutter @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { let controller = window?.rootViewController as! FlutterViewController let channel = FlutterMethodChannel(name: "app_group_prefs", binaryMessenger: controller.binaryMessenger) channel.setMethodCallHandler { [weak self] call, result in guard let groupID = Bundle.main.object(forInfoDictionaryKey: "AppGroupID") as? String else { result(FlutterError(code: "NO_GROUP", message: "App Group ID未配置", details: nil)) return } let defaults = UserDefaults(suiteName: groupID) switch call.method { case "saveString": guard let args = call.arguments as? [String: Any], let key = args["key"] as? String, let value = args["value"] as? String else { result(FlutterError(code: "INVALID_ARGS", message: "参数错误", details: nil)) return } defaults?.set(value, forKey: key) result(nil) case "getString": guard let args = call.arguments as? [String: Any], let key = args["key"] as? String else { result(FlutterError(code: "INVALID_ARGS", message: "参数错误", details: nil)) return } result(defaults?.string(forKey: key)) default: result(FlutterMethodNotImplemented) } } GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } }
第三步:Widget端读取App Group数据
在Widget的TimelineProvider中修改读取逻辑:
struct MyWidgetProvider: TimelineProvider { // ... 其他实现代码 ... func getTimeline(in context: Context, completion: @escaping (Timeline<MyWidgetEntry>) -> Void) { guard let groupID = Bundle.main.object(forInfoDictionaryKey: "AppGroupID") as? String else { let entry = MyWidgetEntry(date: Date(), displayValue: "配置错误") let timeline = Timeline(entries: [entry], policy: .atEnd) completion(timeline) return } let defaults = UserDefaults(suiteName: groupID) // 注意这里的key要和Flutter端存储的完全一致,如果是原shared_preferences的key,会带"flutter."前缀 let targetValue = defaults?.string(forKey: "flutter.KEY") ?? "无数据" let entry = MyWidgetEntry(date: Date(), displayValue: targetValue) // 设置刷新间隔,比如1分钟 let timeline = Timeline(entries: [entry], policy: .after(Date().addingTimeInterval(60))) completion(timeline) } }
排查要点
- 检查App Group ID是否完全一致:主应用和Widget的App Group ID必须完全相同,大小写、拼写都不能错
- 验证数据是否写入App Group:可以用Xcode的调试工具查看App Group的
UserDefaults,或者在Flutter调用存储后,用lldb命令po UserDefaults(suiteName: "你的group ID")?.dictionaryRepresentation()确认数据存在 - 避免混用私有存储:不要尝试用默认
UserDefaults读取主应用数据,Widget无法访问主应用私有存储 - 确认key匹配:如果是迁移原有
shared_preferences的数据,注意原key带有flutter.前缀,自定义存储则用你传入的key
内容的提问来源于stack exchange,提问作者Taha Malik
相关产品推荐
相关产品推荐

