Search code examples
androidkotlin-coroutinesandroid-viewmodeldagger-hilt

Injected variable only null in viewmodel coroutine with viewmodel scope referenced successfully in init block


I have a very strange issue. I am trying to run an API call in a ViewModel using a clean architecture-style repository object. I am able to access the repo object in the init block in a viewModelScope coroutine

I have a simple workaround; I simply don't call the function from the init block and instead expose a public function and call it from a fragment; so I have a solution but it is an interesting problem.

@HiltViewModel
class MainViewModel @Inject constructor(repo: JobsRepository) : ViewModel(){

    @Inject lateinit var repo : JobsRepository
    lateinit var jobDao: JobDao

    val jobsListUpdate: MutableLiveData<Resource<JobsResponse>> = MutableLiveData()
    var jobsListResponse: JobsResponse? = null
    init {
       jobDao = repo.getDao()
      // getJobsList()
        viewModelScope.launch {
            jobsListUpdate.postValue(Resource.Loading())
            val response = repo.getAllJobsByAPI()
            jobsListUpdate.postValue(handleBreakingNewsResponse(response))
        }
    }

The above code works. However the problem arises when I call it first from a non-suspended function.

@HiltViewModel
class MainViewModel @Inject constructor(repo: JobsRepository) : ViewModel(){

    @Inject lateinit var repo : JobsRepository
    lateinit var jobDao: JobDao
    
    val jobsListUpdate: MutableLiveData<Resource<JobsResponse>> = MutableLiveData()
    var jobsListResponse: JobsResponse? = null
    init {
        
       jobDao = repo.getDao() 
       getJobsList()
    }

    fun getJobsList() {
        viewModelScope.launch {
            getJobsListFromApi()
        }
    }

    private suspend fun getJobsListFromApi() {
        jobsListUpdate.postValue(Resource.Loading())
        val response = repo.getAllJobsByAPI()
        jobsListUpdate.postValue(handleBreakingNewsResponse(response))
    }

It's almost like the repo object is being nullified somewhere else in the codeflow, like perhaps after a navigation fragment is attached elsewhere.

The difference is definitely that suspend keyword. I would say its a race condition but whats unusual is that the reference to the repo becomes null after it has a value.

Any ideas? What else would be useful to add to this post?


Solution

  • I think you're real problem is that you're using both constructor injection and field injection on the same thing. I'm surprised this even compiles tbh. Try changing it to:

    @HiltViewModel
    class MainViewModel 
    @Inject constructor(
      private val repo: JobsRepository
    ) : ViewModel(){
        val jobsListUpdate: MutableLiveData<Resource<JobsResponse>> = MutableLiveData()
    
        init {
           getJobsList()
        }
    
        fun getJobsList() {
            viewModelScope.launch {
                getJobsListFromApi()
            }
        }
    
        private suspend fun getJobsListFromApi() {
            jobsListUpdate.postValue(Resource.Loading())
            val response = repo.getAllJobsByAPI()
            jobsListUpdate.postValue(handleBreakingNewsResponse(response))
        }
    }
    

    My guess is what you're seeing is caused by the above problem and isn't actually related to suspend functions.