portaldacalheta.pt
  • Principal
  • Ferramentas E Tutoriais
  • Noticias Do Mundo
  • Pessoas E Equipes
  • Ascensão Do Remoto
Móvel

Melhores aplicativos Android usando MVVM com arquitetura limpa



Se você não escolher a arquitetura certa para o seu Android projeto, você terá dificuldade em mantê-lo à medida que sua base de código crescer e sua equipe se expandir.

c corporação e diferenças corporativas

Este não é apenas um tutorial do Android MVVM. Neste artigo, vamos combinar MVVM (Model-View-ViewModel ou às vezes estilizado “o padrão ViewModel”) com Arquitetura Limpa . Veremos como essa arquitetura pode ser usada para escrever código desacoplado, testável e sustentável.



Por que MVVM com arquitetura limpa?

O MVVM separa sua visão (ou seja, Activity se Fragment s) de sua lógica de negócios. MVVM é suficiente para pequenos projetos, mas quando sua base de código se torna enorme, seus ViewModel s começam a inchar. Separar responsabilidades torna-se difícil.



MVVM com Clean Architecture é muito bom nesses casos. Ele vai um passo além na separação das responsabilidades de sua base de código. Ele abstrai claramente a lógica das ações que podem ser executadas em seu aplicativo.



Nota: Você também pode combinar Clean Architecture com a arquitetura model-view-presenter (MVP). Mas desde Componentes de arquitetura Android já fornece um ViewModel integrado | classe, vamos com MVVM sobre MVP - nenhuma estrutura MVVM necessária!

Vantagens de usar arquitetura limpa

  • Seu código é ainda mais facilmente testável do que com MVVM simples.
  • Seu código é ainda mais desacoplado (a maior vantagem).
  • A estrutura do pacote é ainda mais fácil de navegar.
  • O projeto é ainda mais fácil de manter.
  • Sua equipe pode adicionar novos recursos ainda mais rapidamente.

Desvantagens da Arquitetura Limpa

  • Tem uma curva de aprendizado ligeiramente íngreme. Pode levar algum tempo para entender como todas as camadas funcionam juntas, especialmente se você estiver vindo de padrões como MVVM ou MVP simples.
  • Ele adiciona muitas classes extras, por isso não é ideal para projetos de baixa complexidade.

Nosso fluxo de dados será semelhante a este:



O fluxo de dados do MVVM com Clean Architecture. Os dados fluem de View para ViewModel para Domínio para Repositório de dados e, em seguida, para uma fonte de dados (local ou remota).

Nossa lógica de negócios é completamente desacoplada de nossa IU. Isso torna nosso código muito fácil de manter e testar.



O exemplo que veremos é bastante simples. Ele permite aos usuários criar novas postagens e ver uma lista de postagens criadas por eles. Não estou usando nenhuma biblioteca de terceiros (como Dagger, RxJava, etc.) neste exemplo por uma questão de simplicidade.

As camadas do MVVM com arquitetura limpa

O código é dividido em três camadas separadas:



  1. Camada de apresentação
  2. Camada de Domínio
  3. Camada de Dados

Entraremos em mais detalhes sobre cada camada abaixo. Por enquanto, nossa estrutura de pacote resultante se parece com isto:

MVVM com estrutura de pacote Clean Architecture.



Mesmo dentro da arquitetura do aplicativo Android que estamos usando, há muitas maneiras de estruturar sua hierarquia de arquivos / pastas. Gosto de agrupar arquivos de projeto com base em recursos. Acho que é limpo e conciso. Você é livre para escolher a estrutura de projeto mais adequada para você.

A Camada de Apresentação

Isso inclui nossos Activity s, Fragment s e ViewModel s. An Activity deve ser o mais burro possível. Nunca coloque sua lógica de negócios em Activity s.



An Activity falará com um ViewModel e um ViewModel falará com a camada de domínio para realizar ações. A ViewModel nunca fala diretamente com a camada de dados.

