首页 > 解决方案 > 第二个 viewModel 无法构造

问题描述

我在为我的应用构建第二个 viewModel 时遇到问题。

在我的项目中,第一个 viewModel 观察一个联系人列表,而第二个 viewModel 观察一个联系人。

两个视图模型非常相似,但是第一个构建,而第二个无法构建

E/AndroidRuntime: FATAL EXCEPTION: main
    Process: fr.dleurs.android.contactapp, PID: 12324
    java.lang.RuntimeException: Unable to start activity ComponentInfo{fr.dleurs.android.contactapp/fr.dleurs.android.contactapp.ui.detailsContact.DetailsContactActivity}: java.lang.IllegalArgumentException: Unable to construct viewmodel
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
        at android.os.Handler.dispatchMessage(Handler.java:106)
        at android.os.Looper.loop(Looper.java:223)
        at android.app.ActivityThread.main(ActivityThread.java:7656)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
     Caused by: java.lang.IllegalArgumentException: Unable to construct viewmodel
        at fr.dleurs.android.contactapp.viewmodel.ContactViewModel$Factory.create(ContactViewModel.kt:33)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:187)
        at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:150)
        at fr.dleurs.android.contactapp.ui.detailsContact.DetailsContactActivity.onCreate(DetailsContactActivity.kt:40)
        at android.app.Activity.performCreate(Activity.java:8000)
        at android.app.Activity.performCreate(Activity.java:7984)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601) 
        at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85) 
        at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) 
        at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) 
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066) 
        at android.os.Handler.dispatchMessage(Handler.java:106) 
        at android.os.Looper.loop(Looper.java:223) 
        at android.app.ActivityThread.main(ActivityThread.java:7656) 
        at java.lang.reflect.Method.invoke(Native Method) 
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592) 
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947) 
I/Process: Sending signal. PID: 12324 SIG: 9

第二个视图模型:ContactViewModel.kt

package fr.dleurs.android.contactapp.viewmodel

import android.app.Application
import androidx.lifecycle.*
import fr.dleurs.android.contactapp.database.ContactDatabase
import fr.dleurs.android.contactapp.database.ContactsDatabase.Companion.getInstance
import fr.dleurs.android.contactapp.model.Contact
import fr.dleurs.android.contactapp.repository.ContactRepository
import kotlinx.coroutines.launch

class ContactViewModel(application: Application) : AndroidViewModel(application) {
    private val contactsRepository = ContactRepository(getInstance(application).contactDtbDao())
    fun liveContact(contactId: String): LiveData<Contact> = contactsRepository.contact(contactId)

    public fun updateContact(contact: ContactDatabase) {
        viewModelScope.launch {
            contactsRepository.updateContact(contact);
        }
    }

    public fun deleteContact(contactId: String) {
        viewModelScope.launch {
            contactsRepository.deleteContact(contactId);
        }
    }

    class Factory(val app: Application) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(ContactsViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return ContactViewModel(app) as T
            }
            throw IllegalArgumentException("Unable to construct viewmodel")
        }
    }
}

第二个活动,DetailsContactActivity.kt

package fr.dleurs.android.contactapp.ui.detailsContact

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.ImageButton
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import fr.dleurs.android.contactapp.R
import fr.dleurs.android.contactapp.database.ContactDatabase
import fr.dleurs.android.contactapp.databinding.DetailsContactActivityBinding
import fr.dleurs.android.contactapp.model.Contact
import fr.dleurs.android.contactapp.model.asDatabaseModel
import fr.dleurs.android.contactapp.ui.main.createContactActivityRequestCode
import fr.dleurs.android.contactapp.ui.main.detailContactActivityRequestCode
import fr.dleurs.android.contactapp.ui.newModifyContact.CreateModifyContactActivity
import fr.dleurs.android.contactapp.viewmodel.ContactViewModel
import fr.dleurs.android.contactapp.viewmodel.ContactsViewModel
import timber.log.Timber

class DetailsContactActivity : AppCompatActivity() {

    private lateinit var binding: DetailsContactActivityBinding
    private lateinit var contactId: String
    private lateinit var viewModel: ContactViewModel


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        contactId = intent?.getStringExtra("contactId") ?: ""
        assert(!contactId.isNullOrEmpty())

        viewModel = ViewModelProvider(
            this,
            ContactViewModel.Factory(this.application)
        ).get(ContactViewModel::class.java)

        val buttonEdit = findViewById<ImageButton>(R.id.ibEdit)
        val buttonDelete = findViewById<ImageButton>(R.id.ibDelete)
        val buttonBack = findViewById<ImageButton>(R.id.ibBack)

