When building Android apps, you often deal with data that changes over time — like user input, API responses, or database updates. Handling these changes efficiently and reactively can be tricky. That’s where Kotlin Flows come in!
Kotlin Flows are a powerful tool for working with streams of data asynchronously. They’re part of Kotlin’s coroutines framework, designed to handle data that flows over time, making your code clean, efficient, and reactive.
What Are Flows?
Think of Flows as a stream of data that emits values over time. For example:
- A flow might emit values when a database gets updated.
- It could emit a new value every time a user enters text in a search bar.
Flows are cold by default, meaning they don’t start emitting values until they are collected. This makes them efficient since no work is done unless there’s a listener (collector).
Why Use Flows?
Simplify Asynchronous Tasks: Flows make it easier to handle data that updates over time without writing complex callback code.
Efficient & Reactive: They only do work when needed, saving resources.
Built for Coroutines: Since Flows are part of Kotlin’s coroutine library, they play well with other coroutine features like launch and async.
Flows in Android: Some Practical Examples
Imagine you want to fetch and display a list of items from a database that updates in real time. Here’s how you might use a Flow:
Steps:
- Create a Fake Database with a method to return a Flow of items.
- Use a Repository to provide the data from the fake database.
- Collect the data in your ViewModel and expose it as a StateFlow or LiveData.
- Observe the data in the Activity/Fragment
// Fake Database
object FakeDatabase {
// Simulate fetching a list of items from a database
fun getItems(): Flow<List<String>> = flow {
emit(emptyList()) // Emit an empty list initially
delay(1000) // Simulate a delay for fetching data
emit(listOf("Item 1", "Item 2", "Item 3", "Item 4")) // Emit the actual list
}
}
Repository Implementation:
class FakeRepository {
// Fetch items from the fake database
fun fetchItems(): Flow<List<String>> = FakeDatabase.getItems()
}
ViewModel Implementation:
class ItemViewModel : ViewModel() {
private val repository = FakeRepository()
// StateFlow to hold the list of items
private val _items = MutableStateFlow<List<String>>(emptyList())
val items: StateFlow<List<String>> get() = _items
init {
fetchItems()
}
private fun fetchItems() {
viewModelScope.launch {
repository.fetchItems().collectLatest { fetchedItems ->
_items.value = fetchedItems
}
}
}
}
Activity/Fragment Implementation
class MainActivity : AppCompatActivity() {
private val viewModel: ItemViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Collect items and update the RecyclerView adapter
lifecycleScope.launch {
viewModel.items.collectLatest { items ->
Log.d("LOG_TAG","Data from Database: $items")
}
}
}
}
Key Points in This Example:
- Fake Database: Provides data using a Flow. Simulates asynchronous fetching.
- Repository: Acts as a bridge between the database and ViewModel.
- ViewModel: Uses a StateFlow to expose the fetched data.
- Activity/Fragment: Collects the StateFlow and print it on logcat
Monitor network connection Using Flows
Steps:
- Use ConnectivityManager to monitor the network connection.
- Create a Flow to emit connection status updates.
- Collect the Flow in your activity or fragment and show the toast accordingly.
Code Implementation:
// Create a Flow to emit internet connectivity status
fun observeNetworkStatus(context: Context): Flow<Boolean> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(true) // Emit 'true' when connected
}
override fun onLost(network: Network) {
trySend(false) // Emit 'false' when disconnected
}
}
// Register network callback
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
} else {
val networkRequest = android.net.NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(networkRequest, networkCallback)
}
awaitClose {
// Unregister callback when Flow collection is canceled
connectivityManager.unregisterNetworkCallback(networkCallback)
}
}.distinctUntilChanged(
Usage in an Activity or Fragment:
// Observe network status and show toast
lifecycleScope.launch {
observeNetworkStatus(this@MainActivity).collectLatest { isConnected ->
val message = if (isConnected) {
"Internet Connected"
} else {
"Internet Disconnected"
}
Toast.makeText(this@MainActivity, message, Toast.LENGTH_SHORT).show()
}
}
Conclusion
Kotlin Flows make managing asynchronous data streams a breeze. They simplify your code, eliminate callback hell, and make your apps more reactive. Whether you’re handling user interactions or real-time updates, Flows are a go-to solution for modern Android development.
Start exploring Flows today and take your Android apps to the next level!
Feel free to reach out to me with any questions or opportunities at (aahsanaahmed26@gmail.com)
LinkedIn (https://www.linkedin.com/in/ahsan-ahmed-39544b246/)
Facebook (https://www.facebook.com/profile.php?id=100083917520174).
YouTube (https://www.youtube.com/@mobileappdevelopment4343)
Instagram (https://www.instagram.com/ahsanahmed_03/)
Top comments (0)