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

Why a viewmodel factory is needed in Android?

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

我只是举了一个简单的例子来说明我是如何在没有工厂的情况下做到的

这里是 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

我们无法自行创建 ViewModel。我们需要 Android 提供的 ViewModelProviders 实用程序来创建 ViewModel。

但是 ViewModelProvider 只能实例化没有 arg 构造函数的 ViewModel。

因此,如果我有一个带有多个参数的 ViewModel,那么我需要使用一个可以传递给 ViewModelProviders 的工厂,以便在需要 MyViewModel 的实例时使用。

例如 -

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

要实例化这个 ViewModel,我需要一个工厂,ViewModelProvider 可以使用它来创建它的实例。

ViewModelProviders Utility 无法创建带有参数构造函数的 ViewModel 实例,因为它不知道在构造函数中传递的对象和对象。

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?

因为 Android 只会为您提供尚未创建的新实例 对于特定的给定 ViewModelStoreOwner.

我们也不要忘记 ViewModel 在配置更改时保持活动状态,因此如果您旋转 phone,您不应该创建新的 ViewModel。

如果您要返回到之前的 Activity 并重新打开此 Activity,则之前的 ViewModel 应该接收 onCleared() 而新的 Activity 应该有一个新的 ViewModel。

除非你自己做,否则你应该相信 ViewModelProviders.Factory 来完成它的工作。

(而且你需要工厂,因为你通常不只是有一个 no-arg 构造函数,你的 ViewModel 有构造函数参数,并且 ViewModelProvider 必须知道如何填写构造函数参数您正在使用非默认构造函数)。

简而言之,

如果我们需要传递一些input dataviewModelconstructor,我们需要创建一个factory class 对于视图模型。

例如:-

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")
        }
    }
}

原因

我们不能直接创建 ViewModel 的对象,因为它 不会 知道 lifecyclerOwner。所以我们使用 :-

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

当我们简单地使用 ViewModel 时,我们无法将参数传递给该 ViewModel

class GameViewModel() : ViewModel() {

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

但是,在某些情况下,我们需要将自己的参数传递给 ViewModel。这可以使用 ViewModelFactory.

来完成
class ScoreViewModel(finalScore: Int) : ViewModel() {

    val score = finalScore

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

为了实例化这个 ViewModel,我们需要一个 ViewModelProvider.Factory,因为简单的 ViewModel 无法实例化它。

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")
    }
}

当谈到实例化此 ViewModel 的对象时,即使用 ViewModelProvider,我们将 ViewModelFactory 作为参数传递,其中包含有关我们要传递的自定义参数的信息。它是这样的:

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

这就是工厂方法存在的原因。