`[Flow + LiveData]` Hi, we have been adopting `Flo...
# android
a
[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:
Copy code
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?
l
Try
SharingStarted.WhileSubscribed()
instead of
SharingStarted.Lazily
a
Wouldn’t that trigger the upstream after resubscribing to the livedata?
l
I'm not sure I fully understand when you want
getHome
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 though
a
try using this operator from the latest androidx.lifecycle alphas: https://developer.android.com/reference/kotlin/androidx/lifecycle/package-summary#flowwithlifecycle
specifically, use it when you're observing the data from your UI contexts. It cancels the upstream subscription when you're out of lifecycle and restart when you come back in, which will in turn give you the expected behavior from
SharingStarted.WhileSubscribed
a
@Luke
getHome
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.
@Adam Powell as I understood, I could use from
Fragment
as:
Copy code
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?
a
viewLifecycle will never go back to
CREATED
from
DESTROYED
,
DESTROYED
is terminal and the fragment gets a new view lifecycle instance.
👍 2
Use
viewLifecycleScope.launch {
for the initial launch instead, and launch in
onViewCreated
a
Ok, but that wouldn’t trigger my upstream (
getHome
) every time on
onViewCreated
?
a
tune your usage of SharingStarted/distinctUntilChanged as needed from there; the code in the OP that observes the existing LiveData runs whenever onViewCreated runs already
👍 1
a
OP?
a
original post; the first post in this thread
👍🏽 1
👍 1
where
HomeFragment
observes livedatas
a
Actually, by doing this, the behavior is exactly as I want:
Copy code
private 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