Search code examples
kotlinmvvmviewmodel

java.lang.RuntimeException: Cannot create an instance of class ViewModel


I'm trying to instantiate a viewModel class which contains a init block where I'm trying to send a Get request with volley. The problem is when I'm trying to instantiate this class in my Fragment class I'm getting this error and I don't know why. Maybe the context from the constructor would be the problem? Thanks:

ViewModel class

class BooksFragmentViewModel(c : Context) : ViewModel() {

lateinit var cont: Context
var books: MutableLiveData<MutableList<BookItem>> = MutableLiveData<MutableList<BookItem>>()
var triggerAddBook = MutableLiveData<Boolean>()
var triggerDeleteBook = MutableLiveData<Boolean>()
var triggerEditBook = MutableLiveData<Boolean>()
var bookRepo: BookRepository = BookRepository()
var bookItemAdd: BookItem? = null
var bookItemDelete: BookItem? = null
var bookItemEdit : BookItem? = null
var bookNew: LiveData<BookItem> = Transformations.switchMap(triggerAddBook) {
    if (it != null && it)
        addBook()
    else
        null
}

  init {
        books = bookRepo.booksGetRequest(c)
    }

Fragment Class

class BooksFragment : Fragment(), BooksAdapter.OnDeleteBtnClicked, BooksAdapter.OnEditBtnClicked {
lateinit var booksModel: BooksFragmentViewModel
var position: Int = 0

private var adapter: BooksAdapter = BooksAdapter(this, this)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View? {
    return inflater.inflate(R.layout.fragment_books, container, false)
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    booksModel = ViewModelProvider(requireActivity()).get(BooksFragmentViewModel::class.java)
    booksLoading_progressBar.visibility = View.VISIBLE
    var c: Context? = context
    if (c != null) {
        booksModel.setContext(c)
    }

Solution

  • The issue is indeed because of the primary constructor with arguments. You are asking the ViewModelProvider to create an instance of BooksFragmentViewModel class but the provider does not know anything about the expected argument.

    Here is a post on that topic.

    How to fix the issue?

    1. Get rid of all primary constructor arguments if possible. That should be the easiest way;
    2. Create ViewModelProvider.Factory that will handle creation of your view model.

    The first option is clear, but for the second one, you should do the following.

    Create ViewModelProvider.Factory:

    class BooksFragmentViewModelFactory (val context: Context) :
        ViewModelProvider.Factory {
    
        override fun <T : ViewModel?> create(modelClass: Class<T>): T {
            if (modelClass.isAssignableFrom(BooksFragmentViewModel::class.java)) {
                return BooksFragmentViewModel(context) as T
            } else {
                throw RuntimeException("Failed to create instance of the ${modelClass.simpleName}")
            }
        }
    }
    

    Use the factory with ViewModelProvider:

        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            val bookViewModelFactory = BooksFragmentViewModelFactory(requireContext())
            booksModel = ViewModelProvider(requireActivity(), bookViewModelFactory).get(BooksFragmentViewModel::class.java)
            booksLoading_progressBar.visibility = View.VISIBLE
        }
    

    For more explanation check out this codelab from Google (step 8). There is enough examples to understand the basics of how to use a view model factory.