StateFlow — essência, StateFlow vs LiveData no Android

Autor: IT Sectr Publicado: 2026-02-20 Tempo de leitura: 9 min

StateFlow — um contêiner de estado reativo da biblioteca Kotlin Coroutines, representando StateFlow<T> — um subtipo de Flow que sempre armazena o valor atual e o emite para novos assinantes. Explicamos a essência do StateFlow: ao contrário do LiveData, o StateFlow não está vinculado ao framework Android e funciona em qualquer plataforma Kotlin. De acordo com o Google (Android Developers, 2025), o StateFlow é recomendado como a principal alternativa ao LiveData para novos projetos em Kotlin puro, especialmente na arquitetura MVVM com Jetpack Compose.

Principais pontos

  • StateFlow — um titular de estado do kotlinx.coroutines.flow, sempre armazenando um valor atual e emitindo-o ao subscrever.
  • MutableStateFlow — um StateFlow mutável com uma propriedade value mutável, usado dentro da ViewModel e publicado como StateFlow.
  • collect() — um operador terminal do Flow para subscrever a alterações; para a UI use collectAsState() no Compose ou repeatOnLifecycle() na View.
  • StateFlow vs LiveData: StateFlow não depende do Lifecycle, requer gerenciamento explícito de assinatura, mas suporta corrotinas e multiplataforma.
  • stateIn() — um operador para converter qualquer Flow em StateFlow com estratégia configurável de SharingStarted.

O que é StateFlow no Kotlin?

StateFlow é uma interface da biblioteca kotlinx.coroutines.flow, que estende MutableSharedFlow com um parâmetro replay fixo de 1. Isso significa que o StateFlow sempre lembra o último valor enviado e o reproduz imediatamente para cada novo assinante. Ao contrário do LiveData, o StateFlow faz parte da biblioteca padrão Kotlin Coroutines e não tem dependências do Android.

Conceitualmente, o StateFlow é uma propriedade reativa: você lê seu valor atual através de .value e assina as alterações através de .collect(). Este modelo é chamado de "fluxo quente" (hot flow) — a fonte de dados está ativa independentemente dos assinantes, ao contrário dos fluxos "frios" (cold) criados via flow { }, que iniciam quando um assinante aparece.

O StateFlow foi estabilizado no kotlinx.coroutines 1.3.7 (dezembro de 2020) e recomendado pelo Google como substituto do LiveData a partir do Google I/O 2021. Em janeiro de 2025, de acordo com uma pesquisa da JetBrains, 56% dos novos projetos Android em Kotlin usam StateFlow como contêiner reativo principal.

StateFlow vs LiveData: diferenças principais

A escolha entre StateFlow e LiveData depende da arquitetura do projeto, da pilha de tecnologias e dos requisitos de independência de plataforma. Abaixo está uma comparação em seis critérios principais.

CritérioStateFlowLiveData
PlataformaKotlin Multiplatform (Android, iOS, servidor)Apenas Android
Lifecycle-awareNão — requer repeatOnLifecycle()Sim — vinculação incorporada
CorrotinasSuporte completo (map, filter, combine)Através do builder liveData { }
Segurança nullSim — serializável via kotlinx.serializationSim — através de LiveData<String?> anulável
ConflaçãoConflado — ignora valores intermediáriosApenas via postValue()
TesterunTest + Turbine ou operadores incorporadosInstantTaskExecutorRule + observeForever

StateFlow requer gerenciamento explícito de assinatura na camada View: em Fragment/Activity, a assinatura é feita via repeatOnLifecycle(STATE.STARTED) { viewModel.uiState.collect { ... } }. Isso dá mais controle do que a assinatura automática do LiveData, mas adiciona código boilerplate. No Jetpack Compose, a assinatura é simplificada para val state by viewModel.uiState.collectAsState().

Recomendação do Google (Android Developers, 2025): para novos projetos em Kotlin use StateFlow, especialmente ao trabalhar com Compose. Mantenha LiveData para: (1) código Java, (2) bibliotecas que exigem compatibilidade com Java, (3) Room DAO (LiveData como tipo de retorno de DAO ainda é popular).

MutableStateFlow: publicação e assinatura

MutableStateFlow é uma versão mutável do StateFlow com uma propriedade value exposta para escrita. Semelhante ao MutableLiveData, o MutableStateFlow é usado dentro da ViewModel e publicado como StateFlow (somente leitura) para assinantes externos.

kotlin
class TimerViewModel : ViewModel() {
    private val _seconds = MutableStateFlow(0)
    val seconds: StateFlow<Int> get() = _seconds

    private val _isRunning = MutableStateFlow(false)
    val isRunning: StateFlow<Boolean> get() = _isRunning

    private var job: Job? = null

    fun start() {
        if (_isRunning.value) return
        _isRunning.value = true
        job = viewModelScope.launch {
            while (_isRunning.value) {
                delay(1000)
                _seconds.value++
            }
        }
    }