        binding = DataBindingUtil.setContentView(this, R.layout.details_contact_activity)
        viewModel.liveContact(contactId).observe(this, Observer<Contact> { theContact -> // this or viewLifecycleOwner ?
            theContact?.let {
                binding.apply { contact = it }
            }
        })

        buttonEdit.setOnClickListener {
            val editIntent = Intent(this, CreateModifyContactActivity::class.java)
            //editIntent.putExtra("contact", it)
            startActivityForResult(editIntent, createContactActivityRequestCode)
        }

        buttonDelete.setOnClickListener {
            viewModel.deleteContact(contactId)
            finish()
        }


        buttonBack.setOnClickListener {
            val replyIntent = Intent()
            setResult(Activity.RESULT_CANCELED, replyIntent)
            finish()
        }


    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intentData: Intent?) {

        if (requestCode == createContactActivityRequestCode && resultCode == Activity.RESULT_OK ) {
            val modifiedIntent = Intent()
            val modifiedContact = intentData?.getParcelableExtra<ContactDatabase>("contact")
            Timber.i("modifiedContact : " + modifiedContact.toString())
            val contact: Contact = Contact(
                id = modifiedContact!!.id.toString(),
                firstName = modifiedContact!!.firstName,
                lastName = modifiedContact!!.lastName,
                mail = modifiedContact!!.mail
            )
            modifiedIntent.putExtra("action", "modify")
            modifiedIntent.putExtra("contact", contact)
            setResult(Activity.RESULT_OK, modifiedIntent)
            finish()
        }
        super.onActivityResult(requestCode, resultCode, intentData)
    }
}

存储库:ContactRepository.kt

package fr.dleurs.android.contactapp.repository

import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations
import fr.dleurs.android.contactapp.database.*
import fr.dleurs.android.contactapp.model.Contact
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import fr.dleurs.android.contactapp.network.asDatabaseModel
import java.util.logging.Level.INFO

class ContactRepository(private val contactDtbDao: ContactDtbDao) {

    val contacts: LiveData<List<Contact>> =
        Transformations.map(contactDtbDao.getContacts()) {
            it.asDomainModel()
        }

    fun contact(contactId: String): LiveData<Contact> =
        Transformations.map(contactDtbDao.getContact(contactId)) { it.asDomailModel() }


    suspend fun refreshContacts() {
        withContext(Dispatchers.IO) {
            Log.i("ContactRepo", "Refresh contacts is called");
            val contactList = ContactRetrofitApi.contacts.getContacts()
            Log.i("ContactRepo", "ContactList created ${contactList.toString()}");
            contactDtbDao.insertAll(contactList.asDatabaseModel())
        }
    }

    suspend fun insertContact(contact: ContactDatabase) {
        withContext(Dispatchers.IO) {
            contactDtbDao.insert(contact);
        }
    }

    suspend fun deleteContact(contactId: String) {
        withContext(Dispatchers.IO) {
            contactDtbDao.delete(contactId);
        }
    }

    suspend fun updateContact(contact: ContactDatabase) {
        withContext(Dispatchers.IO) {
            contactDtbDao.update(contact);
        }
    }
}

工作的 ViewModel,ContactsViewModel.kt:

package fr.dleurs.android.contactapp.viewmodel

import android.app.Application
import android.util.Log
import androidx.lifecycle.*
import fr.dleurs.android.contactapp.database.ContactDatabase
import fr.dleurs.android.contactapp.database.ContactsDatabase
import fr.dleurs.android.contactapp.model.Contact
import fr.dleurs.android.contactapp.repository.ContactRepository
import kotlinx.coroutines.launch
import java.io.IOException

class ContactsViewModel(application: Application) : AndroidViewModel(application) {

    //private val contactsRepository = ContactRepository(ContactsDatabase.getDatabase(application))

    private val contactsRepository = ContactRepository(ContactsDatabase.getInstance(application).contactDtbDao())
    var liveContacts: LiveData<List<Contact>> = contactsRepository.contacts

    public fun insertContact(contact: ContactDatabase) {
        viewModelScope.launch {
            contactsRepository.insertContact(contact);
        }
    }


    /**
     * Event triggered for network error. This is private to avoid exposing a
     * way to set this value to observers.
     */
    private var _eventNetworkError = MutableLiveData<Boolean>(false)

    /**
     * Event triggered for network error. Views should use this to get access
     * to the data.
     */
    val eventNetworkError: LiveData<Boolean>
        get() = _eventNetworkError

    /**
     * Flag to display the error message. This is private to avoid exposing a
     * way to set this value to observers.
     */
    private var _isNetworkErrorShown = MutableLiveData<Boolean>(false)

    /**
     * Flag to display the error message. Views should use this to get access
     * to the data.
     */
    val isNetworkErrorShown: LiveData<Boolean>
        get() = _isNetworkErrorShown

