การส่งอาร์กิวเมนต์ไปยัง ViewModel (Hilt)

สูตรนี้แสดงวิธีส่งอาร์กิวเมนต์การนำทาง (คีย์) ไปยัง ViewModel โดยใช้ Hilt สำหรับการแทรกทรัพยากร Dependency

วิธีการทำงาน

ตัวอย่างนี้ใช้ฟีเจอร์การแทรกความช่วยเหลือของ Dagger/Hilt

  1. ViewModel มีคำอธิบายประกอบด้วย @HiltViewModel และตัวสร้างใช้ @AssistedInject เพื่อรับคีย์การนำทาง (ซึ่งมีคำอธิบายประกอบด้วย @Assisted)
  2. มีการกำหนดอินเทอร์เฟซ @AssistedFactory เพื่อสร้าง ViewModel
  3. ใช้ฟังก์ชันที่ประกอบกันได้ hiltViewModel เพื่อรับอินสแตนซ์ ViewModel เราจะมอบ creationCallback เพื่อส่งคีย์การนำทางไปยังโรงงาน ซึ่งจะทำให้ ViewModel ใช้คีย์ดังกล่าวได้

หมายเหตุ: ระบบจะเพิ่ม rememberViewModelStoreNavEntryDecorator ลงในentryDecoratorsของNavDisplay ซึ่งจะช่วยให้มั่นใจได้ว่า ViewModel จะอยู่ในขอบเขตที่ถูกต้องสำหรับ NavEntry ที่เกี่ยวข้อง เพื่อให้ระบบสร้างอินสแตนซ์ ViewModel ใหม่สำหรับคีย์การนำทางที่ไม่ซ้ำกันแต่ละรายการ

/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.nav3recipes.passingarguments.viewmodels.hilt

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.passingarguments.viewmodels.basic.RouteB
import com.example.nav3recipes.ui.setEdgeToEdgeConfig
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel

data object RouteA
data class RouteB(val id: String)

@AndroidEntryPoint
class HiltViewModelsActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)
        setContent {
            val backStack = remember { mutableStateListOf<Any>(RouteA) }

            NavDisplay(
                backStack = backStack,
                onBack = { backStack.removeLastOrNull() },

                // In order to add the `ViewModelStoreNavEntryDecorator` (see comment below for why)
                // we also need to add the default `NavEntryDecorator`s as well. These provide
                // extra information to the entry's content to enable it to display correctly
                // and save its state.
                entryDecorators = listOf(
                    rememberSaveableStateHolderNavEntryDecorator(),
                    rememberViewModelStoreNavEntryDecorator()
                ),
                entryProvider = entryProvider {
                    entry<RouteA> {
                        ContentGreen("Welcome to Nav3") {
                            LazyColumn {
                                items(10) { i ->
                                    Button(onClick = dropUnlessResumed {
                                        backStack.add(RouteB("$i"))
                                    }) {
                                        Text("$i")
                                    }
                                }
                            }
                        }
                    }
                    entry<RouteB> { key ->
                        val viewModel = hiltViewModel<RouteBViewModel, RouteBViewModel.Factory>(
                            // Note: We need a new ViewModel for every new RouteB instance. Usually
                            // we would need to supply a `key` String that is unique to the
                            // instance, however, the ViewModelStoreNavEntryDecorator (supplied
                            // above) does this for us, using `NavEntry.contentKey` to uniquely
                            // identify the viewModel.
                            //
                            // tl;dr: Make sure you use rememberViewModelStoreNavEntryDecorator()
                            // if you want a new ViewModel for each new navigation key instance.
                            creationCallback = { factory ->
                                factory.create(key)
                            }
                        )
                        ScreenB(viewModel = viewModel)
                    }
                }
            )
        }
    }
}

@Composable
fun ScreenB(viewModel: RouteBViewModel) {
    ContentBlue("Route id: ${viewModel.navKey.id} ")
}

