portaldacalheta.pt
  • Основен
  • Дизайн На Марката
  • Тенденции
  • Инструменти И Уроци
  • Технология
Подвижен

По-добри приложения за Android, използващи MVVM с изчистена архитектура



Ако не изберете правилната архитектура за вашия Android проект, трудно ще го поддържате, тъй като вашата кодова база се разраства и екипът ви се разширява.

функция за връзка в директивата angularjs

Това не е просто урок за Android MVVM. В тази статия ще комбинираме MVVM (Model-View-ViewModel или понякога стилизиран „модел ViewModel“) с Чиста архитектура . Ще видим как тази архитектура може да се използва за писане на отделен, тестваем и поддържаем код.



Защо MVVM с изчистена архитектура?

MVVM разделя вашия изглед (т.е. Activity s и Fragment s) от вашата бизнес логика. MVVM е достатъчен за малки проекти, но когато вашата кодова база стане огромна, вашите ViewModel започват да се подуват. Разделянето на отговорностите става трудно.



MVVM с чиста архитектура е доста добър в такива случаи. Това отива една стъпка по-напред в разделянето на отговорностите на вашата кодова база. Той ясно абстрахира логиката на действията, които могат да бъдат извършени във вашето приложение.



Забележка: Можете да комбинирате Clean Architecture и с архитектурата на модел-изглед-презентатор (MVP). Но тъй като Архитектурни компоненти на Android вече предоставя вграден ViewModel клас, отиваме с MVVM през MVP - не се изисква MVVM рамка!

Предимства на използването на чиста архитектура

  • Вашият код е дори по-лесен за тестване, отколкото с обикновен MVVM.
  • Вашият код е допълнително отделен (най-голямото предимство.)
  • Структурата на пакета е още по-лесна за навигация.
  • Проектът е дори по-лесен за поддръжка.
  • Вашият екип може да добавя нови функции още по-бързо.

Недостатъци на чистата архитектура

  • Той има малко стръмна крива на обучение. Начинът, по който всички слоеве работят заедно, може да отнеме известно време, за да се разбере, особено ако идвате от модели като прости MVVM или MVP.
  • Той добавя много допълнителни класове, така че не е идеален за проекти с ниска сложност.

Нашият поток от данни ще изглежда така:



Потокът от данни на MVVM с чиста архитектура. Данните преминават от изглед към ViewModel към домейн към хранилище за данни и след това към източник на данни (локален или отдалечен.)

Нашата бизнес логика е напълно отделена от нашия потребителски интерфейс. Това прави нашия код много лесен за поддръжка и тестване.



Примерът, който ще видим, е съвсем прост. Позволява на потребителите да създават нови публикации и да виждат списък с публикации, създадени от тях. В този пример не използвам никаква библиотека на трети страни (като Dagger, RxJava и др.) За по-голяма простота.

Слоевете на MVVM с изчистена архитектура

Кодът е разделен на три отделни слоя:



  1. Представителен слой
  2. Домейн слой
  3. Слой от данни

Ще влезем в повече подробности за всеки слой по-долу. Засега получената от нас структура на пакета изглежда така:

MVVM с чиста архитектурна структура на пакета.



Дори в рамките на архитектурата на приложението за Android, която използваме, има много начини за структуриране на йерархията на вашите файлове / папки. Харесва ми да групирам файлове на проекти въз основа на функции. Намирам го за спретнато и кратко. Можете свободно да изберете каквато структура на проекта ви подхожда.

Представителен слой

Това включва нашите Activity s, Fragment s и ViewModel s. An Activity трябва да бъде възможно най-тъп. Никога не поставяйте бизнес логиката си в Activity s.



An Activity ще говори с ViewModel и a ViewModel ще разговаря с домейн слоя за извършване на действия. A ViewModel никога не говори директно със слоя данни.

Тук предаваме UseCaseHandler и две UseCase s към нашите ViewModel. Скоро ще влезем в това по-подробно, но в тази архитектура a UseCase е действие, което определя как a ViewModel взаимодейства със слоя данни.

Ето как нашите Котлински код изглежда:

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) } }) } }

Домейнов слой