    /**
     * init{} is called immediately when this ViewModel is created.
     */
    init {
        refreshDataFromRepository()
    }

    /**
     * Refresh data from the repository. Use a coroutine launch to run in a
     * background thread.
     */
    private fun refreshDataFromRepository() {
        viewModelScope.launch {
            try {
                contactsRepository.refreshContacts()
                _eventNetworkError.value = false
                _isNetworkErrorShown.value = false

            } catch (networkError: IOException) {
                // Show a Toast error message and hide the progress bar.
                Log.i("ContactViewModel", "Error on refreshContact : ${networkError.toString()}")
                if (liveContacts.value.isNullOrEmpty())
                    _eventNetworkError.value = true
            }
        }
    }

    /**
     * Resets the network error flag.
     */
    fun onNetworkErrorShown() {
        _isNetworkErrorShown.value = true
    }

    class Factory(val app: Application) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(ContactsViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return ContactsViewModel(app) as T
            }
            throw IllegalArgumentException("Unable to construct viewmodel")
        }
    }
}

工作活动,ContactActivity.kt

package fr.dleurs.android.contactapp.ui.main

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModelProvider
import androidx.viewpager.widget.ViewPager
import com.google.android.material.tabs.TabLayout
import fr.dleurs.android.contactapp.R
import fr.dleurs.android.contactapp.database.ContactDatabase
import fr.dleurs.android.contactapp.model.Contact
import fr.dleurs.android.contactapp.model.asDatabaseModel
import fr.dleurs.android.contactapp.ui.detailsContact.DetailsContactActivity
import fr.dleurs.android.contactapp.ui.newModifyContact.CreateModifyContactActivity
import fr.dleurs.android.contactapp.utils.FabButtonInterface
import fr.dleurs.android.contactapp.viewmodel.ContactsViewModel
import timber.log.Timber

public val createContactActivityRequestCode = 1
public val detailContactActivityRequestCode = 2


class ContactActivity : AppCompatActivity(), FabButtonInterface, OnClick {


    private lateinit var viewModel: ContactsViewModel
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProvider(
            this,
            ContactsViewModel.Factory(this.application)
        ).get(ContactsViewModel::class.java)


        setContentView(R.layout.contact_activity)
        val sectionsPagerAdapter =
            SectionsPagerAdapter(context = this, fm = supportFragmentManager, onClick = this)
        val viewPager: ViewPager = findViewById(R.id.contentFragment)
        viewPager.adapter = sectionsPagerAdapter
        val tabs: TabLayout = findViewById(R.id.tabs)
        tabs.setupWithViewPager(viewPager)
    }

    override fun goToCreateContactActivity() {
        Timber.i("Create a new contact started")
        val intent = Intent(this, CreateModifyContactActivity::class.java)
        startActivityForResult(intent, createContactActivityRequestCode)
    }

    fun goToDetailContactActivity(contactId: String) {
        Timber.i("Details contact started")
        val intent = Intent(this, DetailsContactActivity::class.java)
        intent.putExtra("contactId", contactId)
        startActivity(intent)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, intentData: Intent?) {
        super.onActivityResult(requestCode, resultCode, intentData)

        if (requestCode == createContactActivityRequestCode && resultCode == Activity.RESULT_OK) {
            Timber.i("Intent received")
            val response = intentData?.getParcelableExtra<ContactDatabase>("contact")
            val newContact = ContactDatabase(
                firstName = response!!.firstName,
                lastName = response!!.lastName,
                mail = response!!.mail
            )
            Timber.i("Intent received + " + newContact.toString())
            viewModel.insertContact(newContact)

        } else if (requestCode == createContactActivityRequestCode && resultCode == Activity.RESULT_CANCELED) {

        } else if (requestCode == detailContactActivityRequestCode) {
            Timber.i("DetailsContactActivity intent :" + intent.toString() + ", intentData: "+intentData.toString()+"resultCode :" + resultCode.toString())
            if (resultCode == Activity.RESULT_CANCELED) {
                Timber.i("DetailsContactActivity closed with no action")
            }

        } else {
            Toast.makeText(this, "Error creating contact", Toast.LENGTH_LONG).show()
        }
    }

    override fun onItemClick(contactId: String) {
        Timber.i("On Item clicked + " + contactId)
        goToDetailContactActivity(contactId)
    }
}


谢谢您的帮助 !

标签: androidandroid-viewmodelandroid-mvvm

解决方案


你在这里路过ContactsViewModel::class.java。你必须通过ContactViewModel::class.java

class Factory(val app: Application) : ViewModelProvider.Factory {
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(ContactViewModel::class.java)) {
                @Suppress("UNCHECKED_CAST")
                return ContactViewModel(app) as T
            }
            throw IllegalArgumentException("Unable to construct viewmodel")
        }
    }

推荐阅读