Как инициализировать viewModel с помощью области navGraph - PullRequest
4 голосов
/ 06 мая 2020

Я начинаю изучать модель общего представления. В настоящее время у меня есть 3 фрагмента внутри активности, 2 из них находятся внутри вложенного navGraph.

Я хочу создать общую область видимости navGraph viewModel для них обоих, но я не могу понять, как и где я могу инициировать представление модель внутри этих фрагментов.

Во всех моих прошлых приложениях я создал глобальную модель представления

private lateinit var viewModel: MainViewModel

А затем внутри onCreateView я инициализирую viewModel следующим образом -

viewModel = ViewModelProvider(this, Factory(requireActivity().application)).get(
   MainViewModel::class.java)

Как я могу сделать то же самое с областью navGraph viewModel, если я хочу поделиться одной моделью представления с 2 фрагментами?

В настоящее время у меня есть такой подход:

private val homeViewModel: HomeViewModel by navGraphViewModels(R.id.nested_navigation)

И это работают, но

A. Я никогда не видел, чтобы viewModel был вставлен прямо в глобальную переменную

B. Я не могу передавать переменные внутри factory с этот подход

1 Ответ

2 голосов
/ 07 мая 2020
private val homeViewModel: HomeViewModel by navGraphViewModels(R.id.nested_navigation)

И это работает, но

A. Я никогда не видел, чтобы viewModel инициализировался прямо в глобальной переменной

B. Я не могу передавать переменные внутри фабрики с помощью этого подхода

A.) ViewModel в этом случае инициализируется при первом доступе, поэтому, если вы просто наберете homeViewModel в onCreate или onViewCreated, тогда он будет создан с правильной областью действия.

B.) В этом есть правда, вы определенно можете использовать настраиваемую фабрику с navGraphViewModels, но что вы действительно хотите (вероятно), так это неявно передать любой из ваших Аргументы фрагмента для ViewModel (обратите внимание, что оба ваших фрагмента должны иметь правильные ключи в своих аргументах, чтобы это работало безопасно) с помощью SavedStateHandle.

Чтобы получить SavedStateHandle, вам необходимо использовать ан AbstractSavedStateViewModelFactory. Чтобы создать его, вы должны создать свою ViewModel внутри onViewCreated (onCreate не будет работать с навигационными графами), что проще всего сделать, используя ViewModelLazy.

Чтобы создать viewModelLazy, вы можете использовать createViewModelLazy (то, что написано на банке). Это может определить способ передачи ViewModelStoreOwner (который является NavBackStackEntry) и SavedStateRegistryOwner (который ТАКЖЕ является NavBackStackEntry).

Таким образом, вы можете поместить это в ваш код, и он должен работать.

inline fun <reified T : ViewModel> SavedStateRegistryOwner.createAbstractSavedStateViewModelFactory(
    arguments: Bundle,
    crossinline creator: (SavedStateHandle) -> T
): ViewModelProvider.Factory {
    return object : AbstractSavedStateViewModelFactory(this, arguments) {
        @Suppress("UNCHECKED_CAST")
        override fun <T : ViewModel?> create(
            key: String, modelClass: Class<T>, handle: SavedStateHandle
        ): T = creator(handle) as T
    }
}

inline fun <reified T : ViewModel> Fragment.navGraphSavedStateViewModels(
    @IdRes navGraphId: Int,
    crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
    // Wrapped in lazy to not search the NavController each time we want the backStackEntry
    val backStackEntry by lazy { findNavController().getBackStackEntry(navGraphId) }

    return createViewModelLazy(T::class, storeProducer = {
        backStackEntry.viewModelStore
    }, factoryProducer = {
        backStackEntry.createAbstractSavedStateViewModelFactory(
            arguments = backStackEntry.arguments ?: Bundle(), creator = creator
        )
    })
}

inline fun <reified T : ViewModel> Fragment.fragmentSavedStateViewModels(
    crossinline creator: (SavedStateHandle) -> T
): Lazy<T> {
    return createViewModelLazy(T::class, storeProducer = {
        viewModelStore
    }, factoryProducer = {
        createAbstractSavedStateViewModelFactory(arguments ?: Bundle(), creator)
    })
}

@Suppress("UNCHECKED_CAST")
inline fun <reified T : ViewModel> Fragment.fragmentViewModels(
    crossinline creator: () -> T
): Lazy<T> {
    return createViewModelLazy(T::class, storeProducer = {
        viewModelStore
    }, factoryProducer = {
        object : ViewModelProvider.Factory {
            override fun <T : ViewModel?> create(
                modelClass: Class<T>
            ): T = creator.invoke() as T
        }
    })
}

Теперь вы можете сделать

private val homeViewModel: HomeViewModel by navGraphSavedStateViewModels(R.id.nested_navigation) { savedStateHandle ->
    HomeViewModel(savedStateHandle)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view)

    homeViewModel.someData.observe(viewLifecycleOwner) { someData ->
        ...
    }
}
...