Домейновият слой съдържа всички случаи на употреба на вашето приложение. В този пример имаме UseCase, абстрактен клас. Всички наши UseCase ще разширят този клас.

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) } }

И UseCaseHandler обработва изпълнение на UseCase. Никога не трябва да блокираме потребителския интерфейс, когато извличаме данни от базата данни или от отдалечения ни сървър. Това е мястото, където решаваме да изпълним нашите UseCase на фонова нишка и получавате отговора на основната нишка.

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!! } } }

Както подсказва името му, GetPosts UseCase отговаря за получаването на всички публикации на потребител.

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 }

Целта на UseCase s е да бъде посредник между вашите ViewModel s и Repository s.

Да кажем, че в бъдеще сте решили да добавите функция за „редактиране на публикация“. Всичко, което трябва да направите, е да добавите нов EditPost UseCase и целият му код ще бъде напълно отделен и отделен от другите UseCase s. Всички сме го виждали много пъти: Въвеждат се нови функции и те неволно нарушават нещо в съществуващия код. Създаване на отделен UseCase помага изключително за избягването на това.

Разбира се, не можете да премахнете тази възможност на 100 процента, но със сигурност можете да я минимизирате. Това е, което разделя Clean Architecture от другите модели: Кодът е толкова отделен, че можете да третирате всеки слой като черна кутия.

Слоят данни

Това има всички хранилища, които домейн слоят може да използва. Този слой излага API на източник на данни на външни класове:

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 изпълнява PostDataSource. Той решава дали извличаме данни от локална база данни или от отдалечен сървър.

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 } }

Кодът е предимно обяснителен. Този клас има две променливи, localDataSource и remoteDataSource. Техният тип е PostDataSource, така че не ни интересува как всъщност са внедрени под капака.

От моя личен опит тази архитектура се оказа безценна. В едно от моите приложения започнах с Firebase на задната страна, което е чудесно за бързо изграждане на вашето приложение. Знаех, че в крайна сметка ще трябва да премина към собствения си сървър.

Когато го направих, трябваше само да променя изпълнението в RemoteDataSource. Не трябваше да докосвам друг клас дори след такава огромна промяна. Това е предимството на отделения код. Промяната на даден клас не трябва да засяга други части на кода ви.

по отношение на цялостния ефект на отрицателните лихвени проценти върху икономиката, икономисти

Някои от допълнителните класове, които имаме, са:

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 отговаря за асинхронното изпълнение на задачи, използвайки 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!! } } }

Това е нашата ViewModelFactory. Трябва да създадете това, за да предадете аргументи във вашия ViewModel конструктор.

Инжектиране на зависимост

Ще обясня инжектирането на зависимост с пример. Ако погледнете нашите PostDataRepository клас, той има две зависимости, LocalDataSource и RemoteDataSource. Използваме Injection клас, за да предостави тези зависимости на PostDataRepository клас.

Инжекционната зависимост има две основни предимства. Единият е, че можете да контролирате създаването на обекти от централно място, вместо да го разпространявате в цялата кодова база. Другото е, че това ще ни помогне да напишем модулни тестове за PostDataRepository защото сега можем просто да предадем подигравани версии на LocalDataSource и RemoteDataSource към PostDataRepository конструктор вместо действителни стойности.

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() }

Забележка: Предпочитам да използвам Dagger 2 за инжектиране на зависимости в сложни проекти. Но с изключително стръмната си крива на обучение, това е извън обхвата на тази статия. Така че, ако се интересувате от задълбочаване, горещо препоръчвам Въведение на Хари Вигнеш Джаяпалан в Кинжал 2 .

MVVM с изчистена архитектура: солидна комбинация

Нашата цел с този проект беше да разберем MVVM с чиста архитектура, затова пропуснахме няколко неща, които можете да опитате да подобрите допълнително:

  1. Използвайте LiveData или RxJava, за да премахнете обратните обаждания и да го направите малко по-спретнат.
  2. Използвайте състояния, за да представите своя потребителски интерфейс. (За това вижте тази невероятна беседа от Джейк Уортън .)
  3. Използвайте Dagger 2, за да инжектирате зависимости.

