onGetViewFactoryが1回しか呼ばれなくて泣きそうになった話

どっことです。初投稿です。

## この記事の目的
Androidアプリでウィジェット機能を実装してたけど、
複数ウィジェットを表示させたときに思った通りに動いてくれなかったので備忘録。

## どんなアプリ作ってたの？
タスク管理アプリ。タスクリストを登録して、その中にタスクを追加できる。
タスクリストとタスクの関係は以下のようなイメージ。

 - タスクリストA 
    - タスク1
    - タスク2
    - タスク3
 - タスクリストB
   - タスクX
 - タスクリストC
   - タスクα
   - タスクβ 
   
Androidウィジェットをホーム画面に表示するとき、タスクリスト一覧からタスクリストを選択すると、
そのリストのタスク一覧がウィジェット上に表示される。
ウィジェットのタスク一覧はListViewで表示される。

### ウィジェットの設定についてもうちょっと詳細
タスクリスト一覧からリストが選択されたとき、
追加されるウィジェットのWidget Id と、選択したタスクリストのIDをkey-value形式でPreferenceに保存しておく。
その後、RemoteViewFactoryが生成されるときにPreferenceからWidget IDをkeyにタスクリストのIDを取得する。

## 何があったの？
### 2つ目以降のウィジェットに設定が正しく反映されない。
複数のウィジェットをホーム画面に表示する時、選択したリストのタスク一覧が表示されず、
1つ目と同じリストのタスク一覧が表示される。

## 根本的な原因は？
### RemoteViewsService.onGetViewFactoryが最初にウィジェットを追加した時の1回しか呼ばれてない。

ウィジェットの追加・設定完了したタイミングで、毎回RemoteViewsService.onGetViewFactoryが呼ばれる想定でいたのだけど、
なぜか1つ目のウィジェットの追加・設定完了したタイミングしか呼ばれない。
そのせいで2つ目以降のウィジェットを追加しても、1つ目の情報が使い回しされてる。

以下がその時の実装

```java:AppWidgetProvider.java
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int widgetId : appWidgetIds) {

            **中略**

            // リスト
            Intent widgetServiceIntent = new Intent(context, WidgetService.class);
            // widgetIdをIntentにputExtraする
            widgetServiceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); 
            remoteViews.setRemoteAdapter(R.id.widget_list_view, widgetServiceIntent);

            **以下略**
        } 
    }
```

```java:RemoteViewsFactory.java
    // コンストラクタ
    RemoteViewsFactory(Context context, Intent intent) {
        // IntentからwidgetIdをgetExtraする
        mWidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); 
        int listId = PreferenceUtil.getWidgetTaskListId(mContext, mWidgetId);
        mTaskInfoList = TasksDao.newInstance(mContext).getTaskList(listId);
   }
```

で、解決できずに2週間が経った...


## 対応方法

[stackoverflowさん](http://stackoverflow.com/questions/11350287/ongetviewfactory-only-called-once-for-multiple-widgets)に解決策があがってました。さすがSOF！
ワタシ英語ワカラナイのですが、WidgetServiceを呼び出すIntentにputExtraしちゃダメーってことだけは理解できた。
あと、WidgetService.onBind()のコードを読むとなんでダメーっか分かるらしい。(ただしそこまで読む気力はない)


```java:AppWidgetProvider.java
    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        for (int widgetId : appWidgetIds) {

            **中略**

            // リスト
            Intent widgetServiceIntent = new Intent(context, TasksWidgetService.class);
            widgetServiceIntent.setData(Uri.fromParts("content", Integer.toString(widgetId), null));
            //widgetServiceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId); // TaskItemWidgetFactoryが呼ばれなくなる
            remoteViews.setRemoteAdapter(R.id.widget_list_view, widgetServiceIntent);

            **以下略**
        } 
    }
```

```java:RemoteViewsFactory.java
    // コンストラクタ
    RemoteViewsFactory(Context context, Intent intent) {
        //mWidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); // TaskItemWidgetFactoryが呼ばれなくなる
        mWidgetId = Integer.parseInt(intent.getData().getSchemeSpecificPart());
        int listId = PreferenceUtil.getWidgetTaskListId(mContext, mWidgetId);
        mTaskInfoList = TasksDao.newInstance(mContext).getTaskList(listId);
   }
```

## ちなみに

RemoteViewsFactory以外の箇所でどうにかならないかねーってやってみたけど、どうにもならなかった。
Intentに色々putExtraしてデータ渡そうとしても、結局同じことが原因で情報が更新されず、うまくできなかった。
ひたすら迷走してたけど、そこまで書く気力がないのと原因が同じなので省略。

## 最後に

ウィジェットにListViewを使いたくて
「先人様のコードを程良く~~コピペ~~参考すればいい具合になるでしょー」なモチベーションで実装始めたけど、
先人様がそんなに多くなかったのと、まさかこんなに(2週間)ハマるとは思わなかった。

同じことで悩んでるやつ絶対いるだろコレ。。。
ってことで私の記事で誰かが救われたら何よりです。