StateFlow vs SharedFlow in Android: Differences with Real Examples

Mobile Development

24 July, 2026

stateflow-vs-sharedflow
Suryaprakash Narsinghbhai Sharma

Suryaprakash Narsinghbhai Sharma

Sr Developer, Softices

When Kotlin Coroutines introduced Flow, it changed the way Android developers handle asynchronous data. Instead of relying on callbacks or older reactive APIs, developers gained a simpler, more structured way to work with data.

Later, Kotlin introduced StateFlow and SharedFlow as modern alternatives to many use cases previously handled by LiveData, BroadcastChannel, and ConflatedBroadcastChannel.

One of the most common questions Android developers ask is: 

// Should I use StateFlow or SharedFlow?

Although they belong to the same Flow family and have similar APIs, they solve very different problems. Choosing the wrong one can lead to bugs such as duplicate Snackbars, repeated navigation events, or inconsistent UI state.

In this article, we'll explore the differences between StateFlow and SharedFlow, understand when to use each one, and look at practical Android examples.

What is StateFlow in Android?

A StateFlow represents a state that always has a current value.

Think of it as a data holder that continuously exposes the latest UI state. Any new collector immediately receives the current value, making it ideal for representing data that the UI should always display.

Key Characteristics of StateFlow

  • Always requires an initial value
  • Holds only the latest value
  • Emits updates when the value changes
  • New collectors immediately receive the current value
  • Ideal for representing UI state

StateFlow Example in Android

private val _counter = MutableStateFlow(0)
  val counter: StateFlow<Int> = _counter
  fun increment() {
    _counter.value++
  }
  

Collecting the state:

lifecycleScope.launch {
    viewModel.counter.collect { count ->
    	textView.text = count.toString()
    }
  }
  

If the screen rotates, the collector instantly receives the latest counter value, ensuring the UI remains consistent. 

Real-World StateFlow Example

Imagine a Profile screen.

Loading… → User loaded → User updated
  

The screen should always display the latest user information.

data class User(
    val name: String,
    val age: Int
  )
   
  private val _user = MutableStateFlow<User?>(null)
  val user = _user.asStateFlow()
  

Whenever the repository updates the user:

_user.value = updatedUser
  

Even if another collector starts later such as after a configuration change, it immediately receives the latest user object.

This is exactly what StateFlow is designed for.

What is SharedFlow in Android?

A SharedFlow represents events instead of state.

Unlike StateFlow, it doesn't require an initial value. Instead of storing the latest UI state, it broadcasts events to active collectors.

Typical examples include:

  • Showing a Snackbar
  • Displaying a Toast
  • Navigation
  • Dialog events
  • Analytics events

Key Characteristics of SharedFlow

  • No initial value required
  • Can replay previous events (optional)
  • Can emit the same value multiple times
  • Perfect for one-time events

SharedFlow Example in Android

private val _events = MutableSharedFlow<String>()
  val events = _events.asSharedFlow()
   
  suspend fun showMessage() {
    _events.emit("Saved Successfully")
  }
  

Collecting:

lifecycleScope.launch {
    viewModel.events.collect {
      Toast.makeText(
        this@MainActivity,
        it,
        Toast.LENGTH_SHORT
      ).show()
    }
  } 
  

Real-World SharedFlow Example

Suppose the user taps the Save button.

User taps Save → Show Snackbar → Done
  

Showing a Snackbar is not state.

You don't want the Snackbar to appear again after the device rotates because the event has already been handled.

Instead, use SharedFlow.

private val _uiEvents = MutableSharedFlow<UiEvent>()
  val uiEvents = _uiEvents.asSharedFlow()
   
  sealed class UiEvent {
    data object ShowSuccess : UiEvent()
  }
  

Emit the event:

_uiEvents.emit(UiEvent.ShowSuccess)
  

Collect it:

viewModel.uiEvents.collect { event ->
    when (event) {
      UiEvent.ShowSuccess ->
        snackbarHostState.showSnackbar(
          "Saved Successfully"
        )
    }
  } 
  

The Snackbar appears only once, exactly as intended.

StateFlow vs SharedFlow: Understanding State vs Event

A simple analogy makes the difference easy to remember.

State

Imagine checking today's temperature.

Current temperature = 28°C
  

Anyone checking now should always see 28°C.

That's StateFlow.

Event

Now imagine a doorbell ringing.

Doorbell rang.
  

Someone arriving five minutes later shouldn't hear yesterday's ring.

That's SharedFlow.

StateFlow vs SharedFlow Comparison

Feature

StateFlow

SharedFlow

