flutterでiOSのWidgetKitを使う

##実装方法
1. flutterのshared_preferencesで表示したい値を保存
2. flutterのMethodCannelでswiftのコードを呼び出す
3. swiftで保存した値をUserDefaults(flutterの形式)で取り出す
4. AppGroupを用いてWidgetKitと連携
5. UserDefaultsをiOSの形式で保存し直す
6. WidgetKit側でUserDefaultsを参照し表示

Widgetの値を変更したい場合は、再度MethodCannelを呼び出して、UserDefaultsの値を変更したのち、Widgetを更新すれば良い

##1. flutterのshared_preferencesで表示したい値を保存
main.dart内で次のように実装

```dart
int _counter = 0;
```

```dart
final SharedPreferences _prefs = await SharedPreferences.getInstance();
    _prefs.setInt('count', _counter);
```
##2. flutterのMethodCannelでswiftのコードを呼び出す

```dart
static const methodChannel = MethodChannel('[your app name]/sample');
```

```dart
try {
      final int result = await methodChannel.invokeMethod('setDataForWidgetKit');
      print('SET setUserDefaultsForAppGroup: $result');
    } on PlatformException catch (e) {
      print('ERROR setUserDefaultsData: ${e.message}');
    }
```


##3. swiftで保存した値をUserDefault(flutterの形式)で取り出す
AppDelegate内のApplication関数内に次のコードを追加

```swift
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
        let channel = FlutterMethodChannel(name: "[your app name]/sample",
                                           binaryMessenger: controller.binaryMessenger)
        channel.setMethodCallHandler({
            (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
            
            if call.method == "setDataForWidgetKit"  {
                self.setUserDefaultsForAppGroup(result: result)
                
            }
            
            result(FlutterMethodNotImplemented)
            return
        })
```
[your app name]/sampleという名前のMethodCannelの、setDataForWidgetKit関数がflutterからinvokeされたら、swiftのsetUserDefaultsForAppGroup関数を呼び出すという意味である。

##4. AppGroupを用いてWidgetKitと連携
Xcode上のTARGETのRunner->Signing & Capabilities -> + CapabilityからApp Groupを追加
グループの名前は名前は慣例的にgroup.[bundle id]とする
次に、File -> New -> TargetからWidget Extensionを追加する

##5. UserDefaultsをiOSの形式で保存し直す
AppDelegate.swift内のAppDalegateクラス内に以下の関数を追加する

```swift
private func setUserDefaultsForAppGroup(result: FlutterResult) {
        
        guard let userDefaults = UserDefaults(suiteName: "group.[your bundle id]") else {
            return result(FlutterError(code: "UNAVAILABLE",
                                       message: "setUserDefaultsForAppGroup Failed",
                                       details: nil))
        }
       
        let defaults = UserDefaults.standard
        let count = defaults.value(forKey: "flutter.count") as? Int
        
        userDefaults.setValue(count, forKey: "count")
        
        if #available(iOS 14.0, *) {
            WidgetCenter.shared.reloadAllTimelines()
        }
        result(true)
       
    }
```

flutter.countというUserDefaults(flutterとSwiftでデータを共有するためのもの)を取得し、countというUserDefaults(App Group内でデータを共有するためのもの)に定義し直している。
また、iOS14.0以降の端末でWidgetを更新するようにしている

##6. WidgetKit側でUserDefaultを参照し表示

最後にWidgetKitのテンプレートを修正して、値を表示させれば良い
struct SimpleEntry内にプロパティを追加

```swift
let count: Int
```
getSnapshot関数内に以下のコードを追加する

```swift
let userDefaults = UserDefaults(suiteName: "group.[your bundle id]")
let countData = userDefaults?.value(forKey: "count") as? Int
```
最後にmy_widgetEntryViewのbodyの値を次のように変更する

```swift
Text(String(entry.count))
```
あとはnull Saftyに注意しながら、entryの引数を修正していけば実装できる。

##つまずいたところ
・Swiftの機能であるApp GroupやUserDefaultsの仕組みをある程度理解しなければならない
・swiftのコードの実行時にエラーやログがまともに出ないため、手探りでミスを見つけなければならない
・userDefaultsやMethodCannelで使うKeyのタイプミスで時間が取られる可能性がある
・WidgetKitのUIを作るとき、結局ある程度はswiftUIを理解しなければならない
・flutterに必ずresultを返さないと例外が発生する

##参考資料
以下の記事がわかりやすく参考になった。

https://zenn.dev/mcz9mm/articles/ca58e5b7f9bc96ad552f

https://hondakenya.work/flutter-widgetkit/