10

I have 2 fragments, FragmentA contains a list of starwar characters whereas FragmentB contains details of that character. I am using viewModelScope.launch in my fragments to fetch details for a character. Below is my ViewModel

@HiltViewModel
class DetailsViewModel @Inject constructor(
    private val getSpecieDetailsUseCase: GetSpecieDetailsUseCase,
    private val getFilmDetailsUseCase: GetFilmDetailsUseCase,
    private val getPlanetDetailsUseCase: GetPlanetDetailsUseCase,
    private val mapper: CharacterDetailMapper
) : ViewModel() {

    var characterDetailsModel = MutableLiveData<CharacterDetailsModel?>()

    fun init(infoModel: CharacterInfoModel?) {
        characterDetailsModel.postValue(null)
        try {
            viewModelScope.launch {
                infoModel?.let {
                    val specieResponse = async(context = Dispatchers.IO) {
                        it.specieIdList?.map {
                            getSpecieDetailsUseCase.executeUseCase(
                                GetSpecieDetailsUseCase.GetSpecieDetailsRequest(
                                    it
                                )
                            )
                        }
                    }
                    val filmResponse = async(context = Dispatchers.IO) {
                        it.filmsIdList?.map {
                            getFilmDetailsUseCase.executeUseCase(
                                GetFilmDetailsUseCase.GetFilmDetailsRequest(
                                    it
                                )
                            )
                        }
                    }
                    val planetResponse = it.homeworldId?.let {
                        getPlanetDetailsUseCase.executeUseCase(
                            GetPlanetDetailsUseCase.GetPlanetDetailsRequest(
                                it
                            )
                        )
                    }
                    characterDetailsModel.postValue(
                        mapper.toModel(
                            name = it.name.toString(),
                            birth_year = it.birth_year.toString(),
                            height = it.height.toString(),
                            specieDetailsResponse = specieResponse.await(),
                            filmDetailsResponse = filmResponse.await(),
                            planetDetailsResponse = planetResponse
                        )
                    )
                }
            }
        } catch (exception: Exception) {
            exception.stackTrace
        }

    }
}

Above viewModelScope.launch works perfectly fine when hit for the first time but does not work for the second time [i.e going back to the previous fragment and coming back on details fragment]. The data received in the init function is also updated data however none of my async calls seem to work for the second time. onCleared method of View Model is called every time when I go to the previous fragment which I feel should clear the scope. I tried catching an exception however there is no exception thrown. I tried debugging but none of my debug points for async calls are hit.

Akshay Shah
  • 748
  • 1
  • 8
  • 23
  • Are you sure you aren't passing a null `infoModel` to this function? By the way, the way you are using try/catch won't do anything. Calling `launch` queues the coroutine to start, which will never throw an exception. Exceptions can occur inside the coroutine due to things you do in it, but then you need try/catch to be inside the coroutine. – Tenfour04 Apr 05 '21 at 21:26
  • Nope I am not passing a null to the infoModel – Akshay Shah Apr 05 '21 at 21:30
  • `onCleared` is supposed to be called only once before destroying the viewmodel. If you receive instance that was already cleared when returning to your fragment you're improperly injecting it as dependency or otherwise holding hard reference to it. – Pawel Apr 05 '21 at 22:03
  • Yes, it is called only when viewModel is destroyed. – Akshay Shah Apr 06 '21 at 06:44
  • 4
    @AkshayShah Have you found the reason why and how to fix it ? – Thân Hoàng Mar 30 '22 at 16:09

1 Answers1

2

Check how you create this viewmodel and, which is more important when. If you are using by viewModels() LAZY delegate than check when it will be initialized first time. viewModels() used this as a viewModelStoreOwner. In 99% of cases when launch doesn't work this will point to host Activity. The solution is simple - create your own lamda which will return correct viewModelStoreOwner of Fragment or use old style of viewModel initialization.

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProvider(this).get(TextClassifiedViewModel::class.java)
    }
w201
  • 2,018
  • 1
  • 11
  • 15