A safer way to collect data streams from Android UIs

Android Kotlin . , (flows) , , , , ( , ) , .





, API Lifecycle.repeatOnLifecycle



Flow.flowWithLifecycle



.





.       .       .





API Flow<T> , . .





, , buffer, conflate, flowOn shareIn, API, CoroutineScope.launch, Flow<T>.launchIn LifecycleCoroutineScope.launchWhenX, , Job, , . API , , .





: β€” , , .





, , callbackFlow:





// Implementation of a cold flow backed by a Channel that sends Location updates
fun FusedLocationProviderClient.locationFlow() = callbackFlow<Location> {
    val callback = object : LocationCallback() {
        override fun onLocationResult(result: LocationResult?) {
            result ?: return
            try { offer(result.lastLocation) } catch(e: Exception) {}
        }
    }
    requestLocationUpdates(createLocationRequest(), callback, Looper.getMainLooper())
        .addOnFailureListener { e ->
            close(e) // in case of exception, close the Flow
        }
    // clean up when Flow collection ends
    awaitClose {
        removeLocationUpdates(callback)
    }
}
      
      



: callbackFlow



, 64 .





API , ! . :





class LocationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Collects from the flow when the View is at least STARTED and
        // SUSPENDS the collection when the lifecycle is STOPPED.
        // Collecting the flow cancels when the View is DESTROYED.
        lifecycleScope.launchWhenStarted {
            locationProvider.locationFlow().collect {
                // New location! Update the map
            } 
        }
        // Same issue with:
        // - lifecycleScope.launch { /* Collect from locationFlow() here */ }
        // - locationProvider.locationFlow().onEach { /* ... */ }.launchIn(lifecycleScope)
    }
}
      
      



lifecycleScope.launchWhenStarted



. , callbackFlow



. API lifecycleScope.launch



launchIn



, , ! .





API, , , callbackFlow



, . , - :





class LocationActivity : AppCompatActivity() {

    // Coroutine listening for Locations
    private var locationUpdatesJob: Job? = null

    override fun onStart() {
        super.onStart()
        locationUpdatesJob = lifecycleScope.launch {
            locationProvider.locationFlow().collect {
                // New location! Update the map
            } 
        }
    }

    override fun onStop() {
        // Stop collecting when the View goes to the background
        locationUpdatesJob?.cancel()
        super.onStop()
    }
}
      
      



, , ! Android, , . , !





Lifecycle.repeatOnLifecycle

, , , . 1) , 2) /, , , 3) ! , .





, API, , Lifecycle.repeatOnLifecycle



, lifecycle-runtime-ktx.





: API lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01



.





:





class LocationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Create a new coroutine since repeatOnLifecycle is a suspend function
        lifecycleScope.launch {
            // The block passed to repeatOnLifecycle is executed when the lifecycle
            // is at least STARTED and is cancelled when the lifecycle is STOPPED.
            // It automatically restarts the block when the lifecycle is STARTED again.
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Safely collect from locationFlow when the lifecycle is STARTED
                // and stops collection when the lifecycle is STOPPED
                locationProvider.locationFlow().collect {
                    // New location! Update the map
                }
            }
        }
    }
}
      
      



repeatOnLifecycle



β€” , Lifecycle.State , , state



, , , state



.





, , , , repeatOnLifecycle



. , API onCreate



onViewCreated



, . :





class LocationFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // ...
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                locationProvider.locationFlow().collect {
                    // New location! Update the map
                }
            }
        }
    }
}
      
      



: viewLifecycleOwner



. DialogFragments



, View. DialogFragments



lifecycleOwner



.





: API lifecycle:lifecycle-runtime-ktx:2.4.0-alpha01



.





!





repeatOnLifecycle



, , state



, , Lifecycle . : , repeatOnLifecycle



, , .





class LocationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Create a coroutine
        lifecycleScope.launch {
            
            lifecycle.repeatOnLifecycle(Lifecycle.State.RESUMED) {
                // Repeat when the lifecycle is RESUMED, cancel when PAUSED
            }

            // `lifecycle` is DESTROYED when the coroutine resumes. repeatOnLifecycle
            // suspends the execution of the coroutine until the lifecycle is DESTROYED.
        }
    }
}
      
      



, locationFlow



, lifecycleScope.launch



, , , .





repeatOnLifecycle



, , - .





Difference between using and not using the repeatOnLifecycle API
API repeatOnLifecycle

Flow.flowWithLifecycle

Flow.flowWithLifecycle



, . API repeatOnLifecycle



, , Lifecycle - .





class LocationActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Listen to one flow in a lifecycle-aware manner using flowWithLifecycle
        lifecycleScope.launch {
            locationProvider.locationFlow()
                .flowWithLifecycle(this, Lifecycle.State.STARTED)
                .collect {
                    // New location! Update the map
                }
        }
        
        // Listen to multiple flows
        lifecycleScope.launch {
            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
                // As collect is a suspend function, if you want to collect
                // multiple flows in parallel, you need to do so in 
                // different coroutines
                launch {
                    flow1.collect { /* Do something */ }   
                }
                
                launch {
                    flow2.collect { /* Do something */ }
                }
            }
        }
    }
}
      
      



: API Flow.flowOn(CoroutineContext)



, Flow.flowWithLifecycle



CoroutineContext



, , . , flowOn



, Flow.flowWithLifecycle



, . , callbackFlow



.





API, , , ! , , , . , , : , . , , .





API MutableStateFlow



MutableSharedFlow



subscriptionCount



, , subscriptionCount



. , , , . , , UiState



, ViewModel



UI StateFlow



. ! , ViewModel



.





Flow.stateIn Flow.shareIn . WhileSubscribed() , ! , Eagerly Lazily , CoroutineScope, .





: API, , . API , : , . , .





Flow Jetpack Compose

Flow.collectAsState Compose State<T> (UI) Compose



. Compose



UI, , . Compose



, (View).





Compose



Flow.flowWithLifecycle



:





@Composable
fun LocationScreen(locationFlow: Flow<Flow>) {

    val lifecycleOwner = LocalLifecycleOwner.current
    val locationFlowLifecycleAware = remember(locationFlow, lifecycleOwner) {
        locationFlow.flowWithLifecycle(lifecycleOwner.lifecycle, Lifecycle.State.STARTED)
    }

    val location by locationFlowLifecycleAware.collectAsState()

    // Current location, do something with it
}
      
      



, remember, , locationFlow



lifecycleOwner



, , .





Compose . LaunchedEffect, , . Lifecycle.repeatOnLifecycle



, , , State



.





LiveData

, API LiveData, ! LiveData



Lifecycle



, . API Lifecycle.repeatOnLifecycle



Flow.flowWithLifecycle



!





API LiveData



, Kotlin. API , LiveData



. , , Dispatcher



, . LiveData



, UI.





StateFlow

, , , , LiveData, , . , StateFlow ! StateFlow



.





.       .       .





API Lifecycle.repeatOnLifecycle



Flow.flowWithLifecycle



Android.






"Android Developer. Basic".








All Articles