为什么 Android 需要一个视图模型工厂?

我们一直在讨论这个问题,但是我们不知道创建视图模型工厂来创建视图模型而不是直接实例化视图模型的原因。创建一个只创建视图模型的工厂有什么好处?

我只是举了一个没有 Factory 的简单例子

这就是 Kodein 模块:

val heroesRepositoryModel = Kodein {
bind<HeroesRepository>() with singleton {
HeroesRepository()
}


bind<ApiDataSource>() with singleton {
DataModule.create()
}


bind<MainViewModel>() with provider {
MainViewModel()
}
}

我在其中实例化视图模型而不使用工厂的 Activity 的一部分

class MainActivity : AppCompatActivity() {
private lateinit var heroesAdapter: HeroAdapter
private lateinit var viewModel: MainViewModel
private val heroesList = mutableListOf<Heroes.MapHero>()
private var page = 0
private var progressBarUpdated = false


override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this)
.get(MainViewModel::class.java)
initAdapter()
initObserver()
findHeroes()
}

在 ViewModel 中,我直接实例化用例,而不在构造函数中包含它

class MainViewModel : ViewModel(), CoroutineScope {


private val heroesRepository: HeroesRepository = heroesRepositoryModel.instance()
val data = MutableLiveData<List<Heroes.MapHero>>()


private var job: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + job


fun getHeroesFromRepository(page: Int) {
launch {
try {
val response = heroesRepository.getHeroes(page).await()
data.value = response.data.results.map { it.convertToMapHero() }
} catch (e: HttpException) {
data.value = null
} catch (e: Throwable) {
data.value = null
}
}
}


override fun onCleared() {
super.onCleared()
job.cancel()
}
}

这里有一个使用工厂的例子

class ListFragment : Fragment(), KodeinAware, ContactsAdapter.OnContactListener {


override val kodein by closestKodein()


private lateinit var adapterContacts: ContactsAdapter


private val mainViewModelFactory: MainViewModelFactory by instance()
private val mainViewModel: MainViewModel by lazy {
activity?.run {
ViewModelProviders.of(this, mainViewModelFactory)
.get(MainViewModel::class.java)
} ?: throw Exception("Invalid Activity")
}


override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_list, container, false)
}

视图模型工厂:

class MainViewModelFactory (private val getContacts: GetContacts) : ViewModelProvider.Factory {


override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(MainViewModel::class.java)) {
return MainViewModel(getContacts) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

还有视图模型:

class MainViewModel(private val getContacts: GetContacts) : BaseViewModel() {
lateinit var gamesList: LiveData<PagedList<Contact>>
var contactsSelectedData: MutableLiveData<List<Contact>> = MutableLiveData()
var contactsSelected: ArrayList<Contact> = ArrayList()
private val pagedListConfig by lazy {
PagedList.Config.Builder()
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(PAGES_CONTACTS_SIZE)
.setPageSize(PAGES_CONTACTS_SIZE)
.setPrefetchDistance(PAGES_CONTACTS_SIZE*2)
.build()
}

下面是完整的第一个例子:

Https://github.com/ibanarriolait/marvel/tree/mvvm

完整的第二个例子:

Https://github.com/adrianmeizoso/payment-app

58476 次浏览

We can not create ViewModel on our own. We need ViewModelProviders utility provided by Android to create ViewModels.

But ViewModelProviders can only instantiate ViewModels with no arg constructor.

So if I have a ViewModel with multiple arguments, then I need to use a Factory that I can pass to ViewModelProviders to use when an instance of MyViewModel is required.

For example -

public class MyViewModel extends ViewModel {
private final MyRepo myrepo;
public MyViewModel(MyRepo myrepo) {
this.myrepo = myrepo;
}
}

To instantiate this ViewModel, I need to have a factory which ViewModelProviders can use to create its instance.

ViewModelProviders Utility can not create instance of a ViewModel with argument constructor because it does not know how and what objects to pass in the constructor.

We have been discussing about this but we don't know the reason of creating a viewmodel factory to create a viewmodel instead of instantiate the viewmodel directly. What is the gain of creating a factory that just creates the viewmodel?

Because Android will only give you a new instance if it's not yet created for that specific given ViewModelStoreOwner.

Let's also not forget that ViewModels are kept alive across configuration changes, so if you rotate the phone, you're not supposed to create a new ViewModel.

If you are going back to a previous Activity and you re-open this Activity, then the previous ViewModel should receive onCleared() and the new Activity should have a new ViewModel.

Unless you're doing that yourself, you should probably just trust the ViewModelProviders.Factory to do its job.

(And you need the factory because you typically don't just have a no-arg constructor, your ViewModel has constructor arguments, and the ViewModelProvider must know how to fill out the constructor arguments when you're using a non-default constructor).

In short,

if we need to pass some input data to the constructor of the viewModel , we need to create a factory class for viewModel.

Like example :-

class MyViewModelFactory constructor(private val repository: DataRepository): ViewModelProvider.Factory {


override fun <T : ViewModel> create(modelClass: Class<T>): T {
return if (modelClass.isAssignableFrom(MyViewModel::class.java!!)) {
MyViewModel(this.repository) as T
} else {
throw IllegalArgumentException("ViewModel Not Found")
}
}
}

Reason

We cannot directly create the object of the ViewModel as it would not be aware of the lifecyclerOwner. So we use :-

ViewModelProviders.of(this, MyViewModelFactory(repository)).get(MyViewModel::class.java)

When we are simply using ViewModel, we cannot pass arguments to that ViewModel

class GameViewModel() : ViewModel() {


init {
Log.d(TAG, "GameViewModel created")
}
}

However, in some cases, we need to pass our own arguments to ViewModel. This can be done using ViewModelFactory.

class ScoreViewModel(finalScore: Int) : ViewModel() {


val score = finalScore


init {
Log.d(TAG, "Final score: $finalScore")
}
}

And to instantiate this ViewModel, we need a ViewModelProvider.Factory as simple ViewModel cannot instantiate it.

class ScoreViewModelFactory(private val finalScore: Int) : ViewModelProvider.Factory {


override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ScoreViewModel::class.java)) {
return ScoreViewModel(finalScore) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

When it comes to instantiating object of this ViewModel i.e with ViewModelProvider, we pass ViewModelFactory as an argument which contains information about our custom arguments which we want to pass. It goes like:

viewModelFactory = ScoreViewModelFactory(score)
viewModel = ViewModelProvider(this,viewModelFactory).get(ScoreViewModel::class.java)

That is why factory methods are there.