Android: ViewModel状態管理サンプル

model/User.kt

package com.example.myapplicationstate.model

data class User {
    val id: Int,
    val name: String
}

model/UiState.kt

package com.example.myapplicationstate.model

sealed class UiState {
    object Loading: UiState()
    data class Success(val users: List<User>): UiState()
    data class Error(val message: String): UiState()
}

種類 主な目的 特徴
sealed class 型の制限つき継承(状態分岐) 継承を制限し、when式で exhaustiveness(漏れなくチェック)できる
data class データ保持専用クラス 自動で toString, equals, copy などが生成される

package com.example.myapplicationstate.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class UserViewModel : ViewModel(){
    private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
    val uiState: StateFlow<UiState> = _uiSate

    init {
        fetchUsers()
    }

    fun fetchUsers(){
        viewModelScope.launch {
            _uiState.value = UiState.Loading
            delay(2000)

            val  result = runCatching {
                getUsersFromApi()
            }

            _uiState.value =result.fold (
                onSuccess={ UiState.Success(it) },
                onFailure = { UiState.Error(it.message ?: "Unknown Error") }
            )
        }
    }

    private suspend fun getUsersFromApi(): List<User> {
        // 成功/失敗を切り替えるための仮コード
        if ((0..1).random() == 0) {
            throw RuntimeException("通信エラー")
        }

        return listOf(
            User(1, "Alice"),
            User(2, "Bob"),
            User(3, "Charlie")
        )
    }
}

ui/UserScreen.kt

package com.example.myapplicationstate.ui.screen
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.material.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import model.UiState

@Composable
fun UserScreen(userViewModel: UserViewModel = viewModel()) {
    val uiState by userViewModel.uiState.collectAsState()

    when (uiState){
        is UiState.Loading -> {
            Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
                CircularProgressIndicator()
            }
        }

        is UiState.Success -> {
            val users = (uiState as UiState.Success).users
            LazyColumn {
                items(users) { user ->
                    Text(
                        text = user.name,
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(16.dp)
                    )
                }
            }
        }

        is UiState.Error -> {
            val message = (uiState as UiSatete.Error).message
            Column (
                modifier = Modifier.fillMaxSize(),
                verticalArrangement = Arrangement.Center,
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text("エラー: $message", color = MaterialTheme.colors.error)
                Spacer(modifier = Modifier.height(16.dp))
                Button(onClick = { userViewModel.fetchUsers() }) {
                    Text("リトライ")
                }
            }
        }
    }
}

## @Composable

@Composable
fun Greeting(name: String) {
    Text(text = "Hello, $name!")
}
@Composable
fun MyScreen() {
    Column {
        Text("こんにちは")
        Button(onClick = { /* クリック処理 */ }) {
            Text("ボタン")
        }
    }
}

MainActivityはアプリの最初の画面
アプリのエントリーポイント

androidではuiとロジックが分けられるMVVMが一般的