FragmentのonCreateView()とonViewCreated()の使い分け

# はじめに
　**Fragment** の **onCreateView(LayoutInflater, ViewGroup, Bundle)** と **onViewCreated(View, Bundle)** の使い分けに関するメモです。

# 結論
　Fragment の onCreateView() と onViewCreated() をどのように使い分けるかについて、まずは結論から。

## ◆ Data Binding を使わない場合

- androidx の Fragment を使う
  [android.app.Fragment](https://developer.android.com/reference/android/app/Fragment) ではなく [androidx.fragment.app.Fragment](https://developer.android.com/reference/androidx/fragment/app/Fragment) を使う。[^1]

- layout の inflate は [コンストラクタ](https://developer.android.com/reference/androidx/fragment/app/Fragment#Fragment(int))で指示する

- onCreateView() は使わない
  [onCreateView(LayoutInflater, ViewGroup, Bundle)](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle)) はオーバーライドせずデフォルト実装を用いる。

- [onViewCreated(View, Bundle)](https://developer.android.com/reference/androidx/fragment/app/Fragment#onViewCreated(android.view.View,%20android.os.Bundle)) にて view 以下の初期化を行う
  [onViewCreated(View, Bundle)](https://developer.android.com/reference/androidx/fragment/app/Fragment#onViewCreated(android.view.View,%20android.os.Bundle)) にて [onCreateView()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle)) の戻り値として返された View の初期化を行う。[^2]

## ◆ Data Binding を使う場合

- androidx の Fragment を使う
  [android.app.Fragment](https://developer.android.com/reference/android/app/Fragment) ではなく [androidx.fragment.app.Fragment](https://developer.android.com/reference/androidx/fragment/app/Fragment) を使う。[^1]

- onCreateView() にて layout の inflate から初期化まで全て行う
  - 普通に Data Binding を用いて layout を inflate する。
  - inflate された view の設定を全て行う。

- [onViewCreated(View, Bundle)](https://developer.android.com/reference/androidx/fragment/app/Fragment#onViewCreated(android.view.View,%20android.os.Bundle)) は使わない
  androidx の Fragment が推奨している方法ではなくなってしまうのだが、onViewCreated() で初期化を行う場合、onCreateView() と onViewCreated() にて XxxBinding を手動で指定する必要があり、冗長かつ bug prone なので。[^3]

# androidx 前後の仕様の差異
　onViewCreated(View, Bundle) の javadoc を見ると、従来のものと androidx のものは微妙に異なります。

以下に、javadoc の差異を<font color="red">赤字</font>で示します。(差異の赤文字列の後の※印は説明のために加筆したものです)

## [android.app.Fragment#onCreateView()](https://developer.android.com/reference/android/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle))

> Called to have the fragment instantiate its user interface view. This is optional, and non-graphical fragments can return null <font color="red">(which is the default implementation)[<sup>※1</sup>](#1-戻り値のデフォルト実装の差異)</font>. This will be called between onCreate(android.os.Bundle) and <font color="red">onActivityCreated(android.os.Bundle)[<sup>※2</sup>](#2-onactivitycreatedbundle-が-deprecated-に)</font>.

> If you return a View from here, you will later be called in onDestroyView() when the view is being released.

## [androidx.fragment.app.Fragment#onCreateView()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle))

> Called to have the fragment instantiate its user interface view. This is optional, and non-graphical fragments can return null. This will be called between onCreate(Bundle) and <font color="red">onViewCreated(View, Bundle).[<sup>※2</sup>](#2-onactivitycreatedbundle-が-deprecated-に)</font>

> <font color="red">A default View can be returned by calling Fragment(int) in your constructor. Otherwise, this method returns null.[<sup>※1</sup>](#1-戻り値のデフォルト実装の差異)</font>

> <font color="red">It is recommended to only inflate the layout in this method and move logic that operates on the returned View to onViewCreated(View, Bundle)[<sup>※3</sup>](#3-oncreateview-では-layout-の-inflate-のみ行うことが推奨に)</font>.

> If you return a View from here, you will later be called in onDestroyView() when the view is being released.

## ※1: 戻り値のデフォルト実装の差異
　androidx では Fragment のコンストラクタとして [Fragment(int contentLayoutId)](https://developer.android.com/reference/androidx/fragment/app/Fragment#Fragment(int)) が追加され、引数で与えた layout が [onCreateView()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle)) のデフォルト実装にて自動的に inflate されて戻り値として返されるようになりました。そのため、[onCreateView()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle)) のデフォルトの戻り値が必ずしも null になるわけではなくなりました。

```java
@LayoutRes
private int mContentLayoutId;

@ContentView
public Fragment(@LayoutRes int contentLayoutId) {
    this();
    // ここでフィールドにレイアウトIDが保存される。
    mContentLayoutId = contentLayoutId;
}

@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
        @Nullable Bundle savedInstanceState) {
    // フィールドにレイアウトIDが保存されている場合は inflate して返す。
    // 0 は未指定を意味する。
    if (mContentLayoutId != 0) {
        return inflater.inflate(mContentLayoutId, container, false);
    }
    return null;
}
```

## ※2: onActivityCreated(Bundle) が deprecated に
　[onActivityCreated()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onActivityCreated(android.os.Bundle)) が API level 28 から deprecated になったため、それに合わせて javadoc も変更になったようです。
　(これは使い分けの件とは無関係なので無視してOK)

## ※3: onCreateView() では layout の inflate のみ行うことが推奨に
　[onCreateView()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle)) では layout の inflate のみを行い、このメソッドの戻り値の view に対する操作は [onViewCreated()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onViewCreated(android.view.View,%20android.os.Bundle)) に移動することが **強く** 推奨されるようになりました。

# 考察
- androidx 前に戻る必要性を感じないので、そちらは考えるのをやめよう。
- androidx 以降に関しては、コンストラクタ引数として layout を指定すると onCreateView() のデフォルト実装がその layout を inflate して返すという実装があり、また、実際問題として layout を inflate する以外の実装(動的に構成するとか)をする必要性が全く思いつかないので、もうそれだけでいいじゃんという気がしている。
- という感じで、ひとまずの[結論](#結論)をまとめた。

# 雑感
　正直なところ、androidx の前の android.app.Fragment の onCreateView() と onViewCreated() の使い分けは今でもよくわからない。javadoc やソースコードを読む限り、onCreateView() だけでいーじゃんと思ってしまう。もしかしたらうまい使い分けの考え方があるのかもしれないけれども、少なくとも、開発チームにおいて厳密なルールや共通認識が無い状態で各々が勝手に使い分けをすると、保守で結構困る。それならば onCreateView() だけに統一したほうが保守しやすいと思える。

androidx 以降については、ある程度しっくりきている気がする。コンストラクタで layout を指定できるようになり、onCreateView() にて layout の inflate だけ行う実装が実際に示されたのが大きい。言葉だけで **onCreateView() では inflate だけ行い view への操作は onViewCreate() で行う** とか言われても、**getViewById() してそこに inflate する場合はどっちのメソッドに入れればいいんだよ！** とか結構悩ましくなると思うのだが、コンストラクタと onCreateView() のデフォルト実装を見れば、意図は一目瞭然(だと思う)。

[^1]: 移行コストは時間の経過と共に指数関数的に増大するのだから、早くやるに越したことはないと思う。
[^2]: コンストラクタに layout の id を渡し、かつ [onCreateView()](https://developer.android.com/reference/androidx/fragment/app/Fragment#onCreateView(android.view.LayoutInflater,%20android.view.ViewGroup,%20android.os.Bundle)) をオーバーライドせずにデフォルト実装を用いた場合、layout が inflate されただけの View を扱うことになる。基本的にこのパターンだけでいいんじゃないかという気がする。
[^3]: そこら辺を考慮した形にSDK側が改良されるのを待つしかない気がする。なんかいい方法ないかな、、、。