Reputation: 737
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
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
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