Migrating from LiveData to Kotlin's Flow

We needed LiveData back in 2017. The Observer pattern made life easier for us, but options like RxJava were too complex for beginners at the time. The Architecture Components team created LiveData: a very authoritative observable data store class developed for Android. It was simple to make it easy to get started, and for more complex reactive threading cases it was recommended to use RxJava, taking advantage of the integration between the two.





DeadData?

LiveData - Java-, . , Kotlin Flows. Flows () , Kotlin, Jetbrains; , Compose, .





Flows , ViewModel. , Android, .





, Flows , , . 





Flow: , —

LiveData : , Android. , , .





LiveData Flow:





#1:  

, :





Displaying the result of a single operation with a Mutable data holder (LiveData)
(Mutable) (LiveData)
<!-- Copyright 2020 Google LLC.	
   SPDX-License-Identifier: Apache-2.0 -->

class MyViewModel {
    private val _myUiState = MutableLiveData<Result<UiState>>(Result.Loading)
    val myUiState: LiveData<Result<UiState>> = _myUiState

    // Load data from a suspend fun and mutate state
    init {
        viewModelScope.launch { 
            val result = ...
            _myUiState.value = result
        }
    }
}
      
      



, StateFlow:





Displaying the result of a single operation with a modified data holder (StateFlow)
  (StateFlow)
class MyViewModel {
    private val _myUiState = MutableStateFlow<Result<UiState>>(Result.Loading)
    val myUiState: StateFlow<Result<UiState>> = _myUiState

    // Load data from a suspend fun and mutate state
    init {
        viewModelScope.launch { 
            val result = ...
            _myUiState.value = result
        }
    }
}
      
      



StateFlowSharedFlow ( Flow), LiveData:





  • .





  • .





  • ( ).





  • , .





StateFlow. , .





#2:

, .





LiveData liveData:





Displaying the result of a single operation (LiveData)
(LiveData)
class MyViewModel(...) : ViewModel() {
    val result: LiveData<Result<UiState>> = liveData {
        emit(Result.Loading)
        emit(repository.fetchItem())
    }
}
      
      



, UI-  - Result



, , Loading



, Success



Error



.





Flow , :





Displaying the result of a single operation (StateFlow)
(StateFlow)
class MyViewModel(...) : ViewModel() {
    val result: StateFlow<Result<UiState>> = flow {
        emit(repository.fetchItem())
    }.stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), // Or Lazily because it's a one-shot
        initialValue = Result.Loading
    )
}
      
      



stateIn



— Flow, StateFlow



. , .





#3:

, , ID , AuthManager



, Flow:





One-time data loading with parameters (LiveData)
(LiveData)

LiveData :





class MyViewModel(authManager..., repository...) : ViewModel() {
    private val userId: LiveData<String?> = 
        authManager.observeUser().map { user -> user.id }.asLiveData()

    val result: LiveData<Result<Item>> = userId.switchMap { newUserId ->
        liveData { emit(repository.fetchItem(newUserId)) }
    }
}
      
      



switchMap



— , , userId



.





, userId



LiveData, Flow LiveData.





class MyViewModel(authManager..., repository...) : ViewModel() {
    private val userId: Flow<UserId> = authManager.observeUser().map { user -> user.id }

    val result: LiveData<Result<Item>> = userId.mapLatest { newUserId ->
       repository.fetchItem(newUserId)
    }.asLiveData()
}
      
      



Flows :





One-time data loading with parameters (StateFlow)
(StateFlow)
class MyViewModel(authManager..., repository...) : ViewModel() {
    private val userId: Flow<UserId> = authManager.observeUser().map { user -> user.id }

    val result: StateFlow<Result<Item>> = userId.mapLatest { newUserId ->
        repository.fetchItem(newUserId)
    }.stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), 
        initialValue = Result.Loading
    )
}
      
      



, , transformLatest



emit



:





	val result = userId.transformLatest { newUserId ->
        emit(Result.LoadingData)
        emit(repository.fetchItem(newUserId))
    }.stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), 
        initialValue = Result.LoadingUser // Note the different Loading states
    )
      
      



#4:

. , , .





: fetchItem



observeItem



, Flow.





LiveData LiveData emitSource



:





Watching a stream with parameters (LiveData)
(LiveData)
class MyViewModel(authManager..., repository...) : ViewModel() {
    private val userId: LiveData<String?> = 
        authManager.observeUser().map { user -> user.id }.asLiveData()

    val result = userId.switchMap { newUserId ->
        repository.observeItem(newUserId).asLiveData()
    }
}
      
      



, , flatMapLatest LiveData:





