Maryam Aiyari
Appcent
Published in
3 min readJan 30, 2024

--

Exploring Kotlin Flows: A Guide to Flow, StateFlow, and SharedFlow with Retrofit

Introduction:

Kotlin’s coroutines library brings a powerful set of tools for handling asynchronous programming and reactive patterns. Among them, Flow, StateFlow, and SharedFlow stand out as essential components when dealing with asynchronous data streams. In this article, we’ll delve into these concepts and explore how to leverage them using a list of items retrieved from Retrofit.

Understanding Flows:

1. Flow:

Flow is a fundamental building block in Kotlin’s coroutine library. It represents a cold asynchronous stream of values that can be emitted over time. To create a basic Flow, we use the flow builder function. Let’s consider a simple example of a Flow that emits a list of items fetched from a remote API using Retrofit.

data class Item(val id: Long, val name: String)

interface ApiService {
suspend fun getItems(): List<Item>
}

val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()

val apiService = retrofit.create(ApiService::class.java)

fun fetchItems(): Flow<List<Item>> = flow {
val items = apiService.getItems()
emit(items)
}

In this example, fetchItems is a Flow that fetches a list of items using Retrofit and emits the result.

2. StateFlow:

StateFlow is an extension of Flow designed for representing a single state value that changes over time. It’s particularly useful for observing and reacting to changes in state. Let’s extend our example to use StateFlow.

class ItemViewModel {
private val _itemsStateFlow = MutableStateFlow<List<Item>>(emptyList())
val itemsStateFlow: StateFlow<List<Item>> get() = _itemsStateFlow

fun fetchItems() {
viewModelScope.launch {
val items = apiService.getItems()
_itemsStateFlow.value = items
}
}
}

In this example, ItemViewModel has a MutableStateFlow named _itemsStateFlow that represents the current state of the list of items. The fetchItems function fetches the items and updates the state using the value property of the MutableStateFlow.

3. SharedFlow:

SharedFlow is designed to be shared among multiple collectors, making it suitable for scenarios where you want to broadcast values to multiple subscribers. Let’s modify our example to use SharedFlow.

class ItemRepository {
private val _itemsSharedFlow = MutableSharedFlow<List<Item>>(replay = 1)
val itemsSharedFlow: SharedFlow<List<Item>> get() = _itemsSharedFlow

suspend fun fetchItems() {
val items = apiService.getItems()
_itemsSharedFlow.emit(items)
}
}

In this example, ItemRepository uses a MutableSharedFlow named _itemsSharedFlow. The fetchItems function emits the list of items.

* since SharedFlow is hot, it can broadcast the values even if there are no active collectors.

Consuming Flows in UI:

Now that we have explored the basics of Flow, StateFlow, and SharedFlow, let’s see how to consume these in a UI using, for instance, an Android application.

class ItemListActivity : AppCompatActivity() {
private val itemViewModel: ItemViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item_list)

itemViewModel.itemsStateFlow.observe(this) { items ->
// Update UI with the latest list of items
updateUI(items)
}

// Trigger the fetch operation
itemViewModel.fetchItems()
}

private fun updateUI(items: List<Item>) {
// Update the UI with the list of items
}
}

In this Android example, ItemListActivity observes the itemsStateFlow from ItemViewModel. When the state changes, the UI is updated accordingly.

Choosing Between StateFlow and SharedFlow:

When deciding between StateFlow(cold) and SharedFlow(hot), consider the nature of the data you’re working with and the requirements of your application. If your use case involves managing a single, well-defined state that UI components observe and react to, StateFlow is an excellent choice. On the other hand, if you need to broadcast updates to multiple consumers or if late subscribers should receive the most recent values, SharedFlow might be more suitable.

Conclusion:

In conclusion, Kotlin Flows, including Flow, StateFlow, and SharedFlow, provide a powerful and expressive way to handle asynchronous data streams. When combined with libraries like Retrofit, they offer a seamless and concise approach to dealing with remote API calls and updating UI components. Understanding and incorporating these concepts into your Kotlin projects can lead to more efficient and maintainable asynchronous code.

--

--