FlutterFragmentとViewPagerの組み合わせはonResumeのタイミングで難しかった

[ViewPager2](https://developer.android.com/reference/androidx/viewpager2/widget/ViewPager2)を使って**2ページ目以降**に[FlutterFragment](https://flutter.dev/docs/development/add-to-app/android/add-flutter-fragment?tab=forward-activity-calls-kotlin-tab)を設定すると、完全にページ遷移するまではFlutterFragmentが描画されず真っ白になります。一度表示すると真っ白になりません。

![device-2020-07-28-223018.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/7026b8a7-1e21-3bd4-af45-4e8b3b41f14f.gif)

このように遷移途中でもFlutterFragmentが描画されて欲しいです。

![device-2020-07-29-065552.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/30d1768e-ced2-4c80-174d-f8a87d2d3075.gif)

# なぜそうなるか

## 作り方

ViewPager2のAdapterはこのように作りました。

```MyFragmentStateAdapter.kt
class MyFragmentStateAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
    override fun getItemCount() = 2

    override fun createFragment(position: Int): Fragment {
        return if (position == 0)
        // AndroidネイティブなFragment
            MainFragment()
        else
        // FlutterFragment
            FlutterFragment.withNewEngine()
                    .shouldAttachEngineToActivity(false)
                    .build()
    }
}
```

ViewPager2はこのように設定しました。

```PagerActivity.kt
viewPager.adapter = MyFragmentStateAdapter(this)
// こちらを設定しないとスワイプ開始時に一瞬ひっかかる
viewPager.offscreenPageLimit = 1
```

## Fragmentのライフサイクルを確認する

2ページ目をFlutterFragmentから空のフラグメントにします。

```MyFragmentStateAdapter.kt
class MyFragmentStateAdapter(activity: FragmentActivity) : FragmentStateAdapter(activity) {
    override fun getItemCount() = 2

    override fun createFragment(position: Int): Fragment {
        return if (position == 0)
            MainFragment()
        else
            EmptyFragment()
    }
}
```

空のフラグメントのライフサイクルを確認します。

```EmptyFragment.kt
class EmptyFragment : Fragment() {
    override fun onStart() {
        super.onStart()
        Log.d("EmptyFragment", "onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d("EmptyFragment", "onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d("EmptyFragment", "onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d("EmptyFragment", "onStop")
    }
}
```

logcatを確認した所、onStartは画面を開いたときに呼ばれますが、onResumeは2ページ目に完全遷移するまで呼ばれませんでした。

```sh
# 画面を開いた
D EmptyFragment: onStart
# 2ページ目に完全に遷移
D EmptyFragment: onResume
```

どうやらFlutterFragmentは初回のonResumeで描画されるようです。この挙動を変更する方法は見つけられませんでした。

## ViewPager2ではなくViewPagerを使った場合

コンストラクタの`behavior`フィールドに`BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT`定数を設定した場合はViewPager2と同じくonResumeは2ページ目に完全遷移するまで呼ばれずFlutterFragmentの描画も完全遷移まで開始されません。


```MyFragmentStateAdapter.kt
class MyFragmentStateAdapter(fm: FragmentManager) :
        FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    override fun getCount() = 2

    override fun getItem(position: Int): Fragment {
        return if (position == 0)
            MainFragment()
        else
            FlutterFragment.withNewEngine()
                    .shouldAttachEngineToActivity(false)
                    .build()
    }
}
```

`behavior`フィールドを`BEHAVIOR_SET_USER_VISIBLE_HINT`定数にすれば、画面が開かれると同時にonResumeが呼ばれるので、FlutterFragmentの描画もスワイプ中に行われます。しかしその定数は非推奨扱いなので、後ほど技術的負債になる危険性を感じました。

## onResumeが完全遷移まで呼ばれない理由

非推奨になった[BEHAVIOR_SET_USER_VISIBLE_HINT](
https://developer.android.com/reference/androidx/fragment/app/FragmentStatePagerAdapter#BEHAVIOR_SET_USER_VISIBLE_HINT)定数について調べてみたところ、[setMaxLifecycle](https://developer.android.com/reference/androidx/fragment/app/FragmentTransaction#setMaxLifecycle(androidx.fragment.app.Fragment,%20androidx.lifecycle.Lifecycle.State))メソッドが紹介されていて、それについて調べてみたところ、こちらの記事に行き着きました。

[setUserVisibleHintのdeprecatedとsetMaxLifecycle](https://medium.com/@star_zero/setuservisiblehint%E3%81%AEdeprecated%E3%81%A8setmaxlifecycle-98d98d9aaacf)

この記事の情報を持ってViewPager, ViewPager2のアダプターのソースコードを見てみたところsetMaxLifecycleメソッドの呼び出しで、完全遷移するまではonStartまでしかライフサイクルを遷移させない設定にしていたようです。どうしてもViewPagerを使いたい場合は、ライブラリをフォークしてsetMaxLifecycle呼び出し部分を書き換える必要がありそうです。


# 最終的にはUIデザインを変更しました

結局、横スワイプによるページ切り替えは諦めて、ボタンによる画面呼び出しにしました。

![device-2020-07-29-070749.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/334798/4065fb57-8c11-5853-f3a5-5afd8a0e41ec.gif)

今回に関してはそちらの方がUIとしては分かりやすいと思いました。