Home > Enterprise >  why viewModel is not initialized
why viewModel is not initialized

Time:08-17

I have a MainActivity in which I am using a few Fragments using Navigation components, but my app is crashing lateinit var viewModel is not initialized error, i am not able to figure out why my viewmodel is not intilized

Error

  Caused by: kotlin.UninitializedPropertyAccessException: lateinit property viewModel has not been initialized
    at com.choudhary.newsappmvvm.ui.MainActivity.getViewModel(MainActivity.kt:18)
    at com.choudhary.newsappmvvm.ui.fragments.BreakingNewsFragment.onViewCreated(BreakingNewsFragment.kt:27)
    at androidx.fragment.app.Fragment.performViewCreated(Fragment.java:2987)
    at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:546)
    at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:282)
    at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
    at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2106)
    at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
    at androidx.fragment.app.FragmentManager.dispatchStateChange(FragmentManager.java:3138)

My MainActivity

class MainActivity : AppCompatActivity() {


 lateinit var viewModel: NewsViewModel


override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)


    val repository = NewsRepository(ArticleDataBase(this))

    val viewModelFactory = NewsViewModelFactory(repository)

    viewModel   = ViewModelProvider(this,viewModelFactory).get(NewsViewModel::class.java)


    bottomNavigationView.setupWithNavController(newsNavHostFragment.findNavController())
}

My viewModelFactory Class

class NewsViewModelFactory(private  val repository: NewsRepository)  :
ViewModelProvider.Factory {

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

inside Fragment i using like this

class BreakingNewsFragment : Fragment(R.layout.fragment_breaking_news) {





lateinit var  viewModel : NewsViewModel
lateinit var  newsAdapter : NewsAdapter

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel  =(activity as MainActivity).viewModel

    setUpRecylerView()

    viewModel.breakingNews.observe(viewLifecycleOwner, Observer { response->

        when(response){

            is Resource.Success ->{
                hideProgress()
                response.data?.let {  newsResponse ->
                     newsAdapter.differ.submitList(newsResponse.articles)
                }
            }

            is Resource.Error ->{

                hideProgress()
                response.messsage?.let {

                    Log.d("somnath", it)
                }
            }

            is Resource.Loading ->{
                showProgress()
            }
        }
    })

}

My dependepencies

  implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.0'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'



implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'

   // Room
    implementation "androidx.room:room-runtime:2.2.5"
    annotationProcessor "androidx.room:room-compiler:2.2.5"

// Kotlin Extensions and Coroutines support for Room
implementation "androidx.room:room-ktx:2.2.5"

// Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'



// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
implementation "com.squareup.okhttp3:logging-interceptor:4.5.0"

// Navigation Components
implementation "androidx.navigation:navigation-fragment-ktx:2.2.1"
implementation "androidx.navigation:navigation-ui-ktx:2.2.1"

// Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
kapt 'com.github.bumptech.glide:compiler:4.11.0'

My NewsRepository

class NewsRepository(
val db : ArticleDataBase
) {

suspend fun getBreakingNews(countryCode : String,pageNum : Int) =

    RetrofitInstance.api.getBreakingNews(countryCode, pageNumber = pageNum)

}

My Database class for room

@Database(
entities = [Article::class],
version = 1
)
@TypeConverters(Converters::class)
 abstract class ArticleDataBase  : RoomDatabase() {

abstract  fun getArticlesDao(): ArticleDao

companion object{
    @Volatile
    private  var INSTANCE : ArticleDataBase ? = null
    private  val LOCK = Any()

    operator  fun invoke(context : Context)  = INSTANCE ?: synchronized(LOCK) {
        INSTANCE ?: createDatabase(context).also{ INSTANCE = it}
    }

    private  fun createDatabase(context: Context ) =

        Room.databaseBuilder(
            context.applicationContext,
            ArticleDataBase::class.java,
            "article_db.db"
        ).build()
}

}

CodePudding user response:

You're relying on your Activity's onCreate running before your Fragment's onViewCreated does, and that's not always the case. That's why this:

viewModel = (activity as MainActivity).viewModel

is accessing your lateinit viewModel property before it's been initialised.


You shouldn't really have Fragments accessing ViewModels through the Activity itself, because of issues like this. They have separate lifecycles, and it can be tricky to juggle them and ensure things like this. That's part of why the ViewModel library was created, so you don't need to worry about it because the complexity is handled for you!

So your Fragment should also be using ViewModelProvider to grab the VM. Since you want the same copy the Activity holds, just pass the Activity to the constructor, so it fetches the VM associated with that

// in the Fragment

lateinit var viewModel: NewsViewModel

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    val repository = NewsRepository(ArticleDataBase(requireContext()))
    val viewModelFactory = NewsViewModelFactory(repository)

    viewModel = ViewModelProvider(requireActivity(), viewModelFactory).get(NewsViewModel::class.java)

This is using requireActivity() and requireContext() because you have access to those by the time onCreate runs. The Fragment is attached to the Activity, which has been instantiated, but isn't necessarily in the CREATED state yet, which is the whole problem here. So you can pass the Activity to the ViewModelProvider to fetch its instance of the VM, creating one for it if necessary


Also if this Fragment is the only thing using this NewsViewModel, and it's not shared, then the Activity doesn't need to be involved at all. So all this code could be in the Fragment instead of duplicated in both (and passing the Fragment to ViewModelProvider instead)

  • Related