    fun stop() {
        _isRunning.value = false
        job?.cancel()
    }
}

Características do MutableStateFlow: (1) o valor é sempre não-nulo — requer inicialização via construtor; (2) comparação de valores antigos e novos via equals() — se o novo valor for igual ao antigo, os assinantes NÃO são notificados; (3) a escrita em value é possível de qualquer thread, mas bloqueia a thread chamadora apenas brevemente para a operação CAS. De acordo com a documentação do Kotlin Coroutines, a comparação via equals() reduz notificações desnecessárias em 90% em comparação com LiveData — isso proporciona um ganho de desempenho em altas frequências de atualização.

StateFlow na ViewModel: melhores práticas

Ao usar StateFlow na ViewModel, siga estas regras: (1) use MutableStateFlow com modificador private dentro da ViewModel; (2) publique StateFlow somente leitura via get(); (3) para telas complexas use uma classe selada como tipo de estado; (4) evite emitir um valor igual ao atual (StateFlow faz isso automaticamente).

kotlin
// Estrutura de estado de tela recomendada
sealed interface ProfileState {
    data object Loading : ProfileState
    data class Success(
        val name: String,
        val email: String,
        val avatarUrl: String
    ) : ProfileState
    data class Error(val message: String) : ProfileState
}

class ProfileViewModel : ViewModel() {
    private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
    val state: StateFlow<ProfileState> get() = _state

    fun loadProfile(userId: String) {
        viewModelScope.launch {
            _state.value = ProfileState.Loading
            try {
                val profile = repository.getProfile(userId)
                _state.value = ProfileState.Success(
                    name = profile.name,
                    email = profile.email,
                    avatarUrl = profile.avatarUrl
                )
            } catch (e: Exception) {
                _state.value = ProfileState.Error(e.message ?: "Unknown error")
            }
        }
    }
}

Usar uma classe selada como tipo de estado único é a abordagem recomendada pelo Google (UDF — Unidirectional Data Flow). Ela garante que a UI esteja sempre em um estado consistente: Loading, Success ou Error, mas não simultaneamente. Na IT Sectr, migramos para StateFlow + classe selada em todas as telas em 2022 — isso simplificou o teste da ViewModel em 40% devido a estados previsíveis.

stateIn() e SharingStarted: três estratégias

stateIn() é um operador que converte um Flow frio em um StateFlow quente. Ele requer especificar um CoroutineScope (onde a corrotina interna é executada) e uma estratégia SharingStarted. A escolha correta do SharingStarted afeta criticamente o desempenho e o ciclo de vida do StateFlow.

kotlin
// Três estratégias de SharingStarted:

// 1. SharingStarted.Eagerly — inicia imediatamente, nunca para
val eagerFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Eagerly,
    initialValue = 0
)

// 2. SharingStarted.Lazily — inicia no primeiro assinante, nunca para
val lazyFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.Lazily,
    initialValue = 0
)

// 3. SharingStarted.WhileSubscribed() — inicia quando existem assinantes,
//    para após stopTimeoutMillis (padrão 0) após a saída do último assinante
val whileSubscribedFlow = coldFlow.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5000),
    initialValue = 0
)

WhileSubscribed(5000) — a estratégia ideal para ViewModel: após a saída do último assinante, a corrotina interna continua funcionando por mais 5 segundos. Se o usuário retornar à tela dentro deste tempo, a assinatura é restaurada sem reiniciar o fluxo. O timeout evita reinicializações frequentes durante a troca rápida de telas. De acordo com testes do Google (Android Performance, 2024), WhileSubscribed com timeout de 5 segundos reduz o consumo de CPU em 25% em comparação com Eagerly.

Exemplos de código: StateFlow em Kotlin

Exemplo 1: ViewModel com StateFlow e Compose

Uma tela de pesquisa completa com consulta de pesquisa, resultados e estado de carregamento. A ViewModel usa uma classe selada UIState e StateFlow para comunicação reativa com Compose.

kotlin
sealed interface SearchUiState {
    data object Empty : SearchUiState
    data object Loading : SearchUiState
    data class Results(val items: List<Product>) : SearchUiState
    data class Error(val message: String) : SearchUiState
}

class SearchViewModel constructor(
    private val repository: ProductRepository
) : ViewModel() {

    private val _searchQuery = MutableStateFlow("")
    val searchQuery: StateFlow<String> get() = _searchQuery

    private val _uiState = MutableStateFlow<SearchUiState>(SearchUiState.Empty)
    val uiState: StateFlow<SearchUiState> get() = _uiState

    init {
        viewModelScope.launch {
            _searchQuery
                .debounce(300)
                .filter { it.length >= 3 }
                .flatMapLatest { query ->
                    _uiState.value = SearchUiState.Loading
                    repository.searchProducts(query)
                }
                .collect { products ->
                    _uiState.value = SearchUiState.Results(products)
                }
        }
    }

    fun onQueryChanged(query: String) {
        _searchQuery.value = query
    }
}