@HiltViewModel(assistedFactory = RouteBViewModel.Factory::class)
class RouteBViewModel @AssistedInject constructor(
    @Assisted val navKey: RouteB
) : ViewModel() {

    @AssistedFactory
    interface Factory {
        fun create(navKey: RouteB): RouteBViewModel
    }
}

การส่งอาร์กิวเมนต์ไปยัง ViewModel (พื้นฐาน)

สูตรนี้แสดงวิธีส่งอาร์กิวเมนต์การนำทาง (คีย์) ไปยัง ViewModel โดยใช้ ViewModelProvider.Factory ที่กำหนดเอง

วิธีการทำงาน

  1. สร้าง ViewModelProvider.Factory ที่กำหนดเองซึ่งใช้คีย์การนำทางเป็นพารามิเตอร์ของตัวสร้าง
  2. ภายใน Composable entry จะใช้ viewModel(factory = ...) เพื่อสร้างอินสแตนซ์ ViewModel โดยส่งคีย์การนำทางปัจจุบันไปยัง Factory ซึ่งจะทำให้ViewModelใช้คีย์การนำทางได้

หมายเหตุ: ระบบจะเพิ่ม rememberViewModelStoreNavEntryDecorator ลงในentryDecoratorsของNavDisplay ซึ่งจะช่วยให้มั่นใจได้ว่า ViewModel จะอยู่ในขอบเขตที่ถูกต้องสำหรับ NavEntry ที่เกี่ยวข้อง เพื่อให้ระบบสร้างอินสแตนซ์ ViewModel ใหม่สำหรับคีย์การนำทางที่ไม่ซ้ำกันแต่ละรายการ

