亲宝软件园·资讯

展开

Kotlin ViewModelProvider.Factory的使用实例详解

破浪会有时 人气:0

这里,我们将介绍 Kotlin ViewModelProvider.Factory 的作用和使用方式。

在我们使用 ViewModel 的时候,我们会发现,有的时候我们需要用到 ViewModelFactory,有的时候不需要。

这里,我们将介绍 Kotlin ViewModelProvider.Factory 的作用和使用方式。

在我们使用 ViewModel 的时候,我们会发现,有的时候我们需要用到 ViewModelFactory,有的时候不需要。

1 没有使用到 ViewModelFactory 的例子

下面这个例子中,我们没有使用到 ViewModelFactory:

MainActivity.kt

class MainActivity : AppCompatActivity() {
    lateinit var viewModel: ListViewModel
    private val countriesAdapter = CountryListAdapter(arrayListOf())
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModel = ViewModelProviders.of(this).get(ListViewModel::class.java)
        viewModel.refresh()
        countriesList.apply {
            layoutManager = LinearLayoutManager(context)
            adapter = countriesAdapter
        }
        observeViewModel()
    }
    ...
}

ListViewModel.kt

class ListViewModel: ViewModel() {
    private val countriesService = CountriesService.getCountriesService()
    var job: Job? = null
    private val exceptionHandler = CoroutineExceptionHandler{ coroutineContext, throwable ->
        onError("Exception: ${throwable.localizedMessage}")
    }
    // 生命周期感知型组件 MutableLiveData,可以做到在组件处于激活状态的时候才会回调相应的方法,从而刷新相应的 UI
    val countries = MutableLiveData<List<Country>>()
    val countryLoadError = MutableLiveData<String?>()
    val loading = MutableLiveData<Boolean>()
    fun refresh() {
        fetchCountries()
    }
    private fun fetchCountries() {
        loading.value = true
        // 通过launch启动一个携程回返回一个Job类型的对象实例,我们可以通过job.start()来启动携程(如果launch(start = CoroutineStart.LAZY)
        // 这么设置的话),可以通过job.cancel来取消携程
        job = CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
            val response : Response<List<Country>> = countriesService.getCountries()
            // after we get the response, we hope that we could switch back to main thread and display on screen.
            withContext(Dispatchers.Main) {
                if (response.isSuccessful){
                    countries.value = response.body()
                    countryLoadError.value = null
                    loading.value = false
                } else
                {
                    onError("Error: ${response.message()}")
                }
            }
        }
    }
    private fun onError(message: String) {
        countryLoadError.value = message
        loading.value = false
    }
    override fun onCleared() {
        super.onCleared()
        job?.cancel()
    }
}

这里,我们不纠结代码中的细节,只观察 viewModel 如何被定义和使用。

2 使用到 ViewModelFactory 的例子

下面这个例子中,我们、使用到了 ViewModelFactory:

LoginViewModelFactory.kt

class LoginViewModelFactory(
    private  val repository: RegisterRepository,
    private val application: Application
): ViewModelProvider.Factory{
    @Suppress("Unchecked_cast")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(LoginViewModel::class.java)) {
            return LoginViewModel(repository, application) as T
        }
        throw IllegalArgumentException("Unknown View Model Class")
    }
}

LoginViewModel.kt

class LoginViewModel(private val repository: RegisterRepository, application: Application) :
    AndroidViewModel(application), Observable {
    val users = repository.users
    @Bindable
    val inputUsername = MutableLiveData<String>()
    @Bindable
    val inputPassword = MutableLiveData<String>()
    private val viewModelJob = Job()
    private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
...
}

LoginFragment.kt