Това е една от най-добрите и мащабируеми архитектури за приложения за Android. Надявам се тази статия да ви е харесала и с нетърпение очаквам да чуя как сте използвали този подход в собствените си приложения!

Свързани: Xamarin Forms, MVVMCross и SkiaSharp: Светата Троица на разработката на приложения с различни платформи

Разбиране на основите

Какво представлява Android архитектурата?

Архитектурата на Android е начинът, по който структурирате вашия Android проект код, така че кодът ви да е мащабируем и лесен за поддръжка. Разработчиците отделят повече време за поддържане на проект, отколкото първоначално да го изградят, така че има смисъл да се следва правилен архитектурен модел.

Каква е разликата между MVC и MVVM?

В Android MVC се отнася до шаблона по подразбиране, където Activity действа като контролер, а XML файловете са изгледи. MVVM третира както класовете на активност, така и XML файловете като изгледи, а класовете ViewModel са мястото, където пишете вашата бизнес логика. Той напълно отделя потребителския интерфейс на приложението от неговата логика.

Каква е разликата между MVP и MVVM?

В MVP водещият знае за изгледа, а изгледът за водещия. Те си взаимодействат помежду си чрез интерфейс. В MVVM само изгледът знае за модела на изгледа. Изгледният модел няма представа за изгледа.

Кои са ключовите компоненти на архитектурата на Android?

Едната е разделянето на проблемите, т.е. вашата бизнес логика, потребителски интерфейс и модели на данни трябва да живеят на различни места. Друго е отделянето на кода: Всяка част от кода трябва да действа като черна кутия, така че промяната на каквото и да е в клас не би трябвало да има ефект върху друга част от вашата кодова база.

Какво е чиста архитектура?

„Чистата архитектура“ на Робърт С. Мартин е модел, който ви позволява да разчлените взаимодействието си с данни на по-прости обекти, наречени „случаи на използване“. Той е чудесен за писане на развързан код.

Какво представляват хранилищата на Android?

Повечето приложения запазват и извличат данни или от локално хранилище, или от отдалечен сървър. Репозиториите за Android са класове, които решават дали данните да идват от сървър или локално хранилище, отделяйки логиката ви за съхранение от външни класове.

Realm е най-доброто решение за база данни за Android

Подвижен

Realm е най-доброто решение за база данни за Android
Как да напиша автоматизирани тестове за iOS

Как да напиша автоматизирани тестове за iOS

Подвижен

Популярни Публикации
Fastlane: Автоматизация на iOS в круиз контрол
Fastlane: Автоматизация на iOS в круиз контрол
Ефективни комуникационни стратегии за дизайнери
Ефективни комуникационни стратегии за дизайнери
Изследване на контролирани алгоритми за машинно обучение
Изследване на контролирани алгоритми за машинно обучение
10-те UX доставки, които топ дизайнерите използват
10-те UX доставки, които топ дизайнерите използват
Тест за използваемост за преобразуване: Спрете да следвате тенденциите. Започнете с данните
Тест за използваемост за преобразуване: Спрете да следвате тенденциите. Започнете с данните
 
Кеширане през пролетта с EhCache анотации
Кеширане през пролетта с EhCache анотации
Разбиване на топ 5 мита за отдалечените работници
Разбиване на топ 5 мита за отдалечените работници
Неформално въведение в DOCX
Неформално въведение в DOCX
Проектиране за интерактивна среда и интелигентни пространства
Проектиране за интерактивна среда и интелигентни пространства
Най-добрите UX инструменти (с инфографика)
Най-добрите UX инструменти (с инфографика)
Популярни Публикации
  • някои от най-често срещаните насоки, издавани от уеб сайтовете при проектирането на сигурна парола, включват:
  • как да интегрирате външен API в wordpress
  • казус за капиталово бюджетиране с решение
  • Linux приложенията се разработват с помощта на
  • разнообразие в принципите на дизайна
Категории
  • Дизайн На Марката
  • Тенденции
  • Инструменти И Уроци
  • Технология
  • © 2022 | Всички Права Запазени

    portaldacalheta.pt