/*
 * Copyright 2025 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.nav3recipes.passingarguments.viewmodels.basic

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.ui.setEdgeToEdgeConfig

data object RouteA

data class RouteB(val id: String)

class BasicViewModelsActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)
        setContent {
            val backStack = remember { mutableStateListOf<Any>(RouteA) }

            NavDisplay(
                backStack = backStack,
                onBack = { backStack.removeLastOrNull() },
                // In order to add the `ViewModelStoreNavEntryDecorator` (see comment below for why)
                // we also need to add the default `NavEntryDecorator`s as well. These provide
                // extra information to the entry's content to enable it to display correctly
                // and save its state.
                entryDecorators = listOf(
                    rememberSaveableStateHolderNavEntryDecorator(),
                    rememberViewModelStoreNavEntryDecorator()
                ),
                entryProvider = entryProvider {
                    entry<RouteA> {
                        ContentGreen("Welcome to Nav3") {
                            LazyColumn {
                                items(10) { i ->
                                    Button(onClick = dropUnlessResumed {
                                        backStack.add(RouteB("$i"))
                                    }) {
                                        Text("$i")
                                    }
                                }
                            }
                        }
                    }
                    entry<RouteB> { key ->
                        // Note: We need a new ViewModel for every new RouteB instance. Usually
                        // we would need to supply a `key` String that is unique to the
                        // instance, however, the ViewModelStoreNavEntryDecorator (supplied
                        // above) does this for us, using `NavEntry.contentKey` to uniquely
                        // identify the viewModel.
                        //
                        // tl;dr: Make sure you use rememberViewModelStoreNavEntryDecorator()
                        // if you want a new ViewModel for each new navigation key instance.
                        ScreenB(viewModel = viewModel(factory = RouteBViewModel.Factory(key)))
                    }
                }
            )
        }
    }
}

@Composable
fun ScreenB(viewModel: RouteBViewModel = viewModel()) {
    ContentBlue("Route id: ${viewModel.key.id} ")
}

class RouteBViewModel(
    val key: RouteB
) : ViewModel() {
    class Factory(
        private val key: RouteB,
    ) : ViewModelProvider.Factory {
        override fun <T : ViewModel> create(modelClass: Class<T>): T {
            return RouteBViewModel(key) as T
        }
    }
}

การส่งอาร์กิวเมนต์ไปยัง ViewModel (Koin)

สูตรนี้แสดงวิธีส่งอาร์กิวเมนต์การนำทาง (คีย์) ไปยัง ViewModel โดยใช้ Koin สำหรับการแทรกทรัพยากร Dependency

วิธีการทำงาน

  1. มีการกำหนดโมดูล Koin ที่ให้ ViewModel
  2. ใช้ฟังก์ชันที่ประกอบกันได้ koinViewModel เพื่อรับอินสแตนซ์ ViewModel
  3. ระบบจะส่งคีย์การนำทางไปยังเครื่องมือสร้างของ ViewModel โดยใช้ parametersOf(key) ซึ่งจะทำให้ViewModelใช้คีย์การนำทางได้

หมายเหตุ: ระบบจะเพิ่ม rememberViewModelStoreNavEntryDecorator ลงในentryDecoratorsของNavDisplay ซึ่งจะช่วยให้มั่นใจได้ว่า ViewModel จะอยู่ในขอบเขตที่ถูกต้องสำหรับ NavEntry ที่เกี่ยวข้อง เพื่อให้ระบบสร้างอินสแตนซ์ ViewModel ใหม่สำหรับคีย์การนำทางที่ไม่ซ้ำกันแต่ละรายการ

package com.example.nav3recipes.passingarguments.viewmodels.koin

import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.lifecycle.ViewModel
import androidx.lifecycle.compose.dropUnlessResumed
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator
import androidx.navigation3.ui.NavDisplay
import com.example.nav3recipes.content.ContentBlue
import com.example.nav3recipes.content.ContentGreen
import com.example.nav3recipes.ui.setEdgeToEdgeConfig
import org.koin.compose.KoinApplication
import org.koin.compose.viewmodel.koinViewModel
import org.koin.core.module.dsl.viewModelOf
import org.koin.core.parameter.parametersOf
import org.koin.dsl.koinConfiguration
import org.koin.dsl.module

data object RouteA
data class RouteB(val id: String)

class KoinViewModelsActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {

        setEdgeToEdgeConfig()
        super.onCreate(savedInstanceState)
        setContent {
            val backStack = remember { mutableStateListOf<Any>(RouteA) }

            // Koin Compose Entry point
            KoinApplication(
                configuration = koinConfiguration {
                    modules(appModule)
                }
            ) {
                NavDisplay(
                    backStack = backStack,
                    onBack = { backStack.removeLastOrNull() },

                    // In order to add the `ViewModelStoreNavEntryDecorator` (see comment below for why)
                    // we also need to add the default `NavEntryDecorator`s as well. These provide
                    // extra information to the entry's content to enable it to display correctly
                    // and save its state.
                    entryDecorators = listOf(
                        rememberSaveableStateHolderNavEntryDecorator(),
                        rememberViewModelStoreNavEntryDecorator()
                    ),
                    entryProvider = entryProvider {
                        entry<RouteA> {
                            ContentGreen("Welcome to Nav3") {
                                LazyColumn {
                                    items(10) { i ->
                                        Button(onClick = dropUnlessResumed {
                                            backStack.add(RouteB("$i"))
                                        }) {
                                            Text("$i")
                                        }
                                    }
                                }
                            }
                        }
                        entry<RouteB> { key ->
                            val viewModel = koinViewModel<RouteBViewModel> {
                                parametersOf(key)
                            }
                            ScreenB(viewModel = viewModel)
                        }
                    }
                )
            }
        }
    }
}

// Local Koin Module
private val appModule = module {
    viewModelOf(::RouteBViewModel)
}

@Composable
fun ScreenB(viewModel: RouteBViewModel) {
    ContentBlue("Route id: ${viewModel.navKey.id} ")
}

class RouteBViewModel(val navKey: RouteB) : ViewModel()