Search code examples
androidkotlinmvvmviewmodelkoin

java.lang.RuntimeException: Cannot create an instance of class yodgorbek.komilov.musobaqayangiliklari.viewmodel.MainViewModel?


I am developing news and I have implemented ViewModel in a fragment. However, my app crashes.

Following is the error I get in my logcat:

  java.lang.RuntimeException: Cannot create an instance of class yodgorbek.komilov.musobaqayangiliklari.viewmodel.MainViewModel
            at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:184)
            at androidx.lifecycle.ViewModelProvider$AndroidViewModelFactory.create(ViewModelProvider.java:241)
            at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:164)
            at androidx.lifecycle.ViewModelProvider.get(ViewModelProvider.java:130)
            at yodgorbek.komilov.musobaqayangiliklari.ui.TopHeadlinesFragment.onCreateView(TopHeadlinesFragment.kt:58)
            at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2600)
            at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:881)
            at androidx.fragment.app.FragmentManagerImpl.moveFragmentToExpectedState(FragmentManagerImpl.java:1238)
            at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:1303)
            at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:439)
            at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:2079)
            at androidx.fragment.app.FragmentManagerImpl.executeOpsTogether(FragmentManagerImpl.java:1869)
            at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:1824)
            at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:1727)
            at androidx.fragment.app.FragmentManagerImpl.dispatchStateChange(FragmentManagerImpl.java:2663)
            at androidx.fragment.app.FragmentManagerImpl.dispatchActivityCreated(FragmentManagerImpl.java:2613)
            at androidx.fragment.app.FragmentController.dispatchActivityCreated(FragmentController.java:246)
            at androidx.fragment.app.FragmentActivity.onStart(FragmentActivity.java:542)
            at androidx.appcompat.app.AppCompatActivity.onStart(AppCompatActivity.java:201)
            at android.app.Instrumentation.callActivityOnStart(Instrumentation.java:1425)
            at android.app.Activity.performStart(Activity.java:7825)
            at android.app.ActivityThread.handleStartActivity(ActivityThread.java:3294)
            at android.app.servertransaction.TransactionExecutor.performLifecycleSequence(TransactionExecutor.java:221)
            at android.app.servertransaction.TransactionExecutor.cycleToPath(TransactionExecutor.java:201)
            at android.app.servertransaction.TransactionExecutor.executeLifecycleState(TransactionExecutor.java:173)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:97)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2016)
            at android.os.Handler.dispatchMessage(Handler.java:107)
            at android.os.Looper.loop(Looper.java:214)
            at android.app.ActivityThread.main(ActivityThread.java:7356)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
         Caused by: java.lang.InstantiationException: java.lang.Class<yodgorbek.komilov.musobaqayangiliklari.viewmodel.MainViewModel> has no zero argument constructor
            at java.lang.Class.newInstance(Native Method)
            at androidx.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:182)

Below is my MainViewModel.kt

class MainViewModel(newsRepository: NewsRepository) : ViewModel(), CoroutineScope {
    // Coroutine's background job
     val job = Job()
     val sportNewsInterface: SportNewsInterface? = null
    // Define default thread for Coroutine as Main and add job
    override val coroutineContext: CoroutineContext = Dispatchers.Main + job

     val showLoading = MutableLiveData<Boolean>()
     val sportList = MutableLiveData <List<Article>>()
    val showError = SingleLiveEvent<String>()



    fun loadNews(

    ) {
        // Show progressBar during the operation on the MAIN (default) thread
        showLoading.value = true
        // launch the Coroutine
        launch {
            // Switching from MAIN to IO thread for API operation
            // Update our data list with the new one from API
            val result = withContext(Dispatchers.IO) {
                sportNewsInterface?.getNews()
            }
            // Hide progressBar once the operation is done on the MAIN (default) thread
            showLoading.value = false
            when (result) {

                is UseCaseResult.Success<*> -> {
                    sportList.value = result.data as List<Article>
                }
                is Error -> showError.value = result.message
            }
        }


    }

    override fun onCleared() {
        super.onCleared()
        // Clear our job when the linked activity is destroyed to avoid memory leaks
        job.cancel()
    }
}