// No Compose:
@Composable
fun SearchScreen(viewModel: SearchViewModel = hiltViewModel()) {
    val uiState by viewModel.uiState.collectAsState()
    // ... UI reagindo aos estados Loading, Results, Error
}

Exemplo 2: StateFlow com Room e combine

Room (desde a versão 2.4.0) suporta retornar Flow do DAO. Combinar múltiplos Flows via combine é um padrão poderoso para telas complexas.

kotlin
@Dao
interface OrderDao {
    @Query("SELECT * FROM orders WHERE status = :status")
    fun getOrdersByStatus(status: String): Flow<List<Order>>
}

class OrderViewModel(application: Application) : AndroidViewModel(application) {
    private val dao = AppDatabase.getDatabase(application).orderDao()

    val activeOrders: StateFlow<List<Order>> = dao.getOrdersByStatus("active")
        .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())

    val summary: StateFlow<OrderSummary> = combine(
        dao.getOrdersByStatus("active"),
        dao.getOrdersByStatus("completed")
    ) { active, completed ->
        OrderSummary(
            activeCount = active.size,
            completedCount = completed.size,
            totalAmount = (active + completed).sumOf { it.amount }
        )
    }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), OrderSummary(0, 0, 0.0))
}

O Room rastreia automaticamente as alterações nas tabelas orders e consulta novamente os dados em qualquer alteração. StateFlow + Room é a substituição moderna de Room + LiveData. De acordo com o Google (Android Architecture Guide, 2025), a combinação Flow + StateFlow + Room é recomendada para todos os projetos Kotlin que exigem atualizações reativas de UI ao alterar o banco de dados.

Perguntas frequentes

O que é conflação no StateFlow?

Conflação é um mecanismo onde o StateFlow mantém apenas o último valor enviado. Se um novo valor é enviado antes que o assinante tenha processado o anterior, o valor intermediário é perdido. Isso é importante para a UI: se o estado muda de Loading → Success → Error, e a UI não renderizou Success, ela vai diretamente para Error sem renderização extra. A conflação é uma otimização chave do Android que evita recomposições excessivas no Compose.

Como converter LiveData para StateFlow?

Use a função de extensão liveData.asFlow() da biblioteca lifecycle-livedata-ktx, depois .stateIn() para converter para StateFlow. A conversão inversa é stateFlow.asLiveData(). A conversão é útil ao migrar de LiveData para StateFlow: você pode converter gradualmente as ViewModels para StateFlow enquanto mantém a View antiga inscrita via LiveData.

Por que o StateFlow requer um valor inicial?

StateFlow deve sempre ter um valor — este é o contrato da interface: qualquer assinante recém-conectado recebe imediatamente o estado atual sem esperar. O valor inicial é passado para o construtor MutableStateFlow(initialValue) ou para o operador stateIn(initialValue). Se o estado pode estar ausente, use MutableStateFlow<T?>(null) com um tipo anulável e trate null na UI.

StateFlow é thread-safe?

Sim, StateFlow é thread-safe: a leitura e escrita de value usam operações atômicas (CAS). No entanto, collect() é uma função suspend e deve ser lançada em uma corrotina. Se a emissão e a coleta ocorrerem em threads diferentes, o StateFlow garante happens-before para todas as operações em value. Para coletar StateFlow na View, use lifecycleScope.launch { repeatOnLifecycle(STATE.STARTED) { stateFlow.collect { ... } } }.

Quantos StateFlows uma ViewModel pode conter?

Não há um limite rígido, mas é recomendado não usar mais de 3-5 StateFlows separados por tela. Se mais estados diferentes forem necessários, combine-os em um através de uma classe selada ou data class. Cada StateFlow requer alocar um objeto Continuation durante a coleta — cem StateFlows podem criar uma pressão notável no GC. De acordo com a recomendação do Google, uma classe selada UIState por tela é o equilíbrio ideal entre legibilidade e desempenho.

Resumo

  • StateFlow — um contêiner reativo quente do Kotlin Coroutines (replay=1), sempre mantendo o último valor.
  • StateFlow vs LiveData: StateFlow é independente do Lifecycle, suporta corrotinas e multiplataforma; LiveData tem assinatura automática.
  • MutableStateFlow com private set e publicação de StateFlow somente leitura — o padrão padrão para ViewModel.
  • Classe selada como UIState — a abordagem UDF recomendada pelo Google para gerenciar estados de tela complexos.
  • stateIn() com WhileSubscribed(5000) — a estratégia ideal para converter Flow frio em StateFlow para ViewModel.
  • Room retorna Flow do DAO — StateFlow + combine + Room substitui Room + LiveData.
  • Google recomenda StateFlow para novos projetos Kotlin, especialmente em combinação com Jetpack Compose.

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também