تمرير الوسيطات إلى ViewModels (باستخدام Hilt)
توضّح هذه الوصفة كيفية تمرير وسيطات التنقّل (المفاتيح) إلى ViewModel باستخدام Hilt لتوفير التبعية.
طريقة العمل
يستخدم هذا المثال ميزة "إدخال البيانات المساعدة" في Dagger/Hilt:
- يتم إضافة التعليق التوضيحي
@HiltViewModelإلىViewModel، وتستخدم طريقة وضع التصميم@AssistedInjectلتلقّي مفتاح التنقّل (الذي يتم إضافة التعليق التوضيحي@Assistedإليه). - يتم تحديد واجهة
@AssistedFactoryلإنشاءViewModel. - يتم استخدام الدالة البرمجية القابلة للإنشاء
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 } }
تمرير وسيطات إلى ViewModels (أساسي)
يوضّح هذا المثال كيفية تمرير وسيطات التنقّل (المفاتيح) إلى ViewModel باستخدام ViewModelProvider.Factory مخصّص.
طريقة العمل
- يتم إنشاء
ViewModelProvider.Factoryمخصّص يأخذ مفتاح التنقّل كمعلَمة منشئ. - داخل العنصر القابل للإنشاء
entry، يتم استخدامviewModel(factory = ...)لإنشاء مثيلViewModel، مع تمرير مفتاح التنقّل الحالي إلى المصنع. يؤدي ذلك إلى إتاحة مفتاح التنقّل لـ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 } } }
تمرير وسيطات إلى ViewModels (باستخدام Koin)
توضّح هذه الوصفة كيفية تمرير وسيطات التنقّل (المفاتيح) إلى ViewModel باستخدام Koin لتوفير التبعية.
طريقة العمل
- يتم تحديد وحدة Koin النمطية التي توفّر
ViewModel. - يتم استخدام الدالة البرمجية القابلة للإنشاء
koinViewModelللحصول على مثيلViewModel. - يتم تمرير مفتاح التنقل إلى الدالة الإنشائية
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()