[Java]Android Fragment間のデータ受け渡し　Bundle vs ViewModel

# 目的
Android, Javaで開発するにあたり、Fragment間のデータ受け渡しにBundleとViewModelどちらを使うべきか指標を明示する。
※ただしデータの値は変更しない前提です。

# 結論
Bundle：Fragment間で受け渡すデータ数が少ない場合
ViewModel：Fragment間で受け渡すデータ数が多い場合
のように使い分けるのが良いと思います。

# はじめに
初めてAndroid開発をしたときに、Fragmentから別Fragmentにデータを受け渡すとき、メジャーな方法としてBundleとViewModelがあると知りました。
両方使ってみて、それぞれの利点・欠点が少し分かったので、まとめてみます。
ちなみに筆者は最初はBundleを使いまくっていたのですが、以下に記載する欠点により、ViewModelをメインで使うようになりました。

# Bundle vs ViewModel
BundleとViewModelの利点・欠点をまとめます。

|   |Bundle|ViewModel|
|---|---|---|
|利点|データの定義が不要<br>データのスコープが狭い|データ名／型の管理がシンプル<br>Fragmentへの渡し不要|
|欠点|データ名／型の管理が煩雑<br>Fragmentへの渡し必要|データの定義が必要<br>データのスコープが広い|

以下、それぞれ説明していきます。
## Bundle
### 利点
#### データの定義が不要

BundleはputStringやputIntなどでデータ名を指定し値設定、getStringやgetIntなどでデータ名を指定し値取得ができます。

```java:FirstFlagment.java

public class FirstFlagment extends Fragment {
    ...
    private void moveFragment() {
        Bundle bundle = getArguents();
        bundle.putInt("Apple", 100);// ★値設定
        SecondFragment secondFragment = new SecondFragment();
        SecondFragment.setArguments(bundle);
        // フラグメント遷移
        getChildFragmentManager()
                .beginTransaction()
                .add(R.id.first_layout, secondFragment)
                .commit()
    }
    ...
}
```
```java:SecondFlagment.java

public class SecondFlagment extends Fragment {
    ...
    @Override
    public void onViewCreated(View view, Bundle bundle) {
        Bundle bundle = getArguents();
        int value = bundle.getInt("Apple");// ★値取得
        ...
    }
    ...
}
```

上記のように、データ名を指定して簡単に値を設定し渡すことができます。

#### データのスコープが狭い
上記の例のように、Bundleを渡したフラグメントのみ、該当データを取得できるため、データのスコープは狭く制限できていると言えます。

### 欠点
#### データ名／型の管理が煩雑
データの種類が増えてくると、当然データ名が複数必要になります。
また、各データの型が違う場合は、どのデータがどの型か、の管理も必要になります。
要は、データの種類が増えると、取り出すときにどんなデータ名と型で登録したっけ？と分かりづらくなっちゃうってことです。

以下は、データの設定と取得でデータ名／型が一致していないため、NGとなるケースです。

```java:データの設定
bundle.putInt("Apple", 30);
bundle.putString("Orange", "30");
bundle.putInt("Lemon", 60);
```

```java:データの取得
int apple = bundle.getInt("Apple");// ← OK
int orange = bundle.getInt("Orange");// ← NG: getStringで取得していない
int lemon = bundle.getInt("Lemo");// ← NG: データ名誤り
```

#### Fragmentへの渡し必要
FragmentのsetArgumentsでBundleを渡す必要があります。
これを忘れると、次のFragmentでBundleを使えません。

```java:データの取得
Bundle bundle = getArguents();
bundle.putInt("Apple", 100);
SecondFragment secondFragment = new SecondFragment();
SecondFragment.setArguments(bundle);// ← これを忘れるとデータを渡せない
```

## ViewModel
### 利点
#### データ名／型の管理がシンプル

ViewModelの属性としてデータを定義するため、データ名(変数名)と型をシンプルに管理できます。これで、データ名と型はこのクラスを見れば間違いなく分かります。

```java:MyViewModel.java

public class MyViewModel extends ViewModel {
    public int apple;
    public String orange;
    public int lemon;
    ...
```

データの設定／取得は、ViewModelProviderでViewModelを取得して実行できます。

```java:FirstFlagment.java

public class FirstFlagment extends Fragment {
    ...
    private void moveFragment() {
        MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        viewModel.apple = 100;// ★値設定
        SecondFragment secondFragment = new SecondFragment();
        // フラグメント遷移
        getChildFragmentManager()
                .beginTransaction()
                .add(R.id.first_layout, secondFragment)
                .commit()
    }
    ...
}
```
```java:SecondFlagment.java

public class SecondFlagment extends Fragment {
    ...
    @Override
    public void onViewCreated(View view, Bundle bundle) {
        MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        int value = viewModel.apple;// ★値取得
        ...
    }
    ...
}
```

#### Fragmentへの渡し不要

上記コードの通り、ViewModelのデータ取得／設定はViewModelProvider経由で行うので、bundleのように次フラグメントへの渡し忘れは起きません。

### 欠点
#### データの定義が必要

上記のコード例の通り、ViewModel継承したクラスを定義し、受け渡すデータをすべて属性として定義する必要があります。
そのためBundleと比べると、ひと手間が必要で、1,2個だけデータを受け渡したいときはわざわざViewModelを作るのは面倒に感じます。

#### データのスコープが広い
ViewModelは、ViewModelProviderを用いてどのフラグメントからも取得可能です。
よって、データのスコープが広くなっていると言えます。
チームで開発をしている場合、ViewModelの値がどこかで書き換えられているかもしれませんので、注意して使う必要があります。
私の場合、アプリ全体を一人で開発したため、ViewModelは読み取り専用と決めて使ったため、このデメリットは問題になりませんでした。

```java:ViewModelProvider経由でどこからでもデータ取得可能
MyViewModel viewModel = new ViewModelProvider(this).get(MyViewModel.class);
```

# まとめ

- Bundle
　データの定義が不要でスコープも限定できるが、
　データ名／型の管理が煩雑でFragmentへの渡し必要。
　⇒Fragment間で受け渡すデータ数が少ない場合　に使い易い。

- ViewModel
　データ名／型の管理がシンプルでFragmentへの渡し不要だが、
　データの定義が必要でデータのスコープが広い。
　⇒Fragment間で受け渡すデータ数が多い場合　に使い易い。

と結論づけました。

# 最後に
- 筆者はAndroid開発に携わって1年ほどです。
- まだまだ知見不足の点があるかと思いますので、お気づきの点はご指摘くださると助かります。

# 参考
https://developer.android.com/topic/libraries/architecture/viewmodel?hl=JA#sharing
https://qiita.com/m-coder/items/3a8e66d49f2830b09bf4