{"id":511,"date":"2024-07-30T15:02:28","date_gmt":"2024-07-30T06:02:28","guid":{"rendered":"https:\/\/txn.myds.me\/blog\/?p=511"},"modified":"2024-07-30T15:02:28","modified_gmt":"2024-07-30T06:02:28","slug":"android-kotlin-%e3%81%ae-viewmodel-%e3%81%a7-model-%e3%81%ae%e5%a4%89%e6%9b%b4%e3%82%92%e5%8f%97%e3%81%91%e5%8f%96%e3%82%8b-1","status":"publish","type":"post","link":"https:\/\/blog.txn.red\/?p=511","title":{"rendered":"Android Kotlin \u306e ViewModel \u3067 Model \u306e\u5909\u66f4\u3092\u53d7\u3051\u53d6\u308b -1"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">\u4eca\u56de\u306f\u73cd\u3057\u304f\u9577\u3044\u3067\u3059\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u5148\u65e5\u304b\u3089 Android \u306e\u30bf\u30b9\u30af\u306b\u307e\u305f\u53d6\u308a\u7d44\u3093\u3067\u3044\u307e\u3059\u3002\u30ea\u30cf\u30d3\u30ea\u3092\u304b\u306d\u3066 Jetpack compose \u3092\u52c9\u5f37\u3057\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u81ea\u5206\u306e\u7406\u89e3\u3067\u306f\u3001@composable \u306a\u95a2\u6570\u306f\u5916\u304b\u3089\u72b6\u614b\u3092\u5909\u66f4\u3067\u304d\u306a\u3044\u306e\u3067\u3001\u4f55\u3089\u304b\u306e\u65b9\u6cd5\u3067\u5916\u90e8\u306b\u72b6\u614b\u3092\u7dad\u6301\u3057\u306a\u3051\u308c\u3070\u306a\u308a\u307e\u305b\u3093\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u305d\u3046\u306a\u308b\u3068 MVVM \u3063\u3066\u8a71\u306b\u306a\u308b\u3088\u3046\u306a\u306e\u3067\u3059\u304c\u3001Model \u3068 ViewModel \u306e\u9023\u643a\u306b\u3064\u3044\u3066\u306f\u3042\u307e\u308a\u3044\u3044\u30b5\u30f3\u30d7\u30eb\u304c\u898b\u3064\u3051\u3089\u308c\u306a\u304b\u3063\u305f\u3002\u30cd\u30c3\u30c8\u306b\u8ee2\u304c\u3063\u3066\u3044\u308b\u30b5\u30f3\u30d7\u30eb\u3067\u306f UI \u64cd\u4f5c\u3067 ViewModel \u306e\u5909\u66f4\u304c\u5b8c\u7d50\u3057\u3066\u3044\u308b\u3082\u306e\u304c\u307b\u3068\u3093\u3069\u3067\u3059\u3002\u3053\u306e\u30d1\u30bf\u30fc\u30f3\u3067\u306f Model \u304c View \u3092\u307e\u305f\u3050\u5834\u5408\u306e\u3053\u3068\u306f\u89e6\u308c\u3066\u3044\u306a\u3044\u306e\u3067\u81ea\u5206\u3067\u8003\u3048\u306a\u3051\u308c\u3070\u3044\u3051\u307e\u305b\u3093\u3002r<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u7d50\u5c40\u306e\u3068\u3053\u308d\u3001\u30e2\u30c7\u30eb\u3092\u30d3\u30e5\u30fc(Activity) \u306e\u5916\u306b\u7f6e\u304f\u306e\u304c\u4e00\u756a\u7c21\u5358\u306a\u89e3\u6c7a\u7b56\u3058\u3083\u306a\u3044\u304b\u3068\u601d\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u53c2\u8003\u30ea\u30f3\u30af\u304c\u552f\u4e00\u3001Model \u304b\u3089 ViewModel \u306b\u66f4\u65b0\u3092\u901a\u77e5\u3059\u308b\u4f8b\u3092\u898b\u3064\u3051\u3089\u308c\u305f\u306e\u3067\u305d\u308c\u3092\u305d\u306e\u307e\u307e\u5199\u7d4c\u3057\u3066\u307f\u305f\u3002<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%25e7%259b%25ae%25e6%25ac%25a1\">\u76ee\u6b21<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/blog.txn.red\/?p=511\">\u305d\u306e 1 &#8211; \u57fa\u672c\u306e\u30d3\u30e5\u30fc\u30e2\u30c7\u30eb <\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/LiveData\" data-type=\"URL\" data-id=\"LiveData\">\u305d\u306e 2 &#8211; \u30e2\u30c7\u30eb\u306e\u5909\u66f4\u3092 LiveData \u3067\u53d7\u3051\u53d6\u308b<\/a><\/li>\n\n\n\n<li><a href=\"http:\/\/StateFlow\">\u305d\u306e 3 &#8211; \u30e2\u30c7\u30eb\u306e\u5909\u66f4\u3092 StateFlow \u3067\u53d7\u3051\u53d6\u308b<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%25e5%2589%258d%25e6%258f%2590\">\u524d\u63d0<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Jetpack Compose<\/li>\n\n\n\n<li>Kotlin<\/li>\n\n\n\n<li>Android Studio <\/li>\n\n\n\n<li>Android Studio Koala | 2024.1.1 Patch 1<\/li>\n\n\n\n<li>Build #AI-241.18034.62.2411.12071903, built on July 11, 2024<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"basic\">\u57fa\u672c\u306e\u30d3\u30e5\u30fc\u30e2\u30c7\u30eb<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">\u307e\u305a\u306f\u3001compose \u3092\u4f7f\u3063\u3066 ViewModel \u306e LiveData \u3092 UI \u306b\u901a\u77e5\u3059\u308b\u30d1\u30bf\u30fc\u30f3\u3092\u3064\u304f\u308b\u3002 \u3064\u3044\u3067\u306b ViewModel \u3092 Model \u306e\u4e00\u90e8\u3068\u307f\u306a\u3057\u3066 Activity \u306e\u5916\u3067\u5ba3\u8a00\u3059\u308b\u4f8b\u3002ViewModel \u306f\u5225\u30d5\u30a1\u30a4\u30eb\u306b\u5206\u3051\u308b\u3079\u304d\u3067\u3059\u304c\u9762\u5012\u306a\u306e\u3067\u305d\u306e\u307e\u307e\u3067\u3059\u3002<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-Kotlin\" data-file=\"Activity.kt\" data-lang=\"Kotlin\"><code>package xxx.yyy.logpanecompose\n\nimport android.os.Bundle\nimport androidx.activity.ComponentActivity\nimport androidx.activity.compose.setContent\nimport androidx.activity.enableEdgeToEdge\nimport androidx.compose.foundation.layout.Column\nimport androidx.compose.foundation.layout.fillMaxSize\nimport androidx.compose.foundation.layout.padding\nimport androidx.compose.material3.Button\nimport androidx.compose.material3.Scaffold\nimport androidx.compose.material3.Text\nimport androidx.compose.material3.TextField\nimport androidx.compose.runtime.Composable\nimport androidx.compose.runtime.getValue\nimport androidx.compose.runtime.livedata.observeAsState\nimport androidx.compose.ui.Modifier\nimport androidx.compose.ui.tooling.preview.Preview\nimport androidx.compose.ui.unit.dp\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.viewmodel.compose.viewModel\nimport red.txn.logpanecompose.ui.theme.LogPaneComposeTheme\n\nclass MainActivity : ComponentActivity() {\n\n    val vm = MyApp.getInstance().getViewModel()\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        enableEdgeToEdge()\n\n\/\/        val vm = LogViewModel()\n\n        setContent {\n            LogPaneComposeTheme {\n                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -&gt;\n                    LogPane(vm=vm, modifier = Modifier.padding(innerPadding))\n\/\/                    LogPane(modifier=Modifier.padding(innerPadding))\n                }\n            }\n        }\n    }\n}\n\nclass LogViewModel(): ViewModel() {\n    val buffer = MutableLiveData(&quot;&quot;)\n\n    private var count: Int = 0\n\n    fun append(newLine: String) {\n        buffer.value = buffer.value + newLine + count.toString() + &quot;\\n&quot;\n        count++\n    }\n}\n\n@Composable\nfun LogPane(\n    modifier: Modifier=Modifier,\n    vm: LogViewModel = viewModel()\n) {\n    val text: String by vm.buffer.observeAsState(&quot;&quot;)\n    Column(modifier=modifier.padding(horizontal=16.dp)) {\n        Button(\n            onClick={vm.append(&quot;aZZZ&quot;)},\n            modifier=modifier.padding(top=0.dp, bottom=4.dp )\n        )\n        {\n            Text(text=&quot;add log&quot;)\n        }\n        TextField(\n            value=text,\n            onValueChange = { },\n            singleLine = false,\n            modifier = modifier.fillMaxSize()\n        )\n    }\n}\n\n@Preview(showBackground = true)\n@Composable\nfun LogPanePreview() {\n    LogPaneComposeTheme {\n        LogPane()\n    }\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">MainActivity \u306e\u5148\u982d\u3067\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u304b\u3089 ViewModel  \u3092\u6301\u3063\u3066\u304d\u3066\u3044\u307e\u3059\u3002<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">\u72ec\u81ea\u306e\u30a2\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u30af\u30e9\u30b9\u3092\u66f8\u304d\u307e\u3059<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-Kotlin\" data-file=\"MyApp.kt\" data-lang=\"Kotlin\"><code>package xxx.yyy.logpanecompose\n\nimport android.app.Application\n\nclass MyApp: Application() {\n    public val vm = LogViewModel()  \/\/ private \u306b\u3059\u308b\u3079\u304d\u304b\u3082\n\n    companion object {\n        private var instance: MyApp? = null\n\n        fun getInstance(): MyApp {\n            if (instance == null) {\n                instance = MyApp()\n            }\n            return instance!!\n        }\n    }\n\n    public fun getViewModel() = vm\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\">MyApp \u3092\u4f7f\u3046\u3088\u3046\u306b AndroidManifest.xml \u3092\u5909\u66f4\u3057\u307e\u3059\u3002<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-xml\" data-lang=\"XML\" data-line=\"6\"><code>&lt;?xml version=&quot;1.0&quot; encoding=&quot;utf-8&quot;?&gt;\n&lt;manifest xmlns:android=&quot;http:\/\/schemas.android.com\/apk\/res\/android&quot;\n    xmlns:tools=&quot;http:\/\/schemas.android.com\/tools&quot;&gt;\n\n    &lt;application\n        android:name=&quot;.MyApp&quot;\n        android:allowBackup=&quot;true&quot;\n        android:dataExtractionRules=&quot;@xml\/data_extraction_rules&quot;\n        android:fullBackupContent=&quot;@xml\/backup_rules&quot;\n        android:icon=&quot;@mipmap\/ic_launcher&quot;\n        android:label=&quot;@string\/app_name&quot;\n        android:roundIcon=&quot;@mipmap\/ic_launcher_round&quot;\n        android:supportsRtl=&quot;true&quot;\n        android:theme=&quot;@style\/Theme.LogPaneCompose&quot;\n        tools:targetApi=&quot;31&quot;&gt;\n        &lt;activity\n            android:name=&quot;.MainActivity&quot;\n            android:exported=&quot;true&quot;\n            android:label=&quot;@string\/app_name&quot;\n            android:theme=&quot;@style\/Theme.LogPaneCompose&quot;&gt;\n            &lt;intent-filter&gt;\n                &lt;action android:name=&quot;android.intent.action.MAIN&quot; \/&gt;\n\n                &lt;category android:name=&quot;android.intent.category.LAUNCHER&quot; \/&gt;\n            &lt;\/intent-filter&gt;\n        &lt;\/activity&gt;\n    &lt;\/application&gt;\n\n&lt;\/manifest&gt;<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"LiveData\">LiveData \u3067\u30e2\u30c7\u30eb\u306e\u5909\u66f4\u3092\u53d7\u3051\u53d6\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Model \u306b LiveData \u3092\u8ffd\u52a0\u3057\u3066 ViewModel \u3067\u76e3\u8996\u3059\u308b\u306b\u306f\u4e0b\u306e\u3088\u3046\u306b Model \u3092\u8ffd\u52a0\u3057\u3001 ViewModel \u3092\u5909\u66f4\u3059\u308b\u3002<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-Kotlin\" data-file=\"MainActivity.kt\" data-lang=\"Kotlin\" data-line=\"\uff11\uff11-\uff12\uff12, \uff12\uff14-\uff14\uff13\"><code>\/\/ \u7701\u7565\n\ndata class LogModel(val message: String) {\n    val buffer = MutableLiveData(message)\n\n    fun append(newMessage: String) {\n        buffer.value += newMessage + &quot;\\n&quot;\n    }\n}\n\nclass LogViewModel(val model: LogModel): ViewModel() {\n    var buffer = MutableLiveData(&quot;&quot;)\n\n    private val lifecycleOwner = CustomLifecycleOwner()\n\n    init {\n        lifecycleOwner.start()\n        model.buffer.observe(lifecycleOwner) {\n            println(&quot;viewmodel: $it&quot;)\n            buffer.value = it\n        }\n    }\n\n    fun append(newMessage: String) {\n        model.append(newMessage)\n    }\n\n    inner class CustomLifecycleOwner: LifecycleOwner {\n        private val registry = LifecycleRegistry(this)\n\n        override val lifecycle: Lifecycle\n            get() = registry\n\n        fun start() {\n            registry.currentState = Lifecycle.State.STARTED\n        }\n\n        @Suppress(&quot;unused&quot;)\n        fun stop() {\n            registry.currentState = Lifecycle.State.CREATED\n        }\n    }\n}\n<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">MyApp \u3082\u30e2\u30c7\u30eb\u3092\u5ba3\u8a00\u3059\u308b\u3088\u3046\u306b\u5909\u66f4\u3059\u308b<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-plain\" data-line=\"6-7\"><code>package xxx.yyy.logpanecompose\n\nimport android.app.Application\n\nclass MyApp: Application() {\n    val model = LogModel(&quot;&quot;)\n    val vm = LogViewModel(model=model)\n\n    companion object {\n        private var instance: MyApp? = null\n\n        fun getInstance(): MyApp {\n            if (instance == null) {\n                instance = MyApp()\n            }\n            return instance!!\n        }\n    }\n\n    fun getViewModel() = vm\n}<\/code><\/pre><\/div>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"StateFlow\">StateFlow \u3067\u30e2\u30c7\u30eb\u306e\u5909\u66f4\u3092\u53d7\u3051\u53d6\u308b<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Model\u3001ViewModel \u5c64\u3067 LiveData \u3092\u4f7f\u3046\u306e\u306f\u3042\u307e\u308a\u3088\u304f\u306a\u3044\u3089\u3057\u3044\u3068\u306e\u3053\u3068\u3067\u3001StateFlow \u30d1\u30bf\u30fc\u30f3\u3082\u3084\u3063\u3066\u307f\u308b\u3002<\/p>\n\n\n\n<div class=\"hcb_wrap\"><pre class=\"prism line-numbers lang-Kotlin\" data-file=\"MainActivity.kt\" data-lang=\"Kotlin\" data-line=\"2-10,12-27\"><code>\/\/ \u3044\u308d\u3044\u308d\u7701\u7565\n\ndata class LogModel(val message: String) {\n\/\/    val buffer = MutableLiveData(message)\n    val buffer = MutableStateFlow(message)\n\n    fun append(newMessage: String) {\n        buffer.value += newMessage + &quot;\\n&quot;\n    }\n}\n\nclass LogViewModel(val model: LogModel): ViewModel() {\n    var buffer = MutableLiveData(&quot;&quot;)\n\n    init {\n        viewModelScope.launch(Dispatchers.IO) {\n            model.buffer.collect {\n                println(&quot;viewmodel: $it&quot;)\n                buffer.postValue(it)\n            }\n        }\n    }\n\n    fun append(newMessage: String) {\n        model.append(newMessage)\n    }\n}<\/code><\/pre><\/div>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"%25e5%258f%2582%25e8%2580%2583\">\u53c2\u8003<\/h2>\n\n\n\n<pre class=\"wp-block-preformatted\"><a href=\"https:\/\/qiita.com\/nashitake\/items\/8651a009a894360b1af6\">\u50d5\u305f\u3061\u306f\u3069\u306e\u3088\u3046\u306b\u3057\u3066ViewModel\u306b\u901a\u77e5\u3059\u3079\u304d\u306a\u306e\u304b #Android - Qiita<\/a><\/pre>\n","protected":false},"excerpt":{"rendered":"<p>\u4eca\u56de\u306f\u73cd\u3057\u304f\u9577\u3044\u3067\u3059\u3002 \u5148\u65e5\u304b\u3089 Android \u306e\u30bf\u30b9\u30af\u306b\u307e\u305f\u53d6\u308a\u7d44\u3093\u3067\u3044\u307e\u3059\u3002\u30ea\u30cf\u30d3\u30ea\u3092\u304b\u306d\u3066 Jetpack compose \u3092\u52c9\u5f37\u3057\u3066\u3044\u307e\u3059\u3002 \u81ea\u5206\u306e\u7406\u89e3\u3067\u306f\u3001@composable \u306a\u95a2\u6570\u306f\u5916\u304b\u3089\u72b6\u614b\u3092\u5909\u66f4\u3067\u304d [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"iawp_total_views":10,"footnotes":""},"categories":[1],"tags":[],"class_list":["post-511","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/blog.txn.red\/index.php?rest_route=\/wp\/v2\/posts\/511","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.txn.red\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.txn.red\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.txn.red\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.txn.red\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=511"}],"version-history":[{"count":0,"href":"https:\/\/blog.txn.red\/index.php?rest_route=\/wp\/v2\/posts\/511\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.txn.red\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=511"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.txn.red\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=511"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.txn.red\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=511"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}