Below is my MainViewModelFactory.kt

class MainViewModelFactory(
    private val someParameter: NewsRepository
) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        return MainViewModel(someParameter) as T
    }
}

Below is my TopHeadlinesFragment.kt

class TopHeadlinesFragment : Fragment() {

    private var viewModel: MainViewModel? = null
    private lateinit var topHeadlinesAdapter: TopHeadlinesAdapter
    private  var newsRepository: NewsRepository? = null



    //3
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(
            R.layout.fragment_top_headlines
            , container, false
        )


        val recyclerView = view.findViewById(R.id.recyclerView) as RecyclerView
        val pb = view.findViewById(R.id.pb) as ProgressBar
        topHeadlinesAdapter = TopHeadlinesAdapter(recyclerView.context)
        recyclerView.layoutManager = LinearLayoutManager(context)
        recyclerView.adapter = topHeadlinesAdapter
        val param = newsRepository
        val factory = param?.let { MainViewModelFactory(it) }
        viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)
        initViewModel()

        return view
    }

    private fun initViewModel() {
        viewModel?.sportList?.observe(this, Observer { newList ->
            topHeadlinesAdapter.updateData(newList)
        })

        viewModel?.showLoading?.observe(this, Observer { showLoading ->
            pb.visibility = if (showLoading) View.VISIBLE else View.GONE
        })

        viewModel?.showError?.observe(this, Observer { showError ->
            (showError)
        })

        viewModel?.loadNews()
    }
}

NewsRepository.kt

interface NewsRepository {
    // Suspend is used to await the result from Deferred
    suspend fun getNewsList(): UseCaseResult<List<Article>>
}

class NewsRepositoryImpl(private val sportNewsInterface: SportNewsInterface) : NewsRepository {
    override suspend fun getNewsList(): UseCaseResult<List<Article>> {

        return try {
            val result = sportNewsInterface.getNews()
            UseCaseResult.Success(result) as UseCaseResult<List<Article>>
        } catch (ex: Exception) {
            UseCaseResult.Error(ex)
        }
    }
}

below appModules.kt

const val BASE_URL = "https://newsapi.org/"

val appModules = module {
    // The Retrofit service using our custom HTTP client instance as a singleton
    single {
        createWebService<SportNewsInterface>(
            okHttpClient = createHttpClient(),
            factory = RxJava2CallAdapterFactory.create(),
            baseUrl = BASE_URL
        )
    }
    // Tells Koin how to create an instance of CatRepository
    factory<NewsRepository> { (NewsRepositoryImpl(sportNewsInterface = get())) }
    // Specific viewModel pattern to tell Koin how to build MainViewModel
    viewModel { MainViewModel (newsRepository = get ())  }
}

/* Returns a custom OkHttpClient instance with interceptor. Used for building Retrofit service */
fun createHttpClient(): OkHttpClient {
    val client = OkHttpClient.Builder()
    client.readTimeout(5 * 60, TimeUnit.SECONDS)
    return client.addInterceptor {
        val original = it.request()
        val requestBuilder = original.newBuilder()
        requestBuilder.header("Content-Type", "application/json")
        val request = requestBuilder.method(original.method, original.body).build()
        return@addInterceptor it.proceed(request)
    }.build()
}

/* function to build our Retrofit service */
inline fun <reified T> createWebService(
    okHttpClient: OkHttpClient,
    factory: CallAdapter.Factory, baseUrl: String
): T {
    val retrofit = Retrofit.Builder()
        .baseUrl(baseUrl)
        .addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
        .addCallAdapterFactory(CoroutineCallAdapterFactory())
        .addCallAdapterFactory(factory)
        .client(okHttpClient)
        .build()
    return retrofit.create(T::class.java)
}

Please let me know, how to fix this crash

I have followed a method described here


Solution

  • Your ViewModelFactory needs to have NewsRepository to construct the ViewModel. But you newRepository is not initialized, and hence the factory is not initialized. Check the code again.

    Update : OP is using Koin for dependency injection and forgot to inject NewsRepository in the fragement. Solved by using private val newsRepository: newsRepository by inject()