Assincronia de dados em SwiftUI Views

Este post é o primeiro de uma sequência mais longa para mostrar algumas soluções interessantes para tratar de assincronia de dados em SwiftUI1. Basicamente, o problema é quando a informação que precisamos exibir não está disponível exatamente no momento em que a tela é exibida: um caso muito comum é quando a gente faz uma requisição dos dados de uma API REST no momento em que uma tela é exibida ao usuário; enquanto a resposta não chega (o que pode demorar vários décimos de segundo), a gente precisa exibir alguma coisa para o usuário — alguma mensagem de “loading…” ou uma animação indicando que a informação “já vai chegar”.

Primeiro passo: Como armazenar um dado assíncrono?

A primeira coisa é decidir, sob o ponto de vista de programação, como representar que uma informação ainda não está disponível. À primeira vista, talvez o uso de um Optional pareça perfeito, ou seja, enquanto a informação não chega a gente guarda nil. Mais ou menos assim:

struct AsyncNameView: View {
    let name: String?

    var body: some View {
        if let name {
            Text("Nome: \(name)")
        } else {
            Text("Carregando…")
        }
    }
}

Sinceramente eu não gosto dessa abordagem, por dois motivos: Primeiro que tem algumas informações que podem ser opcionais no servidor, ou seja, a própria resposta da requisição pode retornar nil (pense nas colunas de um banco de dados que podem ser NULL) — nesses casos a gente não consegue diferenciar o estado “a informação ainda não chegou” do estado “a informação já chegou e ela é nil”. Além disso, a gente fica limitado para representar outros estados; por exemplo, indicar que teve uma falha na requisição.

Para resolver isso, eu prefiro criar o meu próprio tipo. Como a ideia é armazenar “um entre vários estados possíveis”, nada melhor do que um enum, certo? Vamos à definição:

/// Armazena o estado de um dado assíncrono
public enum AsyncData<Data> {
    /// O dado ainda não está disponível
    case loading
    /// Houve uma falha na requisição e o dado nunca estará disponível
    case failed
    /// A informação está disponível
    case loaded(Data)
}

Se você está programando em Swift há pouco tempo, aqui pode ter duas novidades interessantes:

  1. A primeira é esse tal de <Data> logo depois do nome do tipo. O que é isso? Bom, aqui estou usando programação genérica2: em resumo, sempre que a gente usar o tipo AsyncData a gente vai precisar dizer qual é o tipo do dado associado a ele. Ou seja, a gente declara uma variável var name: AsyncData<String> para ter uma string associada, ou var age: AsyncData<Int> para ter um inteiro associado, ou mesmo var petNames: AsyncData<[String]> para ter um array de strings associado. Na verdade, qualquer tipo pode ser associado, inclusive as suas próprias classes e estruturas!
  2. Veja que o terceiro caso deste enum, .loaded, tem um valor associado3, indicado pelo termo “(Data)”. Basicamente significa que sempre que a gente usar o caso .loaded é necessário informar qual o valor a ser associado, que é do tipo Data (que, por sua vez, é indicado por nós quando a variável foi criada).

Finalmente a view!

Bom, é um bocado de informação até agora! Vamos então voltar ao nosso código-fonte para fazer uso desse enum:

struct AsyncNameView: View {
    let name: AsyncData<String>

    var body: some View {
        switch name {
        case .loading:
            Text("Carregando…")
                .opacity(0.5)
        case .failed:
            Text("Erro na comunicação!")
                .color(.red)
        case .loaded(let name):
            Text("Nome: \(name)")
        }
    }
}

Agora você já pode instanciar AsyncNameView especificando o estado da informação:

// Para gerar a view no estado “Carregando”, use:
AsyncNameView(name: .loading)
// Para gerar a view indicando que houve erro, use:
AsyncNameView(name: .failed)
// Para gerar a view com o dado carregado, use:
AsyncNameView(name: .loaded("Fulano de Tal"))

Eis as três versões na tela:

Bônus: Usando várias previews

Se, para testar este código, você começou pedindo para o Xcode criar uma view, então ele gerou uma pré-visualização automaticamente no fim do arquivo da seguinte forma:

#Preview {
    AsyncNameView()
}

Se for esse o caso, você já percebeu que nessa altura do campeonato a pré-visualização de AsyncNameView parou de funcionar. Isso porque a declaração da propriedade let name: AsyncData<String> agora requer que a gente especifique algum valor para essa propriedade, e esse código automático da #Preview tenta criar a view sem nenhum parâmetro.

Até aqui não tem muito mistério: É só alterar o código do bloco da #Preview para atribuir algum valor à propriedade que a pré-visualização volta a funcionar:

#Preview {
    AsyncNameView(name: .loaded("Fulano de Tal"))
}

Mas você sabia que é possível gerar várias previews ao mesmo tempo? É bem simples: Basta criar vários blocos de #Preview. Inclusive, pra manter a organização você pode dar um nome para cada bloco:

#Preview("Loaded") {
    AsyncNameView(name: .loaded("Fulano de Tal"))
}

#Preview("Loading") {
    AsyncNameView(name: .loading)
}

#Preview("Failed") {
    AsyncNameView(name: .failed)
}

Esse recurso é bem bacana porque você pode selecionar rapidamente qual versão de pré-visualização você quer ver:

Bacana! Agora, que tal seguir para a próxima parte desta sequência de posts para ver como generalizar essa view assíncrona para qualquer tipo de dados?

  1. Talvez seja mais preciso usar o termo dados atrasados, já que eles chegam “atrasados” em relação ao momento em que a tela é exibida; mas vou manter o termo “dados assíncronos” porque é um efeito natural de usar programação assíncrona em geral (tanto com async/await como com completion handlers). ↩︎
  2. https://docs.swift.org/swift-book/documentation/the-swift-programming-language/generics/ ↩︎
  3. https://docs.swift.org/swift-book/documentation/the-swift-programming-language/enumerations#Associated-Values ↩︎

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *


Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.