Ma2340
Ma2340

Reputation: 737

Android viewmodel pass parameter to view model from view

I have a Fragment code -

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

   val safeArgs: PetDetailsViewArgs by navArgs()
   val petId = safeArgs.petId

   viewModel.getPetDetailsForId(petId).observe(viewLifecycleOwner, {
     // ...
   })
}

I have a ViewModel code -

private val viewState = PetDetailsViewState()

fun getPetDetailsForId(id: String?): LiveData<PetDetailsViewState> {
   return if (id.isNullOrEmpty()) {
         liveData {
             emit(
                 viewState.copy(
                     loading = false,
                     error = ErrorType.PET_ID_NULL_OR_EMPTY
                 )
             )
         }
        } else {
            petDetailsLiveData
        }
    }

    var petDetailsLiveData = petService.performPetAction(PetAction.GetPetDetails("2")).map {
            when (it) {
                // ...
            }
        }.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)

As you see in my ViewModel, I am at the moment hardcoding the id in PetAction.GetPetDetails("2") which is not correct.

How do I pass the id from my view to viewModel?

Upvotes: 0

Views: 3819

Answers (2)

Ma2340
Ma2340

Reputation: 737

Found a way to do with savedStateHandle -

Here is my Fragment -

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

   viewModel.petDetailsViewData.observe(viewLifecycleOwner, {

   })
}

ViewModel -

class PetDetailsViewModel @ViewModelInject constructor(
    private val petService: PetService,
    @Assisted private val savedStateHandle: SavedStateHandle
) :
    ViewModel() {

    private val viewState = PetDetailsViewState()
    var petDetailsViewData =
        petService.performPetAction(PetAction.GetPetDetails(savedStateHandle.get<String>("petId")!!))
            .map {
                when (it) {
                    // ... 
                }
            }.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)
}

I basically use safeArgs key inside viewModel and access it via savedStateHandle. This way I don't need to bother my view with accessing ids and also on configuration change, I only call my service once.

Upvotes: 0

ChristianB
ChristianB

Reputation: 2690

You have two options, if the petId (from the Fragment) does not change, you could create / inject your ViewModel and pass the petId via Constructor.

Can your petId be null? If not you can then directly initialize your LiveData and observe it from your Fragment.

class PetViewModel(petId: String): ViewModel() {
  val petDetailsLiveData = petService.performPetAction(PetAction.GetPetDetails(petId)).map {
            // ...
        }.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)
} 

Second option, as you showed in your question, if petId can change, create the LiveData within the function getPetDetailsForId(id: String?).

fun getPetDetailsForId(id: String?): LiveData<PetDetailsViewState> {
   return if (id.isNullOrEmpty()) {
         liveData {
             emit(
                 viewState.copy(
                     loading = false,
                     error = ErrorType.PET_ID_NULL_OR_EMPTY
                 )
             )
         }
        } else {
            petService.performPetAction(PetAction.GetPetDetails("2")).map {
              // ...
            }.asLiveData(Dispatchers.Default + viewModelScope.coroutineContext)
    }

After discussion

You can consider some caching of your petId and the PetDetailsViewState to avoid duplicate api calls. Take this a a very simple example of getting the idea. There is much to improve here.

class PetViewModel : ViewModel() {

 private val cachedPetDetailsViewState: PetDetailsViewState? = null
 private val cachedPetId: String = "" 

 fun getPetDetailsForId(id: String?): LiveData<PetDetailsViewState> {
   if (id == cachedPetId && cachedPetDetailsViewState != null) return MutableLiveData(cachedPetDetailsViewState)

   cachedPetId == id

   if (id.isNullOrEmpty() { ... }
   else { 
     val petIdViewState = // make the API call
 
     cachedPetDetailsViewState = petIdViewState
     
     return MutableLiveData(petIdViewState)
   }   
 }
}

Upvotes: 1

Related Questions