Android

关注公众号 jb51net

关闭
首页 > 软件编程 > Android > Kotlin ViewModelProvider.Factory

Kotlin ViewModelProvider.Factory的使用实例详解

作者:破浪会有时

这篇文章主要介绍了Kotlin ViewModelProvider.Factory的使用,在我们使用 ViewModel 的时候,我们会发现,有的时候我们需要用到 ViewModelFactory,有的时候不需要

这里,我们将介绍 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。

到此这篇关于Kotlin ViewModelProvider.Factory的使用实例详解的文章就介绍到这了,更多相关Kotlin ViewModelProvider.Factory内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文