Aqui estamos passando um UseCaseHandler e dois UseCase s para o nosso ViewModel. Entraremos em mais detalhes em breve, mas nesta arquitetura, um UseCase é uma ação que define como um ViewModel interage com a camada de dados.

Veja como nosso Código Kotlin parece:

class PostListViewModel( val useCaseHandler: UseCaseHandler, val getPosts: GetPosts, val savePost: SavePost): ViewModel() { fun getAllPosts(userId: Int, callback: PostDataSource.LoadPostsCallback) { val requestValue = GetPosts.RequestValues(userId) useCaseHandler.execute(getPosts, requestValue, object : UseCase.UseCaseCallback { override fun onSuccess(response: GetPosts.ResponseValue) { callback.onPostsLoaded(response.posts) } override fun onError(t: Throwable) { callback.onError(t) } }) } fun savePost(post: Post, callback: PostDataSource.SaveTaskCallback) { val requestValues = SavePost.RequestValues(post) useCaseHandler.execute(savePost, requestValues, object : UseCase.UseCaseCallback { override fun onSuccess(response: SavePost.ResponseValue) { callback.onSaveSuccess() } override fun onError(t: Throwable) { callback.onError(t) } }) } }

A Camada de Domínio

A camada de domínio contém todos os casos de uso de seu aplicativo. Neste exemplo, temos UseCase, uma classe abstrata. Todos os nossos UseCase s estenderão esta classe.

abstract class UseCase { var requestValues: Q? = null var useCaseCallback: UseCaseCallback

? = null internal fun run() { executeUseCase(requestValues) } protected abstract fun executeUseCase(requestValues: Q?) /** * Data passed to a request. */ interface RequestValues /** * Data received from a request. */ interface ResponseValue interface UseCaseCallback { fun onSuccess(response: R) fun onError(t: Throwable) } }

E UseCaseHandler lida com a execução de um UseCase. Nunca devemos bloquear a IU quando buscamos dados do banco de dados ou de nosso servidor remoto. Este é o lugar onde decidimos executar nosso UseCase em um thread de segundo plano e receber a resposta no thread principal.

class UseCaseHandler(private val mUseCaseScheduler: UseCaseScheduler) { fun execute( useCase: UseCase, values: T, callback: UseCase.UseCaseCallback) { useCase.requestValues = values useCase.useCaseCallback = UiCallbackWrapper(callback, this) mUseCaseScheduler.execute(Runnable { useCase.run() }) } private fun notifyResponse(response: V, useCaseCallback: UseCase.UseCaseCallback) { mUseCaseScheduler.notifyResponse(response, useCaseCallback) } private fun notifyError( useCaseCallback: UseCase.UseCaseCallback, t: Throwable) { mUseCaseScheduler.onError(useCaseCallback, t) } private class UiCallbackWrapper( private val mCallback: UseCase.UseCaseCallback, private val mUseCaseHandler: UseCaseHandler) : UseCase.UseCaseCallback { override fun onSuccess(response: V) { mUseCaseHandler.notifyResponse(response, mCallback) } override fun onError(t: Throwable) { mUseCaseHandler.notifyError(mCallback, t) } } companion object { private var INSTANCE: UseCaseHandler? = null fun getInstance(): UseCaseHandler { if (INSTANCE == null) { INSTANCE = UseCaseHandler(UseCaseThreadPoolScheduler()) } return INSTANCE!! } } }

Como o próprio nome indica, o GetPosts UseCase é responsável por obter todas as postagens de um usuário.

