Android+RetrofitでHTTP通信するアプリの作り方（その４）

# UIの仮組みとViewModelの導入

[前回](https://qiita.com/aburagirio/items/abebf9a7fadf8ef94dce)の記事でHiltを活用できるようになりました。

それではここからアプリを組み上げていきましょう。

## 目次

- [UIの仮組みとViewModelの導入](#uiの仮組みとviewmodelの導入)
  - [目次](#目次)
  - [今回の作業内容](#今回の作業内容)
  - [ViewModelを追加する](#viewmodelを追加する)
    - [ViewModelの定義](#viewmodelの定義)
  - [UIを追加する](#uiを追加する)
    - [Sealed Interface](#sealed-interface)
  - [通信状態に応じてUIを更新する機構を追加する](#通信状態に応じてuiを更新する機構を追加する)
    - [MutaleState](#mutalestate)
    - [Stateのhoist](#stateのhoist)
    - [MainActivity](#mainactivity)
  - [できあがり](#できあがり)
  - [リポジトリ](#リポジトリ)


## 今回の作業内容

1. ViewModelを導入する
2. UIを追加する
3. 通信状態に応じてUIを更新する機構を追加する

## ViewModelを追加する

少々ネタバレですが、通信機能とUI更新機能を全部MainActivityに書き込むと、MainActivityの可読性が著しく落ちます。

なので、必要最小限のものはMainActivityに残しますが、必要最小限ではない事柄は全部ViewModelへ移します。

### ViewModelの定義

まず、`プロジェクトエクスプローラー上の任意のディレクトリを右クリック　→　New　→ Kotlin File/Class`とたどり、`JsonPlaceHolderViewModel`クラスを追加します。

```kotlin : JsonPlaceHolderViewModel.kt
class JsonPlaceHolderViewModel  {}

```

これを、Hiltの管理下にあるViewModelだという定義に書き換えます。

```kotlin : JsonPlaceHolderViewModel.kt
@HiltViewModel
class JsonPlaceHolderViewModel @Inject constructor() : ViewModel (){}
```

このViewModelに通信の実行と実行結果としてのUIステート生成を委ねます。

## UIを追加する

とりあえず仮組みのUIとして、ロード中、ロート完了、エラーの3状態を表示できるUIを追加したいと思います。

### Sealed Interface

通信の状態が3つあるとして、それをUIステートに割り当てる場合は`Sealed Interface`が使えます。

```kotlin 
sealed interface JsonPlaceHolderUIState{
    data object Success(val posts: List<Post>): JsonPlaceHolderUIState
    data object Error: JsonPlaceHolderUIState
    data object Loading: JsonPlaceHolderUIState
} 
```

この`Sealed Interfae`により、以下のようなUIステートを生成できます。

1. 通信に成功したら`Post`型のListを引数とするSuccess、
2. 通信中ならLoading
3. 通信にエラーが起きたらError

## 通信状態に応じてUIを更新する機構を追加する

### MutaleState

さきほど定義した3つの状態を広報するMutableStateをViewModelに定義します。


```kotlin : JsonPlaceHolderViewModel.kt
@HiltViewModel
class JsonPlaceHolderViewModel @Inject constructor() : ViewModel (){
      ...
      var jsonPlaceHolderUIState: JsonPlaceHolderUIState by mutableStateOf(JsonPlaceHolderUIState.Loading)
      ...
}
```

### Stateのhoist

先ほど定義した3状態に応じて表示を差し替える機構を追加します。

```kotlin
@Composable
fun JsonPlaceHolderUI(
    jsonPlaceHolderUIState: JsonPlaceHolderUIState,
    modifier: Modifier = Modifier
){
    /**
     * jsonPlaceHolderUIStateの状態に応じて表示を差し替える
     */
    when (jsonPlaceHolderUIState){
        JsonPlaceHolderUIState.Error -> {
            Text(text = "Error", modifier = modifier)
        }
        JsonPlaceHolderUIState.Loading -> {
            Text(text = "Loading", modifier = modifier)
        }
        is JsonPlaceHolderUIState.Success -> {
            val p = jsonPlaceHolderUIState.posts
            Text(text = "Success, size is " + p.size, modifier = modifier)
        }
    }
}
```

やっていることは単純で、状態を受け取ってwhenでTextを差し替えるだけです。

### MainActivity

ViewModel内のMutableStateを読み取って、Composableな関数へ渡します。

このような仕組みを`State Hoist`と呼びます。

<https://developer.android.com/jetpack/compose/state#state-hoisting>

```kotlin
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
  ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ,,,
        private val jsonPlaceHolderViewModel: JsonPlaceHolderViewModel by viewModels()
        ...
        setContent {
            RetrofitTheme {
                // A surface container using the 'background' color from the theme
                Surface(
                    modifier = Modifier.fillMaxSize(),
                    color = MaterialTheme.colorScheme.background
                ) {
                    JsonPlaceHolderUI(
                        jsonPlaceHolderUIState = jsonPlaceHolderViewModel.jsonPlaceHolderUIState
                    )
                }
            }
        }
        ...
    }
    ...
}
```

State Hoistの仕組みを取り入れることで、PreviewやTestが書きやすくなります。

## できあがり

できました。

通信状態に応じてUIが差し代わるようになりました。

1. 通信中は`Loading`
2. 通信に成功したら`Success, size is 100`
3. エラー時は`Error`（機内モードで通信するとエラーを発生させることができます）

## リポジトリ

<https://github.com/aburagirio/retrofitExample.git>

ブランチ`part4`をcheck outしてください。