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.Lazilyalex.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.WhileSubscribedalex.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 onViewCreatedalex.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