Holds state
Initial value required
New collectors receive latest value Optional (replay)
One-time events
Duplicate emissions Only when value changes Yes
Best for UI state
Best for navigation
Best for Snackbar
Best for loading state


When to Use StateFlow?

Use StateFlow whenever your UI should always reflect the latest value.

Common examples include:

  • UI state
  • User profile
  • Theme selection
  • Authentication state
  • Loading indicators
  • Network connectivity
  • Form inputs

Example:

data class LoginUiState(
    val email: String = "",
    val password: String = "",
    val isLoading: Boolean = false
  )
  

When to Use SharedFlow?

Use SharedFlow for actions that should happen only once.

Examples include:

  • Navigation
  • Snackbar
  • Toast
  • Dialog
  • Error messages
  • Logout events
  • Analytics tracking

Example:

sealed class UiEvent {
    data object NavigateHome : UiEvent()
    data class ShowError(val message: String) : UiEvent()
  }
  

Common Mistakes When Using StateFlow and SharedFlow

A frequent mistake is using StateFlow for Snackbar or Toast messages.

For example:

private val _message = MutableStateFlow("")
  

When the device rotates, the collector immediately receives the latest value again.

As a result:

Saved Successfully
  

appears a second time, even though the user didn't trigger another save.

The correct approach is:

private val _events = MutableSharedFlow<String>()
  

Since SharedFlow doesn't retain events by default, the Snackbar is displayed only once. 

Understanding SharedFlow Replay

One powerful feature of SharedFlow is its configurable replay behavior.

MutableSharedFlow<String>(
    replay = 1
  )
  

With replay = 1, the latest event is replayed to new collectors.

However, for one-time UI events, the most common configuration is:

MutableSharedFlow<UiEvent>(
    replay = 0,
    extraBufferCapacity = 1
  )
  

This ensures events are delivered without being replayed after configuration changes.

StateFlow or ShareFlow: Which One Should You Choose?

Whenever you're deciding between the two, ask yourself a simple question:

|| Is this state or an event?

  • I always need the latest value → StateFlow
  • This should happen only once → SharedFlow

 This simple rule covers the vast majority of Android use cases.

Best Practices for Using StateFlow and SharedFlow

  • Expose immutable flows using asStateFlow() and asSharedFlow().
  • Keep MutableStateFlow and MutableSharedFlow private inside the ViewModel.
  • Use StateFlow for UI state.
  • Use SharedFlow for one-time UI events.
  • Avoid using StateFlow for navigation, Snackbars, or Toast messages.
  • Use repeatOnLifecycle() for Views or collectAsStateWithLifecycle() in Jetpack Compose to make flow collection lifecycle-aware.
  • Model your UI using a single UiState object whenever possible to simplify state management.

StateFlow vs SharedFlow: Choosing the Right Flow for Your Android App

Although StateFlow and SharedFlow belong to the same Flow family, they serve fundamentally different purposes.

  • StateFlow is designed for representing state. It always holds the latest value and immediately delivers it to new collectors, making it perfect for UI state, loading indicators, user information, and other continuously changing data.
  • SharedFlow, on the other hand, is designed for events. It broadcasts one-time occurrences such as navigation, Snackbars, Toasts, dialogs, or analytics events without necessarily retaining them.

When in doubt, remember this simple rule:

// State is something you have. Event is something that happens.

Choosing the right flow for the right job will make your Android apps easier to maintain, easier to test, and more resilient as they grow.


Django

Previous

Django

Next

How Jewellery & Diamond Businesses Can Use AI for Grading, Pricing & Inventory

ai-for-jewellery-business

Frequently Asked Questions (FAQs)

StateFlow is used to represent a state and always holds the latest value, making it ideal for UI state management. SharedFlow is used for one-time events like navigation, Snackbars, and Toasts, and doesn't require an initial value.

Use StateFlow whenever your UI needs to observe and display the latest data, such as user information, loading states, authentication status, or form inputs. Since it always retains the current value, new collectors immediately receive the latest state.

SharedFlow doesn't retain events by default, so actions like showing a Snackbar, displaying a Toast, or navigating to another screen are triggered only once. This prevents duplicate events from occurring after configuration changes like screen rotations.

While it's technically possible, it's not recommended. Because StateFlow always re-emits its latest value to new collectors, navigation actions or Snackbar messages may be triggered again after configuration changes. SharedFlow is the better choice for these one-time events.

Keep MutableStateFlow and MutableSharedFlow private inside your ViewModel, expose immutable flows using asStateFlow() and asSharedFlow(), use StateFlow for UI state, SharedFlow for one-time events, and collect flows with repeatOnLifecycle() or collectAsStateWithLifecycle() to ensure lifecycle-aware behavior.