web-dev-qa-db-fra.com

Pourquoi une usine de viewmodel est-elle nécessaire dans Android?

Nous en avons discuté, mais nous ne connaissons pas la raison de la création d'une fabrique de viewmodels pour créer un viewmodel au lieu d'instancier directement le viewmodel. Quel est le gain de créer une usine qui ne fait que créer le viewmodel?

Je viens de mettre un exemple simple de la façon dont je l'ai fait sans Factory

voici le module kodein:

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

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

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

Le morceau de l'activité où j'instancie le modèle de vue sans utiliser l'usine

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

Le ViewModel où j'instancie directement le cas d'utilisation sans l'avoir dans le constructeur

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

Voici donc un exemple utilisant l'usine

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

Le viewmodelfactory:

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

Et le viewmodel:

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

Voici le premier exemple complet:

https://github.com/ibanarriolaIT/Marvel/tree/mvvm

Et le deuxième exemple complet:

https://github.com/AdrianMeizoso/Payment-App

26
Iban Arriola

Nous ne pouvons pas créer ViewModel par nous-mêmes. Nous avons besoin de l'utilitaire ViewModelProviders fourni par Android pour créer ViewModels.

Mais ViewModelProviders ne peut instancier que ViewModels sans constructeur arg.

Donc, si j'ai un ViewModel avec plusieurs arguments, je dois utiliser une fabrique que je peux transmettre à ViewModelProviders pour utiliser lorsqu'une instance de MyViewModel est requise.

Par exemple -

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

Pour instancier ce ViewModel, j'ai besoin d'avoir une usine que ViewModelProviders peut utiliser pour créer son instance.

L'utilitaire ViewModelProviders ne peut pas créer d'instance d'un ViewModel avec un constructeur d'arguments car il ne sait pas comment et quels objets passer dans le constructeur.

24
Vishal Arora

Nous en avons discuté, mais nous ne connaissons pas la raison de la création d'une fabrique de viewmodels pour créer un viewmodel au lieu d'instancier directement le viewmodel. Quel est le gain de créer une usine qui ne fait que créer le modèle de vue?

Parce que Android ne vous donnera une nouvelle instance que si elle n'est pas encore créée pour ce LifecycleOwner donné.

N'oublions pas non plus que les ViewModels sont maintenus en vie malgré les changements de configuration, donc si vous faites pivoter le téléphone, vous n'êtes pas censé créer un nouveau ViewModel.

Si vous revenez à une activité précédente et que vous rouvrez cette activité, le ViewModel précédent devrait recevoir onCleared() et la nouvelle activité devrait avoir un nouveau ViewModel.

À moins que vous ne le fassiez vous-même, vous devriez probablement faire confiance au ViewModelProviders.Factory pour faire son travail.

(Et vous avez besoin de l'usine parce que vous n'avez généralement pas seulement un no-arg constructeur, votre ViewModel a des arguments constructeur, et le ViewModelProvider doit savoir comment remplir les arguments constructeur lorsque vous utilisez un constructeur non par défaut).

14
EpicPandaForce

En bref,

si nous devons passer certains input data au constructor du viewModel, nous devons créer un factory class pour viewModel.

Comme exemple: -

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

Raison

Nous ne pouvons pas créer directement l'objet du ViewModel car il ne serait pas être au courant du lifecyclerOwner. Nous utilisons donc: -

ViewModelProviders.of(this, MyViewModelFactory(repository)).get(MyViewModel::class.Java)
2
Santanu Sur