[Android] Flow × Room × Retrofit でデータ更新をリアルタイム反映させる

こんにちは。
今回は、前回紹介したFlowの応用編です。 アプリ開発では「サーバーから取得したデータをDBに保存して、それをUIに即反映する」というパターンが多いですよね。

ここでは、Flow × Room × Retrofit を使って、 「データ取得 → DB保存 → UI自動更新」までの流れを構築していきます。


今回のゴール

今回の流れは以下の通りです:

  1. Retrofitでサーバーからデータを取得
  2. Roomにデータを保存
  3. RoomのFlowでUIを自動更新

つまり、「UI → ViewModel → Repository → Retrofit/Room」のモダン構成ですね。


1. データモデルの定義


@Entity(tableName = "user_table")
data class UserEntity(
    @PrimaryKey val id: Int,
    val name: String,
    val email: String
)

DBで扱うユーザー情報のエンティティです。


2. DaoでFlowを返す

Roomでは、Flowを返すようにすると自動的に変更検知が行われ、 DBのデータが更新されるとFlow経由でUIに通知されます。


@Dao
interface UserDao {

    @Query("SELECT * FROM user_table")
    fun getAllUsers(): Flow<List<UserEntity>>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertAll(users: List<UserEntity>)
}

これだけで「DB変更 → Flow更新 → collectでUIに反映」が可能になります。


3. Retrofitの定義


interface ApiService {
    @GET("users")
    suspend fun getUsers(): List<UserEntity>
}

object ApiClient {
    private const val BASE_URL = "https://example.com/api/"

    val retrofit: ApiService by lazy {
        Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
            .create(ApiService::class.java)
    }
}

シンプルなRetrofit定義です。 実際のプロジェクトではレスポンス用DTOを作成してEntityにマッピングするのが定石ですが、 ここでは分かりやすく同じ構造にしています。


4. RepositoryでFlowを仲介


class UserRepository(
    private val api: ApiService,
    private val dao: UserDao
) {

    // RoomからFlowで取得
    fun getUsers(): Flow<List<UserEntity>> = dao.getAllUsers()

    // ネットワークから取得してDBに反映
    suspend fun refreshUsers() {
        val users = api.getUsers()
        dao.insertAll(users)
    }
}

Repositoryは、UI層から「Flowを返す+ネットワーク更新を指示される」形になります。


5. ViewModelでcollectする


class UserViewModel(
    private val repository: UserRepository
) : ViewModel() {

    val userList: StateFlow<List<UserEntity>> =
        repository.getUsers()
            .stateIn(viewModelScope, SharingStarted.Lazily, emptyList())

    fun refresh() {
        viewModelScope.launch {
            repository.refreshUsers()
        }
    }
}

ポイントは、RoomのFlowStateFlowに変換してUIに公開している点です。 UI側でcollectすれば、DB更新時に自動で再描画されます。


6. UIで表示する


lifecycleScope.launch {
    viewModel.userList.collect { users ->
        val names = users.joinToString("\n") { it.name }
        binding.textView.text = names
    }
}

// 手動で更新ボタン
binding.refreshButton.setOnClickListener {
    viewModel.refresh()
}

Flowの強みがここで発揮されます。 DBに変更があるたびに、自動的にFlowが新しいリストをemitし、UIが更新されます。


7. 全体の流れをまとめると


ユーザー操作(更新ボタン)
    ↓
ViewModel.refresh()
    ↓
Repository.refreshUsers() → RetrofitでAPI呼び出し
    ↓
UserDao.insertAll() → Room更新
    ↓
UserDao.getAllUsers() Flowが変化を検知
    ↓
UIが再描画

つまり、Flowが「UIとDBの橋渡し」をしてくれるということですね。


まとめ

  • RoomのFlowでDB変更を自動検知
  • Retrofitで取得したデータをDBに流し込む
  • ViewModelではstateIn()でUI向けに変換
  • UIはcollectするだけで常に最新状態を表示

Flowを組み合わせると、これまで面倒だった「UI更新通知」が不要になります。 LiveDataよりも宣言的で、Composeとの相性も抜群です。


ではまた。

コメント

タイトルとURLをコピーしました