class GetPosts(private val mDataSource: PostDataSource) : UseCase() { protected override fun executeUseCase(requestValues: GetPosts.RequestValues?) { mDataSource.getPosts(requestValues?.userId ?: -1, object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded(posts: List) { val responseValue = ResponseValue(posts) useCaseCallback?.onSuccess(responseValue) } override fun onError(t: Throwable) { // Never use generic exceptions. Create proper exceptions. Since // our use case is different we will go with generic throwable useCaseCallback?.onError(Throwable('Data not found')) } }) } class RequestValues(val userId: Int) : UseCase.RequestValues class ResponseValue(val posts: List) : UseCase.ResponseValue }

O objetivo do UseCase s é ser um mediador entre seus ViewModel se Repository s.

Digamos que no futuro você decida adicionar um recurso de “editar postagem”. Tudo o que você precisa fazer é adicionar um novo EditPost UseCase e todo o seu código será completamente separado e desacoplado de outros UseCase s. Todos nós já vimos isso muitas vezes: novos recursos são introduzidos e eles inadvertidamente quebram algo no código preexistente. Criando um UseCase separado | ajuda imensamente a evitar isso.

classe javascript não está definida

Claro, você não pode eliminar essa possibilidade 100 por cento, mas com certeza pode minimizá-la. Isso é o que separa a Arquitetura Limpa de outros padrões: o código é tão desacoplado que você pode tratar cada camada como uma caixa preta.

A camada de dados

Isso contém todos os repositórios que a camada de domínio pode usar. Esta camada expõe uma API de fonte de dados para classes externas:

interface PostDataSource { interface LoadPostsCallback { fun onPostsLoaded(posts: List) fun onError(t: Throwable) } interface SaveTaskCallback { fun onSaveSuccess() fun onError(t: Throwable) } fun getPosts(userId: Int, callback: LoadPostsCallback) fun savePost(post: Post) }

PostDataRepository implementa PostDataSource. Ele decide se buscamos dados de um banco de dados local ou de um servidor remoto.

class PostDataRepository private constructor( private val localDataSource: PostDataSource, private val remoteDataSource: PostDataSource): PostDataSource { companion object { private var INSTANCE: PostDataRepository? = null fun getInstance(localDataSource: PostDataSource, remoteDataSource: PostDataSource): PostDataRepository { if (INSTANCE == null) { INSTANCE = PostDataRepository(localDataSource, remoteDataSource) } return INSTANCE!! } } var isCacheDirty = false override fun getPosts(userId: Int, callback: PostDataSource.LoadPostsCallback) { if (isCacheDirty) { getPostsFromServer(userId, callback) } else { localDataSource.getPosts(userId, object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded(posts: List) { refreshCache() callback.onPostsLoaded(posts) } override fun onError(t: Throwable) { getPostsFromServer(userId, callback) } }) } } override fun savePost(post: Post) { localDataSource.savePost(post) remoteDataSource.savePost(post) } private fun getPostsFromServer(userId: Int, callback: PostDataSource.LoadPostsCallback) { remoteDataSource.getPosts(userId, object : PostDataSource.LoadPostsCallback { override fun onPostsLoaded(posts: List) { refreshCache() refreshLocalDataSource(posts) callback.onPostsLoaded(posts) } override fun onError(t: Throwable) { callback.onError(t) } }) } private fun refreshLocalDataSource(posts: List) { posts.forEach { localDataSource.savePost(it) } } private fun refreshCache() { isCacheDirty = false } }

O código é basicamente autoexplicativo. Esta classe possui duas variáveis, localDataSource e remoteDataSource. Seu tipo é PostDataSource, então não nos importamos como eles são realmente implementados nos bastidores.

Em minha experiência pessoal, esta arquitetura provou ser inestimável. Em um de meus aplicativos, comecei com o Firebase no back-end, o que é ótimo para criar seu aplicativo rapidamente. Eu sabia que eventualmente teria que mudar para meu próprio servidor.

Quando eu fiz, tudo que tive que fazer foi mudar a implementação em RemoteDataSource. Eu não tive que tocar em nenhuma outra classe, mesmo depois de uma mudança tão grande. Essa é a vantagem do código desacoplado. Alterar qualquer classe não deve afetar outras partes do seu código.

Algumas das aulas extras que temos são:

interface UseCaseScheduler { fun execute(runnable: Runnable) fun notifyResponse(response: V, useCaseCallback: UseCase.UseCaseCallback) fun onError( useCaseCallback: UseCase.UseCaseCallback, t: Throwable) } class UseCaseThreadPoolScheduler : UseCaseScheduler { val POOL_SIZE = 2 val MAX_POOL_SIZE = 4 val TIMEOUT = 30 private val mHandler = Handler() internal var mThreadPoolExecutor: ThreadPoolExecutor init { mThreadPoolExecutor = ThreadPoolExecutor(POOL_SIZE, MAX_POOL_SIZE, TIMEOUT.toLong(), TimeUnit.SECONDS, ArrayBlockingQueue(POOL_SIZE)) } override fun execute(runnable: Runnable) { mThreadPoolExecutor.execute(runnable) } override fun notifyResponse(response: V, useCaseCallback: UseCase.UseCaseCallback) { mHandler.post { useCaseCallback.onSuccess(response) } } override fun onError( useCaseCallback: UseCase.UseCaseCallback, t: Throwable) { mHandler.post { useCaseCallback.onError(t) } } }

UseCaseThreadPoolScheduler é responsável por executar tarefas de forma assíncrona usando ThreadPoolExecuter.

class ViewModelFactory : ViewModelProvider.Factory { override fun create(modelClass: Class): T { if (modelClass == PostListViewModel::class.java) { return PostListViewModel( Injection.provideUseCaseHandler() , Injection.provideGetPosts(), Injection.provideSavePost()) as T } throw IllegalArgumentException('unknown model class $modelClass') } companion object { private var INSTANCE: ViewModelFactory? = null fun getInstance(): ViewModelFactory { if (INSTANCE == null) { INSTANCE = ViewModelFactory() } return INSTANCE!! } } }

Este é o nosso ViewModelFactory. Você tem que criar isso para passar argumentos em seu ViewModel construtor.

Injeção de dependência

Vou explicar a injeção de dependência com um exemplo. Se você olhar para nosso PostDataRepository classe, tem duas dependências, LocalDataSource e RemoteDataSource. Usamos o Injection classe para fornecer essas dependências ao PostDataRepository classe.

A dependência de injeção tem duas vantagens principais. Uma é que você controla a instanciação de objetos a partir de um local central, em vez de espalhá-la por toda a base de código. Outra é que isso nos ajudará a escrever testes de unidade para PostDataRepository porque agora podemos apenas passar versões simuladas de LocalDataSource e RemoteDataSource para PostDataRepository construtor em vez de valores reais.

object Injection { fun providePostDataRepository(): PostDataRepository { return PostDataRepository.getInstance(provideLocalDataSource(), provideRemoteDataSource()) } fun provideViewModelFactory() = ViewModelFactory.getInstance() fun provideLocalDataSource(): PostDataSource = LocalDataSource.getInstance() fun provideRemoteDataSource(): PostDataSource = RemoteDataSource.getInstance() fun provideGetPosts() = GetPosts(providePostDataRepository()) fun provideSavePost() = SavePost(providePostDataRepository()) fun provideUseCaseHandler() = UseCaseHandler.getInstance() }

Nota: Eu prefiro usar o Dagger 2 para injeção de dependência em projetos complexos. Mas com sua curva de aprendizado extremamente íngreme, está além do escopo deste artigo. Então, se você estiver interessado em se aprofundar, eu recomendo fortemente Introdução de Hari Vignesh Jayapalan a Dagger 2 .

MVVM com arquitetura limpa: uma combinação sólida

Nosso objetivo com este projeto era entender MVVM com Arquitetura Limpa, então pulamos algumas coisas que você pode tentar melhorar ainda mais:

  1. Use LiveData ou RxJava para remover callbacks e torná-lo um pouco mais organizado.
  2. Use estados para representar sua IU. (Para isso, verifique esta palestra incrível de Jake Wharton .)
  3. Use o Dagger 2 para injetar dependências.

Esta é uma das melhores e mais escalonáveis ​​arquiteturas para aplicativos Android. Espero que tenha gostado deste artigo e estou ansioso para saber como você usou essa abordagem em seus próprios aplicativos!

Relacionado: Xamarin Forms, MVVMCross e SkiaSharp: The Holy Trinity of Cross-Platform App Development

Compreender o básico

O que é arquitetura Android?

A arquitetura do Android é a maneira como você estrutura o código do seu projeto Android para que ele seja escalonável e fácil de manter. Os desenvolvedores gastam mais tempo mantendo um projeto do que inicialmente construindo-o, portanto, faz sentido seguir um padrão arquitetônico adequado.

Qual é a diferença entre MVC e MVVM?

No Android, MVC se refere ao padrão padrão em que uma Activity atua como um controlador e os arquivos XML são visualizações. O MVVM trata as classes Activity e arquivos XML como visualizações, e as classes ViewModel são onde você escreve sua lógica de negócios. Ele separa completamente a IU de um aplicativo de sua lógica.

Qual é a diferença entre MVP e MVVM?

No MVP, o apresentador conhece a visão e a visão conhece o apresentador. Eles interagem entre si por meio de uma interface. No MVVM, apenas a visão conhece o modelo de visão. O modelo de visualização não tem ideia sobre a visualização.

Quais são os principais componentes da arquitetura Android?

Um é a separação de interesses, ou seja, sua lógica de negócios, IU e modelos de dados devem estar em lugares diferentes. Outra é a dissociação do código: cada pedaço de código deve atuar como uma caixa preta para que qualquer alteração em uma classe não tenha nenhum efeito em outra parte de sua base de código.

O que é arquitetura limpa?

A 'Arquitetura Limpa' de Robert C. Martin é um padrão que permite dividir sua interação com os dados em entidades mais simples chamadas de 'casos de uso'. É ótimo para escrever código desacoplado.

O que são repositórios Android?

A maioria dos aplicativos salva e recupera dados do armazenamento local ou de um servidor remoto. Repositórios Android são classes que decidem se os dados devem vir de um servidor ou armazenamento local, desacoplando sua lógica de armazenamento de classes externas.

Tecnologia vestível: como e por que funciona

Design Ux

Tecnologia vestível: como e por que funciona
Mistério centenário da cachoeira vermelha da Antártica resolvido

Mistério centenário da cachoeira vermelha da Antártica resolvido

Mundo

Publicações Populares
Manter frameworks PHP MVC Slim com uma estrutura em camadas
Manter frameworks PHP MVC Slim com uma estrutura em camadas
Modelos de preços SaaS - exemplos de estratégia de preços e práticas recomendadas
Modelos de preços SaaS - exemplos de estratégia de preços e práticas recomendadas
A pessoa mais velha do mundo, a italiana Emma Morano, morre aos 117 anos
A pessoa mais velha do mundo, a italiana Emma Morano, morre aos 117 anos
Salesforce Einstein AI: um tutorial de API
Salesforce Einstein AI: um tutorial de API
UX em evolução - Design de produto experimental com um CXO
UX em evolução - Design de produto experimental com um CXO
 
Na adolescência, o estresse excessivo pode desencadear acidez
Na adolescência, o estresse excessivo pode desencadear acidez
Princípios de Design Organizacional e Otimização: Lições da Grande Recessão
Princípios de Design Organizacional e Otimização: Lições da Grande Recessão
Da política à família: como a indicação de Kamala Harris é relevante para a Índia
Da política à família: como a indicação de Kamala Harris é relevante para a Índia
Implementando um servidor Framebuffer remoto em Java
Implementando um servidor Framebuffer remoto em Java
Republicanos e democratas criticam Trump por reivindicar a vitória prematuramente
Republicanos e democratas criticam Trump por reivindicar a vitória prematuramente
Publicações Populares
  • desenvolver práticas recomendadas de sites para celular
  • s corp vs c corp. exemplo de imposto
  • aprenda c ou c ++
  • calculadora de comparação w2 vs 1099
  • ajuste e otimização de desempenho do sql server
Categorias
  • Ferramentas E Tutoriais
  • Noticias Do Mundo
  • Pessoas E Equipes
  • Ascensão Do Remoto
  • © 2022 | Todos Os Direitos Reservados

    portaldacalheta.pt