Jetpack Compose 初期+簡素UIコード解説〜基本概念

初期コード解説

① Activityを作る

class MainActivity : ComponentActivity() {

これは

「MainActivityという画面を作ります」

という意味です。

ComponentActivity

Androidの画面の基本クラスです。

昔は

AppCompatActivity

を使うことが多かったですが、

Jetpack Composeでは ComponentActivity が標準です。

② onCreate()

override fun onCreate(savedInstanceState: Bundle?) {

画面が最初に表示されるときに呼ばれる関数です。

例えば

  • 初期化
  • ボタンの設定
  • 画面を作る

などをここで行います。

③ 親クラスの初期化

super.onCreate(savedInstanceState)

これは

「ComponentActivityの初期化もちゃんと実行してください」

という意味です。

これを書かないとActivityが正常に動きません。

④ Edge-to-Edge

enableEdgeToEdge()

Android15以降で推奨されている設定です。

画面を

┌───────────────┐
│ステータスバー│
├───────────────┤
│ │
│ アプリ │
│ │
├───────────────┤
│ナビゲーション│
└───────────────┘

ではなく

┌───────────────┐
│ステータスバー│
│ │
│ アプリ │
│ │
│ │
│ナビゲーション│
└───────────────┘

のように、

画面いっぱいまで描画する設定です。

iPhoneのSafe Areaに少し似ています。

⑤ Compose開始

setContent {

ここが一番重要です。

これは

「ここからComposeで画面を書きます」

という意味です。

昔のAndroidなら

setContentView(R.layout.activity_main)

でした。

ComposeではXMLを使わないため、

setContent { ... }

の中に画面を書きます。

⑥ テーマ

Teat1Theme {

これはアプリ全体のデザインです。

例えば

  • 色
  • フォント
  • 角丸
  • Material Design

などをまとめています。

SwiftUIなら

Environment

や

.tint()

に少し近いイメージです。

テーマを外しても画面は表示できますが、

Material Designらしい見た目ではなくなります。

⑦ Scaffold

Scaffold(

Scaffoldは

「画面の骨組み」

です。

例えば

┌────────────────────┐
│TopAppBar │
├────────────────────┤
│ │
│ │
│ コンテンツ │
│ │
├────────────────────┤
│BottomBar │
└────────────────────┘

を簡単に作れるコンポーネントです。

後から

  • TopAppBar
  • BottomBar
  • FAB(丸いボタン)
  • Snackbar

などを追加できます。

⑧ Modifier.fillMaxSize()

modifier = Modifier.fillMaxSize()

Modifierは

「見た目や配置を変更するもの」

です。

ここでは

fillMaxSize()

なので

画面いっぱいに広げる

という意味になります。

例えば

200×200

ではなく

画面全体

になります。

⑨ innerPadding

{ innerPadding ->

Scaffoldは

例えば

TopAppBar

や

BottomBar

の高さを考慮して

この分だけ余白を空けてね

という値を渡してくれます。

それが

innerPadding

です。

⑩ Greeting

Greeting(

これは自分で作ったComposable関数です。

例えば

@Composable
fun Greeting(name: String)

が別の場所にあります。

SwiftUIでいう

GreetingView()

のようなものです。

⑪ 引数

name = "Android",

Greetingへ

"Android"

という文字列を渡しています。

中では

Text("Hello $name")

のように使われます。

⑫ Padding

modifier = Modifier.padding(innerPadding)

ここはとても重要です。

Scaffoldから受け取った余白を適用しています。

もし書かなければ

┌──────────────┐
│TopAppBar │
│Hello Android │ ← 重なる
└──────────────┘

になってしまいます。

書くと

┌──────────────┐
│TopAppBar │
├──────────────┤
│Hello Android │
└──────────────┘

となり、バーの下からコンテンツが始まります。

簡素UIのコード解説

完成イメージはこんな感じです。

┌──────────────────────────┐
│ │
│ │
│ [ 画像 ] │
│ │
│ [ボタン1] [ボタン2] │
│ │
└──────────────────────────┘

① 画像を用意する

app > src > main > res > drawable

の中に画像を置きます。

例えば

logo.png

という名前にします。

ファイル名は 小文字・数字・アンダースコアのみ にしてください。

例

logo.png
app_icon.webp
title_image.png

② MainActivity

setContent の中を次のようにします。

setContent {
Teat1Theme {
MainScreen()
}
}

これで画面を MainScreen() に任せます。


③ MainScreenを作る

④ 必要なimport

Android Studioならほとんど自動で追加してくれますが、主にこれらを使っています。

import自動化の設定

コードの見方

まず画面全体を作っています。

Column(...)

Columnは

上
↓
↓
↓
下

へ並べるレイアウトです。

画面中央に配置

verticalArrangement = Arrangement.Center

これが

↑
中央
↓

へ移動させています。

横方向も中央

horizontalAlignment = Alignment.CenterHorizontally

これで

← 中央 →

になります。

画像表示

Image(
painter = painterResource(id = R.drawable.logo),
...
)

これが

res/drawable/logo.png

を表示しています。

SwiftUIなら

Image("logo")

とほぼ同じイメージです。

サイズ指定

Modifier.size(180.dp)

180×180dpで表示します。

Spacer

Spacer(
modifier = Modifier.height(32.dp)
)

これは

画像

(32dp空ける)

ボタン

という余白です。

SwiftUIなら

Spacer()

ではなく

.padding(.bottom, 32)

に近い役割です。

横並び

Row

Rowは

□□□□

と横に並べます。

SwiftUIなら

HStack

です。

ボタン間隔

Arrangement.spacedBy(16.dp)

で

[ボタン1]    [ボタン2]

のように16dp空きます。

上記コードに画面遷移を追加

完成イメージはこんな感じになります。

MainScreen
┌──────────────────┐
│ │
│ 画像 │
│ │
│ [ボタン1][ボタン2]│
└──────────────────┘
│
│ ボタン1
▼
SettingsScreen
┌──────────────────┐
│ │
│ [ON/OFFスイッチ] │
│ │
│ test1 │
│┌────────────────┐│
││入力してください││
│└────────────────┘│
└──────────────────┘

① Navigation Composeを追加

build.gradle.kts (Module: app) に追加します。

同期(Sync Now)してください。

② MainActivity

setContent {} を次のように変更します。

③ Navigationを作成

新しく AppNavigation.kt を作ります。

ここでは

  • "main" → 最初の画面
  • "settings" → 遷移先

を登録しています。

④ MainScreenを変更

引数に NavController を追加します。

ポイントはここです。

navController.navigate("settings")

この一行だけで画面遷移できます。

⑤ SettingsScreenを作る

⑥ 必要なimport

Android Studioがほぼ自動で追加してくれます。

このコードで初めて出てくるもの

remember

var checked by remember {
mutableStateOf(false)
}

これは

「値を覚えておいてください」

というComposeの仕組みです。

例えば

false
↓
true
↓
false

と変更されても画面が更新されます。

SwiftUIなら

@State private var checked = false

とほぼ同じ役割です。

mutableStateOf

mutableStateOf("")

画面更新の対象になる変数です。

普通の

var text = ""

では画面は更新されません。

Composeでは

var text by remember {
mutableStateOf("")
}

とすることで、値が変わるたびにUIが自動で再描画されます。

OutlinedTextField

OutlinedTextField()

これは枠付きのテキスト入力欄です。

表示は

test1
┌────────────────────┐
│入力してください │
└────────────────────┘

となります。

戻るボタンとテキストやスイッチの状態を保存するコードの追加

① DataStoreを追加

build.gradle.kts

Syncしてください。

② DataStoreManager.kt

新しく作成します。

これだけで保存と読み込みができます。

③ SettingsScreen

引数を変更します。

DataStoreを取得

Flowを監視

Composeでは

val checked by dataStore.checkedFlow.collectAsState(false)

val text by dataStore.textFlow.collectAsState("")

これだけで

保存値

↓

UI

が自動同期されます。

これがComposeの便利なところです。

④ TopAppBarを追加

画面全体をScaffoldで囲みます。

戻るボタンは

navController.popBackStack()

だけです。

⑤ Switch

保存すると

DataStore

↓

Flow

↓

画面更新

が全部自動です。

⑥ TextField

入力するたび保存されます。

⑦ Navigation修正

MainScreenは変更ありません。

もう少しComposeらしく書く方法

ここまでで十分動作しますが、Composeでは**CoroutineScope(Dispatchers.IO)を毎回作る書き方はあまり一般的ではありません**。

通常は

val scope = rememberCoroutineScope()

として、

scope.launch {
dataStore.saveChecked(it)
}

のように書くことが多いです。

上記のDataStoreManagerの解説

この DataStoreManager は最初は少し難しく感じますが、**「設定ファイルを読み書きするクラス」**だと思うと理解しやすいです。

まず全体像から見てみましょう。

SettingsScreen
│
│ saveChecked(true)
▼
DataStoreManager
│
▼
DataStore(settings.preferences_pb)
▲
│
checkedFlow
│
▼
SettingsScreen

つまり、

  • SettingsScreen が保存をお願いする
  • DataStoreManager がDataStoreへ保存
  • DataStoreが変更される
  • checkedFlow が変更を通知
  • 画面が自動更新

という流れです。

① DataStoreを作る

private val Context.dataStore by preferencesDataStore("settings")

これが一番分かりにくいですね(笑)

実は

Context.dataStore

という新しいプロパティを追加しています。

イメージすると

val context: Context

context.dataStore

が使えるようになります。

例えば

context.dataStore.edit { }

のように書けるようになります。

"settings"

preferencesDataStore("settings")

これは保存ファイル名です。

実際には内部で

settings.preferences_pb

というファイルになります。

SwiftUIなら

UserDefaults.standard

に近い存在です。

② クラス

class DataStoreManager(private val context: Context)

ここは

Contextを受け取ってDataStoreを操作するクラス

という意味です。

例えば

val manager = DataStoreManager(context)

とすると

manager

の中で

context.dataStore

が使えます。

③ companion object

companion object {

これは

クラス共通の定数置き場

です。

Javaなら

public static

Swiftなら

static let

に相当します。

④ キーを作る

val CHECKED = booleanPreferencesKey("checked")

これは

checked

という名前で保存するという意味です。

例えばDataStoreの中は

checked = true

になります。

もう一つは

val TEXT = stringPreferencesKey("text")

なので

text = "こんにちは"

のように保存されます。

つまり

DataStore

checked = true

text = Hello

という辞書(Dictionary)みたいなものになります。

⑤ checkedFlow

val checkedFlow =
context.dataStore.data.map {
it[CHECKED] ?: false
}

ここは一番重要です。

まず

context.dataStore.data

は

DataStoreの内容

を監視しています。

つまり

変更があれば教えてね

という状態です。

次の

map {

は

欲しい値だけ取り出す

という意味です。

例えばDataStoreが

checked = true

text = abc

だったら

checkedだけ欲しい

となります。

it 説明リンク

it[CHECKED]

の

it

は

DataStore全体

です。

つまり

it

├ checked
└ text

になります。

そこから

it[CHECKED]

で

checkedだけ取得

しています。

?: false

it[CHECKED] ?: false

これは

もし存在しなければfalse

という意味です。

保存したことが無い最初の起動では

checked

はありません。

なので

false

を返します。

⑥ textFlow

こちらも同じです。

val textFlow =
context.dataStore.data.map {
it[TEXT] ?: ""
}

最初だけ

空文字

になります。

⑦ suspend

suspend fun saveChecked(...)

これは

時間の掛かる処理

という意味です。

保存処理は

  • ファイルを開く
  • 書き込む
  • 閉じる

という作業なので、

普通の関数ではなく

suspend

になります。

Swiftなら

async

とかなり似ています。

⑧ edit

context.dataStore.edit {

これは

設定を書き換えます

という意味です。

中のit

it[CHECKED] = value

この

it

は

Preferences

です。

つまり

設定一覧

になります。

例えば

変更前

checked = false

text = Hello

↓

変更後

checked = true

text = Hello

になります。

⑨ saveText

it[TEXT] = value

こちらも同じです。

例えば

value = "Android"

なら

checked = true

text = Android

になります。

全体の流れ

例えばスイッチをONにすると

Switch
│
▼
saveChecked(true)
│
▼
DataStore.edit
│
▼
checked = true
│
▼
checkedFlowが変更を検知
│
▼
Composeが再描画
│
▼
SwitchがONになる

MVVMの概念

まず、現在の問題点

今の SettingsScreen はこんな仕事をしています。

SettingsScreen
│
├ UIを表示
├ DataStoreを作る
├ 保存する
├ 読み込む
└ 状態を管理する

つまり…

仕事が多すぎます。

本来UIは

「表示するだけ」

にしたいです。

MVVMとは?

MVVMは役割を分けます。

          ユーザー
│
▼
SettingsScreen
(View)
│
▼
SettingsViewModel
(状態管理)
│
▼
DataStoreManager
(データ保存)

役割はこのようになります。

View(画面)

・表示するだけ
・ボタンが押されたことを知らせる

ViewModel

・画面の状態を持つ
・保存する
・読み込む
・UIへ通知する

Model

今回は

DataStoreManager

です。

保存専門です。

ディレクトリ構成

私は最初からこの構成にすることをおすすめします。

com.example.test

MainActivity.kt

navigation/
AppNavigation.kt

ui/
MainScreen.kt
SettingsScreen.kt

viewmodel/
SettingsViewModel.kt

data/
DataStoreManager.kt

SwiftUIでもフォルダを分けていましたよね。

それとほぼ同じです。

ViewModelを作る

まず

viewmodel/

フォルダを作ります。

その中に

SettingsViewModel.kt

このコードの仕事

例えば

fun setChecked(value: Boolean)

は

画面から

ONになりました!

と言われたら

DataStoreへ保存

するだけです。

つまり

View

↓

ViewModel

↓

DataStore

です。

viewModelScope

これもComposeで超重要です。

以前は

CoroutineScope(Dispatchers.IO).launch {

}

と書いていました。

でもViewModelでは

viewModelScope.launch {

}

だけです。

理由は

ViewModelが破棄されると

Coroutineも自動終了

するからです。

メモリリークを防げます。

stateIn

これもよく出てきます。

DataStoreは

Flow

でした。

でもUIは

State

が欲しいです。

なので

Flow

↓

StateFlow

へ変換しています。

これが

stateIn(...)

です。

SettingsScreen

すると画面はかなりシンプルになります。

画面は

表示だけ

になりました。

保存のことを知りません。

処理の流れ

例えば

スイッチをONにすると

Switch
│
▼
viewModel.setChecked(true)
│
▼
DataStore.saveChecked(true)
│
▼
DataStore更新
│
▼
checkedFlow更新
│
▼
checked StateFlow更新
│
▼
画面更新

すごくきれいですね。

Repositoryの概念

Repositoryとは?

一言でいうと

「データの窓口」

です。

現在の構成はこうでした。

SettingsScreen
│
▼
SettingsViewModel
│
▼
DataStoreManager

ViewModelが直接DataStoreを触っていますね。

これの問題

例えば将来、

設定を

  • DataStore
  • Room
  • Web API

の3か所から取得したいとします。

するとViewModelは

SettingsViewModel

├ DataStore
├ Room
└ API

全部知ることになります。

これではViewModelの仕事が増えすぎます。

Repositoryを入れる

そこでRepositoryを1枚挟みます。

SettingsScreen
│
▼
SettingsViewModel
│
▼
SettingsRepository
│
┌──────┴─────────┐
▼ ▼
DataStore Room
│
▼
API

ViewModelは

Repositoryだけ知っていればOK

になります。

Repositoryの役割

Repositoryは

データはどこから来たか

を隠す

役目です。

例えば

viewModel.setChecked(true)

↓

repository.saveChecked(true)

↓

Repositoryが

DataStoreへ保存

するのか

Roomへ保存

するのか

APIへ送信

するのか

ViewModelは知りません。

フォルダ構成

この辺からAndroidらしくなります。

data/

DataStoreManager.kt

repository/

SettingsRepository.kt

viewmodel/

SettingsViewModel.kt

ui/

SettingsScreen.kt

Repositoryを書いてみる

まず

repository/

を作ります。

中に

SettingsRepository.kt
class SettingsRepository(

private val dataStore: DataStoreManager

) {

val checkedFlow = dataStore.checkedFlow

val textFlow = dataStore.textFlow

suspend fun saveChecked(value: Boolean) {

dataStore.saveChecked(value)

}

suspend fun saveText(value: String) {

dataStore.saveText(value)

}

}

え?

これだけ?

と思いますよね(笑)

最初はこれだけです。

ViewModel変更

今までは

private val dataStore: DataStoreManager

でした。

これを

private val repository: SettingsRepository

へ変更します。

読み込みも

repository.checkedFlow

になります。

保存も

repository.saveChecked(value)

になります。

DataStoreを交換してもViewModelは変わらない

例えば将来

DataStoreではなく

Roomへ変更したい。

Repositoryだけ

suspend fun saveChecked(value: Boolean){

roomDao.save(value)

}

へ変更します。

ViewModelは

viewModel.setChecked(true)

のままです。

UIも変更なし。

Repositoryが一番活躍する例

例えば

プロフィール画面。

名前

メール

年齢

を表示します。

データは

Room

にあります。

でも

ネットに新しいデータがあるか確認

↓

あれば更新

↓

Roomへ保存

↓

Roomから読む

という流れにしたい。

Repositoryなら

Repository

↓

APIから取得

↓

Roomへ保存

↓

Roomから取得

↓

ViewModelへ返す

と全部まとめられます。

ViewModelは

repository.loadProfile()

しか知りません。

SwiftUIとの比較

SwiftUIでも

View

↓

ViewModel

↓

Repository

↓

API

という構成を採用しているプロジェクトは多いです。

なので考え方はほぼ同じです。

「これだけならRepositoryいらないのでは?」

その通りです。

今回のサンプルでは

DataStoreManager

だけでも十分です。

しかし実際のアプリでは、

  • 設定をクラウドと同期する
  • ローカルキャッシュを持つ
  • API通信に失敗したらローカルデータを返す
  • 複数のデータソースを組み合わせる

といったことが増えてきます。

そのときRepositoryがあると、ViewModelは「データの取得・保存をRepositoryに依頼する」だけで済みます。

Hilt(依存性注入)

まず「依存性注入(DI)」って何?

名前が難しいですが、実はやっていることはシンプルです。

今までのコードでは、自分で必要なものを作っていました。

val dataStore = DataStoreManager(context)

val repository = SettingsRepository(dataStore)

val viewModel = SettingsViewModel(repository)

図にすると

自分で作る

DataStoreManager
│
▼
SettingsRepository
│
▼
SettingsViewModel

これを**依存性注入(Dependency Injection:DI)**と呼ばれる仕組みで自動化します。

Hiltを使うと…

自分では何も作りません。

val viewModel: SettingsViewModel = hiltViewModel()

これだけ。

Hiltが裏で

DataStoreManager

↓

SettingsRepository

↓

SettingsViewModel

全部作ってくれます。

なぜ便利なの?

例えばRepositoryが大きくなると…

class SettingsRepository(

private val dataStore: DataStoreManager,

private val api: ApiService,

private val room: SettingsDao,

private val logger: Logger,

private val analytics: Analytics

)

ViewModelを作るだけでも

SettingsViewModel(

SettingsRepository(

DataStoreManager(context),

ApiService(),

Room.databaseBuilder(...),

Logger(),

Analytics()

)

)

になってしまいます。

これ、大変ですよね(笑)

Hiltなら

ViewModelください

↓

はい!

↓

ViewModel

↓

Repository

↓

DataStore

↓

API

↓

Room

全部勝手に準備してくれます。

Hiltの仕組み

Hiltは

「どうやって作るか」

だけ教えてあげます。

① Application

まず

@HiltAndroidApp
class MyApplication : Application()

を作ります。

これは

このアプリはHiltを使います!

という宣言です。

AndroidManifest.xml

<application
android:name=".MyApplication"

も追加します。

② build.gradle

依存関係を追加します。

implementation("com.google.dagger:hilt-android:2.xx")
ksp("com.google.dagger:hilt-compiler:2.xx")

ComposeではKSP版を使うことが多いです。

③ DataStoreManager

ここが面白いです。

今までは

class DataStoreManager(

private val context: Context

)

でした。

Hiltでは

class DataStoreManager @Inject constructor(

@ApplicationContext
private val context: Context

)

になります。

たった

@Inject

が付いただけ。

意味は

「Hilt、このクラスは作っていいですよ。」

です。

④ Repository

こちらも

class SettingsRepository @Inject constructor(

private val dataStore: DataStoreManager

)

になります。

するとHiltは

Repository作るには

↓

DataStoreが必要

↓

DataStoreも作れる

↓

OK

となります。

⑤ ViewModel

ここも

@HiltViewModel
class SettingsViewModel @Inject constructor(

private val repository: SettingsRepository

) : ViewModel()

になります。

ここまで来ると

ViewModel

↓

Repository

↓

DataStore

全部自動生成できます。

⑥ Activity

Activityには

@AndroidEntryPoint
class MainActivity : ComponentActivity()

を付けます。

これで

val viewModel: SettingsViewModel by viewModels()

が使えます。

Composeなら

val viewModel: SettingsViewModel = hiltViewModel()

です。

全体の流れ

今までは

MainActivity

↓

new ViewModel

↓

new Repository

↓

new DataStore

でした。

Hiltでは

MainActivity

↓

ViewModelください

↓

Hilt

↓

DataStore作成

↓

Repository作成

↓

ViewModel作成

↓

返却

になります。

SwiftUIとの比較

SwiftUIにも似た考え方があります。

例えば

@EnvironmentObject
var settings: SettingsViewModel

これも

必要なものを自分で作らず、「注入」してもらう

という点では、Hiltと近い発想です。

ただし、HiltはViewModelだけでなく、RepositoryやDataStoreなどの依存関係までまとめて管理できるのが大きな違いです。

「@」がたくさん出てきた…

Android初心者の多くがここで戸惑います(笑)

Hiltでは

@HiltAndroidApp
@AndroidEntryPoint
@Inject
@HiltViewModel
@ApplicationContext

とアノテーション(@が付くもの)が増えます。

最初は「おまじない」のように見えますが、それぞれ役割があります。

アノテーション役割
@HiltAndroidAppアプリ全体でHiltを有効にする
@AndroidEntryPointこのActivityやFragmentでHiltを使う
@InjectこのクラスやコンストラクタはHiltが生成できる
@HiltViewModelこのViewModelはHiltが管理する
@ApplicationContextアプリ全体のContextを注入する

Room

まずSQLiteとは?

Androidには昔から SQLite というデータベースが入っています。

例えばメッセージアプリなら、

Message

id | user | text
--------------------------
1 | A | こんにちは
2 | B | おはよう
3 | A | 元気?

このような表(テーブル)としてデータを保存できます。

昔はSQLiteを直接操作していました。

db.execSQL(...)
db.rawQuery(...)

しかし、

  • SQLを書く必要がある
  • 型安全ではない
  • コードが長くなる

という欠点がありました。

Roomとは?

Roomは一言でいうと

SQLiteを簡単・安全に扱うためのライブラリ

です。

つまり

Compose
│
ViewModel
│
Repository
│
Room
│
SQLite

RoomがSQLiteを包んでくれています。

SwiftUIでいうと

Core Data や SwiftData に近い存在だと思うとイメージしやすいです。

DataStoreとの違い

ここが重要です。

DataStoreRoom
設定保存データ保存
bool○
String○
一覧データ✕
検索✕
並び替え✕
テーブル✕
SQL✕
メッセージ保存△
ゲームセーブ◎

例えば

ダークモード ON

ならDataStore。

でも

メッセージ1000件

ならRoomです。

Roomの基本は3つ

Roomは次の3つだけ覚えれば始められます。

Entity

↓

DAO

↓

Database

① Entity

Entityは

テーブル定義

です。

例えば

@Entity
data class Message(

@PrimaryKey(autoGenerate = true)
val id: Int = 0,

val user: String,

val text: String

)

これだけで

SQLiteには

Message

id

user

text

というテーブルが作られます。

Swiftなら

struct Message

に近い存在です。

② DAO

DAOは

データの操作方法

です。

例えば

@Dao
interface MessageDao {

@Insert
suspend fun insert(
message: Message
)

@Query("SELECT * FROM Message")
fun getAll(): Flow<List<Message>>

}

これだけで

dao.insert(...)

や

dao.getAll()

が使えます。

SQLは

SELECT * FROM Message

だけ書けばOKです。

③ Database

最後に

@Database(

entities = [Message::class],

version = 1

)

abstract class AppDatabase : RoomDatabase() {

abstract fun messageDao(): MessageDao

}

を書きます。

これで

AppDatabase

↓

MessageDao

↓

Message

がつながります。

Repository

ここからがMVVMらしいところです。

Repositoryは

class MessageRepository(

private val dao: MessageDao

)

となります。

保存は

suspend fun addMessage(

message: Message

){

dao.insert(message)

}

読み込みは

val messages = dao.getAll()

だけです。

ViewModel

ViewModelは

class MessageViewModel(

repository: MessageRepository

)

になります。

val messages =
repository.messages

画面は

viewModel.messages

だけ見ればOK。

Compose

画面では

val messages by
viewModel.messages.collectAsState()

として、

LazyColumn {

items(messages){

Text(it.text)

}

}

これだけで

こんにちは

おはよう

元気?

が表示されます。

全体の流れ

メッセージを送信すると

Button

↓

ViewModel

↓

Repository

↓

DAO

↓

Room

↓

SQLite

になります。

逆に読み込むと

SQLite

↓

Room

↓

DAO

↓

Repository

↓

ViewModel

↓

Compose

になります。

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