Desenvolvimento de aplicações em SwiftUI. Parte 1: Fluxo de dados e Redux

Desenvolvimento de aplicações em SwiftUI. Parte 1: Fluxo de dados e Redux

Depois de participar da sessão sobre o Estado da União na WWDC 2019, decidi me aprofundar no SwiftUI. Passei muito tempo trabalhando com ele e agora comecei a desenvolver um aplicativo real que pode ser útil para uma ampla gama de usuários.

Chamei-o de MovieSwiftUI - este é um aplicativo para pesquisar filmes novos e antigos, bem como coletá-los em uma coleção usando APITMDB. Sempre adorei filmes e até criei uma empresa que trabalha nesta área, embora há muito tempo. A empresa dificilmente poderia ser chamada de legal, mas o aplicativo era!

Lembramos: para todos os leitores de "Habr" - um desconto de 10 rublos ao se inscrever em qualquer curso Skillbox usando o código promocional "Habr".

A Skillbox recomenda: Curso educacional on-line "Profissão desenvolvedor Java".

Então, o que o MovieSwiftUI pode fazer?

  • Interage com a API - quase qualquer aplicativo moderno faz isso.
  • Carrega dados assíncronos em solicitações e analisa JSON no modelo Swift usando Codificável.
  • Mostra imagens carregadas mediante solicitação e as armazena em cache.
  • Este aplicativo para iOS, iPadOS e macOS oferece a melhor experiência do usuário para usuários desses sistemas operacionais.
  • O usuário pode gerar dados e criar suas próprias listas de filmes. O aplicativo salva e restaura os dados do usuário.
  • Visualizações, componentes e modelos são claramente separados usando o padrão Redux. O fluxo de dados aqui é unidirecional. Ele pode ser totalmente armazenado em cache, restaurado e substituído.
  • O aplicativo usa os componentes básicos de SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, etc. Ele também fornece visualizações personalizadas, gestos, UI/UX.

Desenvolvimento de aplicações em SwiftUI. Parte 1: Fluxo de dados e Redux
Na verdade, a animação é suave, o GIF ficou um pouco irregular

Trabalhar no aplicativo me deu muita experiência e, no geral, foi uma experiência positiva. Consegui escrever um aplicativo totalmente funcional, em setembro irei melhorá-lo e publicá-lo na AppStore, simultaneamente ao lançamento do iOS 13.

Redux, BindableObject e EnvironmentObject

Desenvolvimento de aplicações em SwiftUI. Parte 1: Fluxo de dados e Redux

Trabalho com Redux há cerca de dois anos, então sou relativamente bem versado nele. Em particular, eu o uso no frontend para Reagir website, bem como para desenvolvimento de aplicativos nativos iOS (Swift) e Android (Kotlin).

Nunca me arrependi de ter escolhido Redux como arquitetura de fluxo de dados para construir um aplicativo SwiftUI. As partes mais desafiadoras ao usar Redux em um aplicativo UIKit são trabalhar com a loja e obter e recuperar dados e mapeá-los para suas visualizações/componentes. Para fazer isso, tive que criar uma espécie de biblioteca de conectores (usando ReSwift e ReKotlin). Funciona bem, mas bastante código. Infelizmente, (ainda) não é de código aberto.

Boas notícias! As únicas coisas com que se preocupar com o SwiftUI - se você planeja usar o Redux - são lojas, estados e redutores. A interação com a loja é totalmente feita pelo SwiftUI graças ao @EnvironmentObject. Portanto, a loja começa com um BindableObject.

Eu criei um pacote Swift simples, SwiftUIFlux, que fornece uso básico do Redux. No meu caso, faz parte do MovieSwiftUI. eu também escrevi um tutorial passo a passo, que o ajudará a usar este componente.

Como isso funciona?

final public class Store<State: FluxState>: BindableObject {
    public let willChange = PassthroughSubject<Void, Never>()
        
    private(set) public var state: State
    
    private func _dispatch(action: Action) {
        willChange.send()
        state = reducer(state, action)
    }
}