class MyViewModel(authManager..., repository...) : ViewModel() {
    private val userId: Flow<String?> = 
        authManager.observeUser().map { user -> user?.id }

    val result: LiveData<Result<Item>> = userId.flatMapLatest { newUserId ->
        repository.observeItem(newUserId)
    }.asLiveData()
}
      
      



Flow , LiveData:





Monitoring flow with parameters (StateFlow)
(StateFlow)
class MyViewModel(authManager..., repository...) : ViewModel() {
    private val userId: Flow<String?> = 
        authManager.observeUser().map { user -> user?.id }

    val result: StateFlow<Result<Item>> = userId.flatMapLatest { newUserId ->
        repository.observeItem(newUserId)
    }.stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), 
        initialValue = Result.LoadingUser
    )
}
      
      



StateFlow , .





#5 : MediatorLiveData -> Flow.combine

MediatorLiveData ( LiveData) - , . MediatorLiveData:






val liveData1: LiveData<Int> = ...
val liveData2: LiveData<Int> = ...

val result = MediatorLiveData<Int>()

result.addSource(liveData1) { value ->
    result.setValue(liveData1.value ?: 0 + (liveData2.value ?: 0))
}
result.addSource(liveData2) { value ->
    result.setValue(liveData1.value ?: 0 + (liveData2.value ?: 0))
}
      
      



Flow :





val flow1: Flow<Int> = ...
val flow2: Flow<Int> = ...

val result = combine(flow1, flow2) { a, b -> a + b }
      
      



combineTransform zip.





StateFlow ( stateIn)

stateIn



StateFlow, . -, :





val result: StateFlow<Result<UiState>> = someFlow
    .stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), 
        initialValue = Result.Loading
    )
      
      



, , 5- started



, .





StateIn



3 ( ):





@param scope the coroutine scope in which sharing is started.
@param started the strategy that controls when sharing is started and stopped.
@param initialValue the initial value of the state flow.
This value is also used when the state flow is reset using the [SharingStarted.WhileSubscribed] strategy with the `replayExpirationMillis` parameter.
      
      



started



3 :





  • Lazily



    : , , , scope .





  • Eagerly



    : , scope .





  • WhileSubscribed



    : .





Lazily



Eagerly



. , , WhileSubscribed



, , .





WhileSubscribed

WhileSubscribed , . StateFlow, stateIn



, View, , ( ). , , , , .. , , .





WhileSubscribed



:





public fun WhileSubscribed(
    stopTimeoutMillis: Long = 0,
    replayExpirationMillis: Long = Long.MAX_VALUE
)
      
      



:





stopTimeoutMillis



( ) . ( ).





, , . — , .





liveData 5 , , . WhileSubscribed(5000)



:





class MyViewModel(...) : ViewModel() {
    val result = userId.mapLatest { newUserId ->
        repository.observeItem(newUserId)
    }.stateIn(
        scope = viewModelScope, 
        started = WhileSubscribed(5000), 
        initialValue = Result.Loading
    )
}
      
      



:





  • , , , , .





  • , , , .





  • , , , .





replayExpirationMillis



— ( ) ( shareIn



initialValue



stateIn



). Long.MAX_VALUE



( , ). .





StateFlow

, StateFlows ViewModel, . , , , .





, . :





  • Activity.lifecycleScope.launch



    : .





  • Fragment.lifecycleScope.launch



    : .





LaunchWhenStarted, launchWhenResumed...

launch, launchWhenX



, , lifecycleOwner



X, , lifecycleOwner



X. , , .





Collecting threads using launch / launchWhenX is insecure
launch/launchWhenX

, , , View. , , .





, , StateFlow, ; API.





lifecycle.repeatOnLifecycle

( lifecycle-runtime-ktx 2.4.0-alpha01) , : , .





Various methods of collecting a stream

, :





onCreateView(...) {
    viewLifecycleOwner.lifecycleScope.launch {
        viewLifecycleOwner.lifecycle.repeatOnLifecycle(STARTED) {
            myViewModel.myUiState.collect { ... }
        }
    }
}
      
      



, STARTED



, RESUMED



, STOPPED



. Android.





API repeatOnLifecycle



StateFlow .





StateFlow is exposed using WhileSubscribed (5000) and collected using repeatOnLifecycle (STARTED) 
StateFlow WhileSubscribed(5000) repeatOnLifecycle(STARTED) 

: StateFlow, Data Binding, launchWhenCreated



, repeatOnLifecycle



` , .





Data Binding Flows asLiveData()



, . , lifecycle-runtime-ktx 2.4.0



.





ViewModel :





  • StateFlow



    , WhileSubscribed



    , . []





  • repeatOnLifecycle



    . [].





, :






"Android Developer. Basic". , . , .








All Articles