Tratamento de Morte de Processo em Aplicativos Móveis: Restauração de Estado no DroidKaigi 2024

Um aplicativo de conferência robusto precisa preservar o contexto do usuário mesmo após a interrupção abrupta do processo. O projeto DroidKaigi 2024 implementa uma estratégia abrangente para restaurar o estado da interface do usuário em plataformas Android e iOS. Este guia técnico explora as arquiteturas e padrões utilizados para garantir uma experiência contínua.

Visão Geral da Arquitetura de Restauração

A aplicação segue uma abordagem de gerenciamento de estado em camadas, separando claramente o estado recuperável do estado temporário da interface. Os módulos centrais são:

  • Android: Módulos como core/data e core/main são responsáveis pela persistência e gerenciamento de ciclo de vida.
  • iOS: Camadas de modelo em Sources/Model/Entity e reducers definem estados serailizáveis.
  • Código Compartilhado (KMP): Contratos de estado e repositórios em core/model e core/common asseguram consistência.

Implementação na Plataforma Android (Jetpack Compose)

No lado Android, a persistência é gerenciada principalmente através do SavedStateHandle dentro dos ViewModels e da API rememberSaveable nos Composables.

Um ViewModel de exemplo demonstra como salvar e restaurar o ID de uma sessão selecionada:

class ProgramDetailViewModel(
    private val stateHolder: SavedStateHandle,
    private val repository: ProgramRepository
) : ViewModel() {
    val currentSession = stateHolder.getStateFlow<String?>("active_session_id", null)

    fun onSessionSelected(id: String) {
        stateHolder["active_session_id"] = id
        // Dispara carregamento de detalhes...
    }
}

Na camada de interface, componentes anotados com @Composable utilizam rememberSaveable para preservar estados de UI, como a expansão de uma seção:

@Composable
fun SessionAgendaView(
    agendaId: String,
    detailViewModel: ProgramDetailViewModel = hiltViewModel()
) {
    val expandedItems = rememberSaveable { mutableListOf<String>() }
    // Lógica do componente...
}

Implementação na Plataforma iOS (SwiftUI & Combine)

No iOS, o estado é modelado como structs Equatable e codificado para persistência. O framework Combine gerencia as atualizações.

Uma struct de estado para a agenda pode ser definida assim:

struct AgendaViewState: Codable, Equatable {
    var chosenDay: EventDay
    var focusedSessionKey: String?
    var activeFilters: [FilterCategory]
    
    var serialized: Data? {
        try? JSONEncoder().encode(self)
    }
    
    static func deserialize(from data: Data) -> AgendaViewState? {
        try? JSONDecoder().decode(AgendaViewState.self, from: data)
    }
}

O reducer da aplicação centraliza as ações de salvar e restaurar o estado usando UserDefaults:

struct ApplicationReducer {
    static func process(action: ApplicationAction, state: inout ApplicationState) -> Effect<ApplicationAction> {
        switch action {
        case .didLaunch:
            if let persisted = UserDefaults.standard.data(forKey: "agenda_view") {
                state.agendaView = AgendaViewState.deserialize(from: persisted) ?? .initial
            }
            return .none
        case .agendaViewChanged(let viewState):
            UserDefaults.standard.set(viewState.serialized, forKey: "agenda_view")
            state.agendaView = viewState
            return .none
        }
    }
}

Sincronização de Estado Entre Plataformas

O uso do Kotlin Multiplatform permite compartilhar lógica de validação e modelos de domínio. A sincronização é garantida por:

  1. Contratos Compartilhados: Interfaces e expectativas em código comum definem o comportamneto.
  2. Implementações Específicas: Cada plataforma provê sua implementação de persistência (Android: SavedStateHandle, iOS: UserDefaults).
  3. Camada de Repositório: Atua como ponto único de acesso, garantindo atomicidade nas operações de leitura/escrita do estado.

[Diagrama Conceitual: Fluxo de Sincronização de Estado entre Plataformas]

Estratégias de Teste e Validação

Suites de testes automatizados verificam a robustez da restauração em cenários desafiadores:

  • Encerramento limpo do aplicativo.
  • Terminação do prcoesso pelo sistema (memória baixa).
  • Modo offline e perda de conectividade.
  • Conflitos em cenários de sincronização em múltiplos dispositivos.

Um teste típico verifica se o estado é corretamente mantido após a "morte" e "recriação" do ViewModel:

@Test
fun session_focus_persists_across_recreation() = runTest {
    val repository = FakeProgramRepository()
    val stateStorage = SavedStateHandle()
    
    val initialModel = ProgramDetailViewModel(stateStorage, repository)
    initialModel.onSessionSelected("session_abc")
    
    // Simular ciclo de vida, recriando o ViewModel com o mesmo stateStorage
    val recreatedModel = ProgramDetailViewModel(stateStorage, repository)
    
    assertThat(recreatedModel.currentSession.value).isEqualTo("session_abc")
}

Resumo de Padrões Recomendados

Tipo de Estado Padrão Recomendado Exemplo no Projeto
Estado efêmero da UI (ex: aberta/fechada) Android: rememberSaveable | iOS: @State expandedItems em SessionAgendaView
Estado de rota/tela ViewModel + SavedStateHandle (Android) | Reducer + UserDefaults (iOS) ProgramDetailViewModel (Android)
Estado global do aplicativo Singleton Repository + Armazenamento persistente ApplicationReducer (iOS) com UserDefaults
Estado compartilhado entre plataformas Kotlin Multiplatform (definição comum) + implementações nativas Modelos em core/model e lógica em core/common

Tags: SwiftUI Jetpack Compose Kotlin Multiplatform Android State Restoration iOS App Lifecycle

Publicado em 6-4 22:19 por Thomas