В данной статье будут рассмотрены способы создания ViewModel (далее VM) в Android, а также usecase для каждого подхода.
Для начала сразу оговорим как нельзя создавать VM.
private val viewModel = MydViewModel() // Do not do that
Конечно, система Android никак не ограничивает такой подход к созданию VM. Однако при таком создании теряется смысл VM так как она не будет переживать смену конфигурации.
За создание в емуду в android отвечает ViewModelProvider. В нем есть 2 два метода: get(modelClass: Class<T>)
и get(key: String, modelClass: Class<T>)
.
Рассмотрим первый get(modelClass: Class<T>)
class MainActivity : AppCompatActivity() { val viewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) } //This is an identical entry //val viewModel by lazy { ViewModelProvider(this)[MyViewModel::class.java] } private val sharedViewModel: SharedViewModel by viewModels( factoryProducer = { MyViewModelFactory(MyRepository()) } ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("ViewModel", "viewModel $viewModel") } } class MyViewModel : ViewModel()
ViewModelProvider в конструктор принимает объект типа ViewModelStoreOwner, метод get
тип класс VM. Такая VMбудет уничтожена, когда вызовется метод соответствующего Activity.
Когда использовать:
-
когда VM должна быть привязана к жизненному циклу ViewModelStoreOwner
-
конструктор VM не имеет параметров
Рассмотрим второйget(key: String, modelClass: Class<T>)
class MainActivity : AppCompatActivity() { val id0 = 0L val id1 = 1L val viewModel0 by lazy { ViewModelProvider(this, MyViewModelFactory(id0)).get("id_$id0", MyViewModel::class.java) } val viewModel1 by lazy { ViewModelProvider(this, MyViewModelFactory(id1)).get("id_$id1", MyViewModel::class.java) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("ViewModel0", "viewModel0 $viewModel0") Log.d("ViewModel1", "viewModel1 $viewModel1") } } class MyViewModel( private val id: Long, ) : ViewModel() class MyViewModelFactory(private val id: Long) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MyViewModel::class.java)) { return MyViewModel(id) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
Когда использовать:
-
Если вашему приложению требуются несколько экземпляров одного и того же класса VM с разными данными или поведением, вы можете различать их с помощью уникальных ключей. Пример: в пользовательском интерфейсе с вкладками, где каждой вкладке нужен свой экземпляр одного и того же VM.
-
Когда VM необходимо обрабатывать данные, которые определяются динамически (например, на основе пользовательского ввода или условий выполнения), вы можете использовать ключи для различения экземпляров.
-
Повторное использование ViewModel Factory. Если вы используете кастомный ViewModelProvider.Factory для передачи параметров, ключ может помочь в управлении несколькими экземплярами VM, созданными с использованием одной и той же фабрики.
-
Расширенные сценарии с Navigation Graphs.При использовании компонента Navigation разные destinations могут использовать один и тот же класс ViewModel, но требовать разные экземпляры. Ключ может гарантировать, что каждый пункт назначения получит уникальный экземпляр.
Теперь рассмотрим конструкторы класса ViewModelProvider. Их 3:
class ViewModelProvider constructor( private val store: ViewModelStore, private val factory: Factory, private val defaultCreationExtras: CreationExtras = CreationExtras.Empty, ) constructor( owner: ViewModelStoreOwner, factory: Factory ) constructor( owner: ViewModelStoreOwner )
В примере выше можно увидеть реализацию MyViewModelFactory. Для чего служит ViewModelProvider.Factory:
-
передачи аргументов конструктора VM. Если VM требуются зависимости или данные например, repository, useCase или ID и тд.
-
создания нескольких экземпляров ViewModel. Если нужно несколько экземпляров одного и того же класса VM(например для разных наборов данных), фабрика позволяет различать их.
-
Кастомная логика VM. Если логика создания VM включает дополнительную настройку или конфигурацию, фабрика инкапсулирует эту логику.
Пример создания VM с помощью ViewModelProvider.Factory:
class MainActivity : AppCompatActivity() { private val viewModel: MyViewModel by lazy { val repository = MyRepository() val factory = MyViewModelFactory(repository) ViewModelProvider(this, factory).get(MyViewModel::class.java) } ) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("ViewModel", "viewModel $viewModel") } } class MyViewModel(myRepository: MyRepository) : ViewModel() class MyViewModelFactory( private val myRepository: MyRepository, ) : ViewModelProvider.Factory { override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MyViewModel::class.java)) { return MyViewModel(myRepository) as T } throw IllegalArgumentException("Unknown ViewModel class") } } class MyRepository
Следующий аргумент конструктора ViewModelProvider
— CreationExtras
.
CreationExtras позволяет передавать данные runtime, такие как аргументы или Context-зависимые значения, в процесс создания ViewModel без жесткой привязки ViewModel к конкретным источникам. Этот подход идеален, когда необходимо динамически вводить параметры во время выполнения, например данные, передаваемые через аргументы фрагментов, намерения действий или другие источники. Вот пример реализации:
class MainActivity : AppCompatActivity() { private val viewModel by lazy { ViewModelProvider( store = this.viewModelStore, factory = defaultViewModelProviderFactory, defaultCreationExtras = MutableCreationExtras(defaultViewModelCreationExtras).apply { this[DEFAULT_ARGS_KEY] = bundleOf("id" to 12345L) } )[MyViewModel::class.java] } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) Log.d("ViewModel", "viewModel $viewModel") } } class MyViewModel( private val savedStateHandle: SavedStateHandle, ) : ViewModel() { init { Log.d( "MyViewModel", "ViewModel [$this] created someId = ${savedStateHandle.get<Long>("id")}" ) } }
И последний аргумент ViewModelStoreOwner или ViewModelStore. Так как интерфейс ViewModelStoreOwner имеет вид
interface ViewModelStoreOwner { val viewModelStore: ViewModelStore }
то рассматривать их будем одним блоком.
ViewModelStoreOwner позволяет управлять жизненным циклом VM. По умолчанию он использует Fragment или Activity, где он вызывается (т.е. this). Однако никто не мешает создать свой кастомный ViewModelStoreOwner.
Вот несколько распространенных вариантов использования ViewModelStoreOwner :
-
Совместное использование ViewModel между родительскими и дочерними Fragment. Если вы хотите, чтобы несколько дочерних фрагментов совместно использовали один и тот же экземпляр VM, можете использовать родительский фрагмент как ViewModelStoreOwner.
class ParentFragment : Fragment() { private val sharedViewModel: SharedViewModel by viewModels() } class ChildFragment : Fragment() { private val sharedViewModel: SharedViewModel by viewModels( ownerProducer = { requireParentFragment() } ) }
-
Совместное использование VM между Activity и Fragments. Когда вам нужна VM в Activity scope, которую можно совместно использовать между несколькими фрагментами в одной Activity.
class MainActivity : AppCompatActivity() { val viewModel by lazy { ViewModelProvider(this).get(MyViewModel::class.java) } } class FragmentA : Fragment() { val viewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } } class FragmentB : Fragment() { val viewModel by lazy { ViewModelProvider(requireActivity()).get(MyViewModel::class.java) } }
-
Использование ViewModel, привязанной к NavGraph. Когда работаете с Navigation Component и хотите поделиться ViewModel, привязанной к определенному навигационному графику.
class MyFragment : Fragment() { val viewModel by lazy { ViewModelProvider( findNavController().getBackStackEntry(R.id.nav_graph_id) ).get(MyViewModel::class.java) } }
-
Custom Lifecycle Management. В некоторых advanced вариантах использования может быть custom ViewModelStoreOwner, который используется вместо Activity или Fragment по умолчанию. Например, может быть динамически созданный компонент, который действует как ViewModelStoreOwner. Вот примитивный пример:
class SomeTask { val customViewModelStoreOwner = object : ViewModelStoreOwner { override val viewModelStore: ViewModelStore = ViewModelStore() } val viewModel by lazy { ViewModelProvider(customViewModelStoreOwner)[MyViewModel::class.java] } init { delay(1000) customViewModelStoreOwner.viewModelStore.clear() } }
Эффективное создание экземпляров VM является ключевым аспектом разработки современных приложений Android, поскольку позволяет разработчикам управлять данными, связанными с пользовательским интерфейсом, с учетом жизненного цикла. Android предоставляет несколько механизмов для создания VM , каждый из которых подходит для определенных вариантов использования:
Создание ViewModel по умолчанию
Стандартный подход с использованием ViewModelProvider упрощает создание VM без дополнительных параметров, что делает его идеальным для сценариев, где не требуется внешняя инъекция данных.
Custom Fabrics
Использование ViewModelProvider.Factory позволяет внедрять зависимости и пользовательскую логику инициализации. Это особенно полезно в сценариях, где VM требуют параметры в runtime или сложных этапов настройки.
CreationExtras
CreationExtras предоставляет гибкий и современный способ динамической передачи данных в экземпляры VM . Он гарантирует, что зависимости, такие как аргументы, пользовательские данные или конфигурации, могут быть внедрены во время выполнения, сохраняя при этом несвязанный и тестируемый код.
Расширенные методы
ViewModelStoreOwner и доступ на основе ключей (например, get(key, modelClass)) дают разработчикам более гибкий контроль над созданием VM вне Activity и Fragment. Эти методы предназначены для определенных вариантов использования, таких как совместное использование VM несколькими фрагментами, обработка уникальных ключей или создание VM там где это нужно.
ссылка на оригинал статьи https://habr.com/ru/articles/871922/
Добавить комментарий