Flutterアプリに(ホーム)ウィジェットを追加してみる＋α

この記事は [NTTテクノクロス Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ntt-tx)の20日目です。

こんにちは。NTTテクノクロスで、エバンジェリストとして活動している神原([@korodroid](https://twitter.com/korodroid))です。実は、本日投稿予定だった弊社の別メンバがやむを得ない事情により、担当できなくなりました。同記事を楽しみにしてくださっていた皆様、申し訳ございません。代わりに、私が20日目をお送りします。

自己紹介は、投稿済みの14日目の私の別記事[「海外活動を通じて得た英語の学び方＋α」](https://qiita.com/korodroid/items/193788fd1a911be77725)をご覧いただけますと幸いです。よろしければ、記事も合わせて読んでいただけたらうれしいです。

それでは、今回の内容に入っていきます。先日、 **Flutterでアプリを作っていて、このアプリに(ホーム)ウィジェットを追加したいという衝動に駆られ、実現しました。** その内容をお伝えします。また、後半で、 **会社で取り組んでいる技術者コミュニティ活動(の一例)** をご紹介させてください。

# Flutterアプリに(ホーム)ウィジェットを追加したいと考えたきっかけ
プライベートで、 [セカイフォン(リアルタイム翻訳)](http://www.iplatform.org/blog/sekai-phone) というアプリを作っています。例えば、相手が英語で話したら、日本語で聞こえてくるといった機能を持っています。当初、Android版をKotlin、iOS版をSwiftで開発していたのですが、1本化すべくFlutterへの置き換えを進めています。

自身もこのアプリの1ユーザなのですが、使っていて、こんな機能がほしいと思うことがありました。それが、本記事で扱う(ホーム)ウィジェット機能です。具体的には、 **自分が最近、翻訳した結果をホーム画面上に表示したら、内容確認のためだけにアプリを立ち上げる必要がなくなり、便利ではないかと考えた** のがきっかけです。

![セカイフォンFlutter版](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34766/5834e38a-1e42-18a2-152e-cd63573b7faa.png)

ここからは、Flutterアプリに(ホーム)ウィジェットを追加していく方法を紹介していきます。これ以降に出てくる **ウィジェットは、Flutterの世界におけるいわゆるWidgetのことではなく、全て(ホーム)ウィジェットのことを指しています** 。ご注意ください。

# (ホーム)ウィジェット機能実装の流れ
## 実現方法
まず、(ホーム)ウィジェット機能を実現する流れを考えていきましょう。例えば、Androidにおいて、ネイティブで同機能を実現する場合は、[Android for DevelopersのCreate a simple widget](https://developer.android.com/guide/topics/appwidgets)にあるとおり、 **幾つかのクラスやレイアウトファイルなどを作る** 必要があります。以下の図は、同サイトから転載させていただいています。

![Androidのホームウィジェット](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34766/9f7a9a56-b034-058e-c5af-71c6e68a569a.png)

iOSについても同様で、幾つかのファイルを作るなど準備が必要となります。

ただ、今回はFlutterアプリとして本機能を実現する必要があり、かつ、できるだけ効率的に実装できないかと考えていました。車輪の再開発をする前にということで、[pub.dev](https://pub.dev/)を調べたところ、幾つか該当する機能を提供してくれているパッケージを見つけることができました。実際に試してみたところ、[home_widget](https://pub.dev/packages/home_widget)が今回はベストマッチすると判断し、こちらを使わせていただくことにしました。今回は同パッケージの執筆時点における最新版(version 0.1.5)を用います。

## アプローチ
結論としては、 **AndroidとiOSでそれぞれ向けのネイティブコードを一部書きつつ、共通部分はDartコードで書き、連携させる** というアプローチになります。

例えば、Androidでは、以下のイメージのとおり構成し、必要なファイルを作っていきます。

![Androidのホームウィジェットg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/34766/a6adeb28-bf25-9ed9-302b-aa7336e67183.png)

iOSについては、今回は詳細を割愛しますが、 **同機能を実現するためのWidget Extensionを追加し、そこに必要なネイティブコード(Swift)を書く** ことになります。

話を戻して、ここからは、Androidをターゲットに、(ホーム)ウィジェットを実現するためのコード例を解説します。

# 実装イメージ(Android編)
先ほど紹介したアプローチに沿って、実際のコード断片を交え、順に解説します。実装の事前準備など、本記事ではカバーしていない箇所が幾つかあります。詳細は、[home_widget公式サイト](https://pub.dev/packages/home_widget)を参照するのがおすすめです。

## 1. App Widget Layout
ホーム画面上に表示する画面のレイアウトを作ります。コード例の断片は、以下のとおりです。

```xml:(android_src)/res/layout/sample_widget_layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
        <ImageView
            android:id="@+id/icon"
            android:layout_margin="4dp"
            android:layout_width="48dp"
            android:layout_height="48dp"
            android:src="@drawable/chara1" />
<…>
```

実際の画面では、LinearLayoutおよびImageViewやTextViewなどを使って、レイアウトを作っています。ファイルパスやファイル名は、任意ですので、各自の環境に合わせ読み替えてください。

## 2. App Widget Configuration
次に、(ホーム)ウィジェットの設定を行うファイルを作ります。コード例は、以下のとおりです。

```xml:(android_src)/res/xml/sample_widget_config.xml
<?xml version="1.0" encoding="utf-8"?>
<appwidget-provider
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:initialLayout=“@layout/sample_widget_layout"
    android:minWidth="320dp"
    android:minHeight="40dp"
    android:minResizeWidth="320dp"
    android:minResizeHeight="40dp"
    android:widgetCategory="home_screen" />
```

(ホーム)ウィジェットであることを示すために、**widgetCategory="home_screen"** と設定します。さらに、ウィジェットの大きさ( **minWidth/minHeight/minResizeWidth/minResizeHeightプロパティ** )や、1.で準備したファイルのパス( **initialLayoutプロパティ** )などを設定します。

 **ここで注意点があります。(ホーム)ウィジェットの大きさの設定にはルールがあり、それに従う必要** があります。詳細は[Android
for DevelopersのCreate a simple widget](https://developer.android.com/guide/topics/appwidgets)に記載されていますので、そちらを確認するのがおすすめです。

## 3. App Widget Provider 
次に、(ホーム)ウィジェットの振る舞いを制御するためのウィジェットプロバイダーを作りましょう。Kotlinで書いています。

```kotlin:(android_src)/SampleAppWidgetProvider.kt
class SampleAppWidgetProvider : HomeWidgetProvider() {
    override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
        appWidgetIds.forEach { widgetId ->
            val views = RemoteViews(context.packageName, R.layout.sample_widget_layout).apply {
                var srcText = widgetData.getString("_srcText", "")
                setTextViewText(R.id.srcText, srcText)
            }
            appWidgetManager.updateAppWidget(widgetId, views)
        }
    }
}
```

HomeWidgetProviderクラスを継承して、同クラスを作成します。1. で準備したホーム画面のレイアウトファイルのパスを設定し、RemoteViewsを取得しています。この後の5.で紹介するDartで作るクラスから渡ってくる値(パラメータは"_srcText")を受け取り( **widgetData.getString("_srcText", "")** )、ウィジェットの表示に反映しています( **setTextViewText(R.id.srcText, srcText)**) 。

## 4. Android Manifest
次に、AndroidManifest.xmlの編集です。

```xml:(android_src)/AndroidManifest.xml
<receiver android:name=".SampleAppWidgetProvider" >
    <intent-filter>
        <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
    </intent-filter>
    <meta-data android:name="android.appwidget.provider"
        android:resource="@xml/sample_widget_config" />
</receiver>
```

3.で準備したウィジェットプロバイダーの定義を追加し、さらに、2.で準備したウィジェットの設定ファイルのパスを設定しています。

## 5. Dart Code
最後に、Flutter(Dart)側のコードです。

```dart:(flutter_src)/hoge.dart
Future<void> updateAppWidget() async {
    HomeWidget.saveWidgetData<String>('_srcText', "Sample Data from Dart");
    HomeWidget.updateWidget(
        name: 'SampleAppWidget',
        androidName: 'SampleAppWidgetProvider',
        iOSName: 'SampleAppWidgetExtention');
}
```

まず、Flutter(Dart)側→(ホーム)ウィジェットへデータを送ります( **HomeWidget.saveWidgetData()** )。データをセットするときに、Key-Value方式で、Keyを"_srcText"、Valueを"Sample Data from Dart"としています。"_srcText"は、3.で説明したウィジェットプロバイダーで使っていたものを指します。実際は、送りたい適切な内容を設定することになります。

次に、(ホーム)ウィジェット側に反映します( **HomeWidget.updateWidget** )。その際、パラメータとして、Androidについては、 **ウィジェットプロバイダーの名前(3.で準備したもの)をandroidNameへ**、iOSについては、 **ウィジェットエクステンションの名前をiOSNameへ** 設定します。該当するものが見つからない場合は、nameに設定したものが参照されます。

お疲れ様でした。以上で主要な実装については完了です。本記事では触れませんが、逆方向の連携(ホームウィジェット→Flutter(Dart))を行うことも可能です。必要に応じて、トライしてみてください。

# [+α]会社で取り組んでいる技術者のコミュニティ活動(の一例)
最後におまけのネタです。会社で行われている技術者コミュニティ活動のうち、自身が携わっているものをご紹介します。

当社では、お客様の課題を解決するとともに新たな価値をご提供できるよう、その手段の1つとして、様々な技術を取り扱っています。自分自身も働いていて、いつも多くの刺激をもらっています。その一方で、純粋に技術的知見が特定の組織やチームの中に閉じられることが少なからずあり、(当然、守秘義務遵守は大前提で)何とかできないかなあという考えを持っていました。

これまでも社内では色々な勉強会などを通じて、経験やノウハウを共有する場があり、自身も多くの気付きをいただいています。ただ、新たに別の切り口として、**特定技術/特定メンバにあえて絞り、かつ、全社横断で高いレベルの技術交流をできる場をつくると良いのではないか？** という考えに至りました。そこで、会社で関係する組織の人々と相談し、社内で **「有識者ミートアップ」** という施策を立ち上げました。

これを実行した背景には、さまざまな思いがあります。例えば、 **「高いレベルで技術交流することでお互いの知見をより高めたい」「技術者として個々のさらなる成長につなげたい」** といった考えを持っています。

現在はこの仕組みの中で第1弾として「モバイル・ウェアラブル」に関するコミュニティを立ち上げ、運営しています。自身も社内から有志で集まってくれた同分野の有識者の皆さんと、直近の技術トレンドなどについて定期的に語り合い、刺激をもらっています。 **各分野ごとに技術トレンドも日々進化しており、1個人で全てのトピックをカバーするのは現実的に困難であり、効率的なキャッチアップが大切** だと実感しています。第2弾の活動についても、会社の仲間が立ち上げに向け準備してくれているので、自分としてもできることを協力できたらなと考えています。

昔は、 **会社の文化はそこにあって、与えられるもの** という感覚を持ってしまっていました。実際は、待っていたら与えられるものではなく、 **会社の文化は自分たちで作っていけるものである** という実感を今は持っています。お互いに技術力を高め合う文化をさらに醸成すべく、自分もできることを引き続き、やっていこうと考えています。

# おわりに
本日は「Flutterアプリに(ホーム)ウィジェットを追加してみる＋α」について、ご紹介しました。今回はスペースの都合で省略しましたが、iOSの方も実装済なので、別の記事もしくは、勉強会、セミナーなどで紹介できたらなと考えています。

当社では、様々な分野で多くのエンジニアが活躍しています。興味を持ってくださった方は、[NTTテクノクロス Advent Calendar 2021](https://qiita.com/advent-calendar/2021/ntt-tx)から気になる記事も読んでいただけますとうれしいです。

明日は[@etctaro](https://qiita.com/etctaro)による「Jetpack Composeが正式版になったのでQiitaビューアに機能を追加してみた」です。某アニメが大好きな仲間なので、自分も全集中して読みたいと思います。こちらもお楽しみに！