ViewModel的创建
ViewModel的创建
ViewModel本身只是
ViewModel这个类的子类:
class MainViewModel: ViewModel() { }
在屏幕旋转UI重建的时候, 它是如何拥有保持数据的能力的呢? 它又是何时被清理的呢?
答案全跟它是如何创建, 保存的有关系.
本文回顾一下创建ViewModel的几种常见写法.
注: 本文中的图并不是严格意义的时序图(也不符合规范), 只是为了简略表示一下代码中的调用关系.
原生手动创建ViewModel
当ViewModel没有构造参数
当ViewModel没有参数的时候很简单:
class MainViewModel: ViewModel() {} class MainActivity : ComponentActivity() { private lateinit var viewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this).get(MainViewModel::class.java) } }
当然不是随便就
new出来的啦, 得通过
ViewModelProvider来get.
注意: 这句不能在Activity的
onCreate()之前调用, 也意味着你不能声明字段直接赋值.
否则你就会得到这个报错:
Caused by: java.lang.IllegalStateException: Your activity is not yet attached to the Application instance. You can't request ViewModel before onCreate call.
此时的方法调用大概是这样:
从这里我们看到
ViewModelProvider这个工具人要借助其他两个东西来提供ViewModel:
ViewModelStore
: 负责存储ViewModel.Factory
: 负责实例化具体的ViewModel类型.
请记住这两个知识点, 后面要考.
在上面这个最简单的例子中:
- Activity是
ViewModelStoreOwner
, 它可以getViewModelStore()
当ViewModelStoreOwner
(比如Activity)因为configuration changes重建, 新的owner仍然会get这个旧的ViewModelStore
实例. - 我们没有传
Factory
, 所以最终用的是没有参数的NewInstanceFactory
.
当ViewModel有构造参数
但是通常, ViewModel会需要一些依赖, 我们就需要从构造传入一些参数
class MainViewModel( private val repository: MainRepository, ) : ViewModel()
此时我们的工厂就需要自己实现了:
我们需要把依赖对象传给工厂, 好让它构造ViewModel的时候能用上:
class MyViewModelFactory constructor(private val repository: MainRepository) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.j 32da ava)) { return MainViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
接着把我们的工厂传给
ViewModelProvider:
class MainActivity : ComponentActivity() { private lateinit var viewModel: MainViewModel private val repository: MainRepository = MainRepository() private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProvider(this, viewModelFactory).get(MainViewModel::class.java) } }
此时的过程大概是这样:
AndroidX来帮忙
感谢AndroidX
activity-ktx(
fragment-ktx里也有Fragment版本的)包里的
by viewModels()属性代理,
我们上述的代码可以简化成这样:
class MyViewModelFactory constructor(private val repository: MainRepository) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { return MainViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } } class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels { viewModelFactory } private val repository: MainRepository = MainRepository() private val viewModelFactory: MyViewModelFactory = MyViewModelFactory(repository) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // use viewModel } }
工厂依然自己写, 简化了provider get的部分:
- 扩展方法, 隐含activity对象.
- lazy规避了生命周期的问题, 只要使用ViewModel的地方不在
onCreate
之前就行.
ViewModel没有参数的时候更简单:
private val viewModel: MainViewModel by viewModels()
Dagger时代
有了dagger, 我们可以在构造上标记
@Inject告诉dagger帮我们创建repository和viewModelFactory:
class MainRepository @Inject constructor(){} @Singleton class MyViewModelFactory @Inject constructor(private val repository: MainRepository) : ViewModelProvider.Factory { override fun <T : ViewModel?> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { return MainViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } }
但是我们却不能对ViewModel这样做, 因为ViewModel应该被存储在ViewModelStore里, 而它是由activity提供的.
我们会注入viewModelFactory, 然后用它来创建ViewModel:
class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels { viewModelFactory } @Inject lateinit var viewModelFactory: MyViewModelFactory }
因为你用的是dagger, 你会需要在
onCreate()里写类似这样的东西:
(applicationContext as MyApplication).appComponent.inject(this)
这里只是用DI框架简化了依赖和工厂的构造.
Hilt时代
在ViewModel上标记:
@HiltViewModel, 构造标记:
@Inject:
@HiltViewModel class MainViewModel @Inject constructor( private val repository: MainRepository, ) : ViewModel() { }
依赖们也可以构造标记
@Inject:
class MainRepository @Inject constructor(){}
在Activity加上注解
@AndroidEntryPoint,
也用了
by viewModels():
@AndroidEntryPoint class MainActivity : ComponentActivity() { private val viewModel: MainViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // use viewModel } }
请注意, 和dagger不同的是, 这里并不需要注入工厂(也不需要写工厂), Hilt用的是自己的工厂
HiltViewModelFactory.
这个过程:
Compose的ViewModel()方法
在Compose中获取一个ViewModel非常简单, 可以只用一个
viewModel()方法.
(这个方法在
androidx.lifecycle:lifecycle-viewmodel-compose依赖包里).
@Composable fun Greeting(name: String) { val viewModel: MainViewModel = viewModel() // use viewModel }
Activity的onCreate()中setContent是Compose, 于是在这里就设置好各种owners,
与ViewModel相关的就是这个
LocalViewModelStoreOwner, 之后包在里面的内容就可以随时获取owner, 得到ViewModelStore和ViewModel了.
Compose的hiltViewModel()方法
上面的
viewModel()方法不管在哪里获取, ViewModel的scope都是和当前的Activity或Fragment绑定.
假如我们有多个composable的界面呢?
使用
hiltViewModel()这个composable方法我们可以获取到一个scope到某个导航目的地的ViewModel.
(这个方法在
androidx.hilt:hilt-navigation-compose依赖包里).
NavHost(navController = navController, startDestination = "friendslist") { composable("friendslist") { val viewModel = hiltViewModel<MainViewModel>() FriendsList(viewModel = viewModel, navHostController = navController) } ... }
这种方式获取的ViewModel, 它的生命周期是和这个导航目的地相关的, 当退出这个界面, ViewModel就被clear, 下次再进就又是新的对象.
此时的owner终于不再是Activity或Fragment, 而是
NavBackStackEntry.
导航这个话题就先不展开这里讲了.
总结
ViewModel的创建很关键, 关系到它的生命周期.
手动创建比较麻烦, 很多样板代码.
本文总结了几种常见的创建方式, 希望读者看完后能有更清晰的理解, 每种方式都是怎么回事, 那些方便的工具替我们做了什么.
- (转)Qt Model/View 学习笔记 (四)——创建新的Models
- IntraWeb下Model-View-Presenter开发实战--创建篇之一 (View)
- IntraWeb下Model-View-Presenter开发实战--创建篇之二(Presenter)
- IntraWeb下Model-View-Presenter开发实战--创建篇之三(Model)
- Qt Model/View 学习笔记 (四) 创建新的Models
- (转)Qt Model/View 学习笔记 (四)——创建新的Models
- OpenCV 2 学习笔记(13): 算法的基本设计模式<4> :使用Model-View-Controller模式创建一个应用程序
- 结合ViewModel创建Fragement完成数据的保存
- Qt Model/View学习笔记之四创建新的Model
- 如何创建Asp.net MVC ViewModel
- ASP.NET MVC自定义视图引擎ViewEngine 创建Model的专属视图
- 自定义MVC视图引擎ViewEngine 创建Model的专属视图
- 在ASP.NET MVC中使用Knockout实践02,组合View Model成员、Select绑定、通过构造器创建View Model,扩展View Model方法
- Qt Model/View 学习笔记 (二):为使用Models与views热身
- MODEL/VIEW (二)
- 创建横向的UitableView
- DTO、Model,ViewModel,Object,Entity作用
- Android 属性动画(Property Animation 给按钮的添加添加动画效果,同样可以用在整个布局上) (下)(viewGroup创建对象竟然可以用布局的id)
- java 代码创建RecyclerView 无法显示滚动条
- Model/View Programming