class LoginFragment : Fragment() {
    private lateinit var loginViewModel: LoginViewModel
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentLoginBinding = DataBindingUtil.inflate(
            inflater,
            R.layout.fragment_login, container, false
        )
        val application = requireNotNull(this.activity).application
        val dao = RegisterDatabase.getInstance(application).registerDatabaseDao
        val repository = RegisterRepository(dao)
        val factory = LoginViewModelFactory(repository, application)
        loginViewModel = ViewModelProvider(this, factory).get(LoginViewModel::class.java)
        binding.myLoginViewModel = loginViewModel
        binding.lifecycleOwner = this
        loginViewModel.navigatetoRegister.observe(this, Observer { hasFinished->
            if (hasFinished == true){
                Log.i("MYTAG","insidi observe")
                displayUsersList()
                loginViewModel.doneNavigatingRegiter()
            }
        })
        ...
    }
}

3 分析

我们发现,当我们在 MainActivity.kt 中使用 ViewModelProviders 声明 viewModel 时,我们没有调用任何 viewModel 的构造函数。这是因为,ViewModelProviders 在内部为我们管理并调用 ViewModel 的主要构造函数(primary constructor)并创建 ViewModel 的实例并将实例返回。

如果我们将参数传递给 viewModel 的构造函数时,其他都不变,这个时候,系统会报错:RunTimeError。之所以会有这个报错是因为 ViewModelProviders.of() 方法在内部创建默认的 ViewModelProvider.Factory 实现来创建我们的没有参数的 ViewModel(再次注意,这里的 ViewModel 是没有参数的)。 因此,当我们在构造函数中添加参数时,ViewModelProvider.Factory 的内部实现无法初始化我们这个 ViewModel,因为 ViewModelProvider.Factory 调用了创建 ViewModel 实例的主构造函数。

所以说,如果在构造函数中添加参数,则必须创建自己的 ViewModelProvider.Factory 实现来创建 ViewModel 实例。

那么,什么是 ViewModelProvider.Factory?还是刚才的第二个例子,我们把相关的代码复制在下面:

LoginViewModelFactory.kt

class LoginViewModelFactory(
    private  val repository: RegisterRepository,
    private val application: Application
): ViewModelProvider.Factory{
    @Suppress("Unchecked_cast")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(LoginViewModel::class.java)) {
            return LoginViewModel(repository, application) as T
        }
        throw IllegalArgumentException("Unknown View Model Class")
    }
}

这里有几点需要注意:

我们可以通过构造函数或我们喜欢的任何其他模式(Singleton、FactoryPattern 等)将 ViewModel 参数传递给 ViewModelProvider.Factory。 这是因为我们在初始化 ViewModel 时无法在 ActivityFragment 中调用 ViewModel 构造函数,并且我们想设置 ViewModel 构造函数的参数值,因此我们需要将参数值传递给

ViewModelProvider.Factory,它将创建 ViewModelViewModelProvider.Factory 是一个具有 create 方法的接口。 create 方法负责创建我们的 VeiwModel 的实例。

我们在LoginFragment.kt中是这么实例化 ViewModel 的:

val factory = LoginViewModelFactory(repository, application)
loginViewModel = ViewModelProvider(this, factory).get(LoginViewModel::class.java)

我们将我们的参数或依赖项传递给我们的 ViewModelProvider.Factory,以便它能够为我们创建 ViewModel。 ViewModelProviders.of(context, factory) 方法获取我们的 ViewModelProvider.Factory 的实例。

4 结论

现在我们应该很清楚 ViewModelProvider.Factory 的作用和使用方式了。这里做一个简单的总结:

何时使用 ViewModelProvider.Factory

如果我们的 ViewModel 有依赖项或参数传递,那么我们应该通过构造函数传递此依赖项(这是传递依赖项的最佳方式)。这个时候 ViewModelProvider.Factory 需要被使用。

何时不使用 ViewModelProvider.Factory

如果我们的 ViewModel 没有依赖项或参数传递,那么我们将不需要创建自己的 ViewModelProvider.Factory。默认会自动为我们创建 ViewModel。

加载全部内容

相关教程
猜你喜欢
用户评论