Cada vez que você aciona uma ação, você ativa a caixa de câmbio. Ele avaliará as ações de acordo com o estado atual da aplicação. Em seguida, ele retornará um novo estado modificado de acordo com o tipo de ação e os dados.

Bem, como store é um BindableObject, ele notificará o SwiftUI quando seu valor for alterado usando a propriedade willChange fornecida por PassthroughSubject. Isso ocorre porque o BindableObject deve fornecer um PublisherType, mas a implementação do protocolo é responsável por gerenciá-lo. No geral, esta é uma ferramenta muito poderosa da Apple. Assim, no próximo ciclo de renderização, o SwiftUI ajudará a renderizar o corpo das visualizações de acordo com a mudança de estado.

Na verdade, esse é todo o coração e a magia do SwiftUI. Agora, em qualquer visualização que se inscreva em um estado, a visualização será renderizada de acordo com os dados recebidos do estado e o que foi alterado.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
 
    var window: UIWindow?
 
 
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            let controller = UIHostingController(rootView: HomeView().environmentObject(store))
            window.rootViewController = controller
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}
 
struct CustomListCoverRow : View {
    @EnvironmentObject var store: Store<AppState>
    
    let movieId: Int
    var movie: Movie! {
        return store.state.moviesState.movies[movieId]
    }
    
    var body: some View {
        HStack(alignment: .center, spacing: 0) {
            Image(movie.poster)
        }.listRowInsets(EdgeInsets())
    }
}

O Store é injetado como um EnvironmentObject quando o aplicativo é iniciado e fica acessível em qualquer visualização usando @EnvironmentObject. Não há penalidade de desempenho porque as propriedades derivadas são rapidamente recuperadas ou calculadas a partir do estado do aplicativo.

O código acima altera a imagem se o pôster do filme mudar.

E isso é feito com apenas uma linha, com a ajuda da qual as visualizações são conectadas ao estado. Se você trabalhou com ReSwift no iOS ou mesmo connect com React, você entenderá a magia do SwiftUI.

Agora você pode tentar ativar a ação e publicar o novo estado. Aqui está um exemplo mais complexo.

struct CustomListDetail : View {
    @EnvironmentObject var store: Store<AppState>
 
    let listId: Int
    
    var list: CustomList {
        store.state.moviesState.customLists[listId]!
    }
    
    var movies: [Int] {
        list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state)
    }
    
    var body: some View {
        List {
            ForEach(movies) { movie in
                NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) {
                    MovieRow(movieId: movie, displayListImage: false)
                }
            }.onDelete { (index) in
               self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!]))
            }
        }
    }
}

No código acima, estou usando a ação .onDelete do SwiftUI para cada IP. Isso permite que a linha na lista exiba o deslizamento normal do iOS para excluir. Assim, quando o usuário toca no botão excluir, ele aciona a ação correspondente e remove o filme da lista.

Bem, como a propriedade list é derivada do estado BindableObject e é injetada como EnvironmentObject, o SwiftUI atualiza a lista porque ForEach está associado à propriedade calculada de filmes.

Aqui está parte do redutor MoviesState:

func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState {
    var state = state
    switch action {
    
    // other actions.
    
    case let action as MoviesActions.AddMovieToCustomList:
        state.customLists[action.list]?.movies.append(action.movie)
        
    case let action as MoviesActions.RemoveMovieFromCustomList:
        state.customLists[action.list]?.movies.removeAll{ $0 == action.movie }
        
    default:
        break
    }
    return state
}

O redutor é executado quando você despacha uma ação e retorna um novo estado, conforme mencionado acima.

Não entrarei em detalhes ainda - como o SwiftUI realmente sabe o que exibir. Para entender isso mais profundamente, vale visualizar sessão WWDC sobre fluxo de dados em SwiftUI. Também explica em detalhes por que e quando usar Estado, @Binding, ObjectBinding e EnvironmentObject.

A Skillbox recomenda:

Fonte: habr.com

Adicionar um comentário