alex.tavella
04/21/2021, 7:11 PM[Flow + LiveData]
Hi, we have been adopting Flow
on our app and there are still some behaviors that we want to achieve that are a bit unclear. So, our presentation architecture basically consists on archictecture component’s ViewModel
producing state in a ViewState
class that has its properties exposed as LiveData
, which are observed by the View
layer. We have a case similar to the following:
internal class HomeViewModel constructor(
observeSessionAddress: GetSessionAddressFlowUseCase,
getHome: GetHomeUseCase,
) : ViewModel {
private val addressStream = observeSessionAddress()
.distinctUntilChanged()
.shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)
private val homeStream = addressStream
.map { getHome(it.lat, it.lon) }
.shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)
val viewState = HomeViewState(
addressStream.asLiveData(),
homeStream.asLiveData()
)
}
internal class HomeFragment : Fragment() {
...
override fun onViewCreated(
view: View,
savedInstanceState: Bundle?,
) {
super.onViewCreated(view, savedInstanceState)
...
with (viewModel.viewState) {
address.observe(viewLifecycleOwner, ::updateAddress)
home.observe(viewLifecycleOwner, ::updateHome)
}
}
}
The class GetSessionAddressFlowUseCase
returns a Flow
.
The code works fine, when Fragment is firstly created, it starts observing the address and fetches home content appropriately and also when navigating forward and getting back, we get the previous value without actually triggering getHome
again.
The only problem is: by using shareIn
I just made homeStream
and addressStream
hot flows, and after leaving HomeFragment
by navigating forward (we replace this fragment and keep in the backstack, so only onDestroyView
is called), the hot flow will still be live since HomeViewModel
is still alive and hence if we change address on another screen, the getHome
use case will still trigger. That may seem harmless at first, but if other devs start following this approach, we might trigger a bunch of expensive operations even if that info is not important to the user at that moment. One alternative is to keep these streams as cold, but after navigating back to the Fragment
would trigger getHome
again, which is an expensive operation, so that is out of the question.
Any ideas/suggestions?Luke
04/21/2021, 7:19 PMSharingStarted.WhileSubscribed()
instead of SharingStarted.Lazily
alex.tavella
04/21/2021, 7:23 PMLuke
04/21/2021, 7:50 PMgetHome
to be called. I'm thinking maybe you want a StateFlow
(using .stateIn()
instead)? When subscribing, you get the stored value, and subsequent values. You would need to give an initial value thoughAdam Powell
04/21/2021, 7:55 PMAdam Powell
04/21/2021, 7:56 PMSharingStarted.WhileSubscribed
alex.tavella
04/21/2021, 7:59 PMgetHome
should be called only if address changes and if there is a subscriber. If the user gets back to the screen and address hasn’t change, getHome
should not be called because it probably will yield in the same results obtained previously, therefore calling it again would waste resources.alex.tavella
04/21/2021, 8:10 PMFragment
as:
lifecycleScope.launch {
viewModel.viewState.home
.flowWithLifecycle(viewLifecycle, Lifecycle.State.CREATED)
.collect { updateHome(it) }
}
But after Fragment
view lifecycle goes from DESTROYED
to CREATED
again, the upstream will be triggered again, right?Adam Powell
04/21/2021, 8:12 PMCREATED
from DESTROYED
, DESTROYED
is terminal and the fragment gets a new view lifecycle instance.Adam Powell
04/21/2021, 8:12 PMviewLifecycleScope.launch {
for the initial launch instead, and launch in onViewCreated
alex.tavella
04/21/2021, 8:18 PMgetHome
) every time on onViewCreated
?Adam Powell
04/21/2021, 8:21 PMalex.tavella
04/21/2021, 8:29 PMAdam Powell
04/21/2021, 8:30 PMAdam Powell
04/21/2021, 8:31 PMHomeFragment
observes livedatasalex.tavella
04/21/2021, 10:30 PMprivate val addressStream = observeSessionAddress()
.distinctUntilChanged()
.onEach { addressState.value = it }
.shareIn(viewModelScope, SharingStarted.WhileSubscribed(), replay = 1)
private val addressState = MutableStateFlow<Address?>(null)
private val homeStream = addressState
.map { getHome(it.lat, it.lon) }
.shareIn(viewModelScope, SharingStarted.Lazily, replay = 1)
Will still keep trying to tune parameters tho