Рекомендации по архитектуре Android

На этой странице представлены несколько лучших практик и рекомендаций по архитектуре . Используйте их, чтобы улучшить качество, надежность и масштабируемость вашего приложения. Они также упрощают поддержку и тестирование вашего приложения.

Рекомендации ниже сгруппированы по темам. У каждого есть приоритет, который отражает, насколько сильно его рекомендует команда. Список приоритетов выглядит следующим образом:

  • Настоятельно рекомендуется: вам следует применять эту практику, если она не противоречит фундаментальному вашему подходу.
  • Рекомендуется: эта практика может улучшить ваше приложение.
  • Необязательно: эта практика может улучшить ваше приложение в определенных обстоятельствах.

Многоуровневая архитектура

Рекомендуемая нами многоуровневая архитектура способствует разделению задач. Он управляет пользовательским интерфейсом на основе моделей данных, соответствует принципу единого источника истины и принципам однонаправленного потока данных . Вот некоторые рекомендации по многоуровневой архитектуре:

Рекомендация Описание
Используйте четко определенный уровень данных .
Настоятельно рекомендуется
Уровень данных предоставляет данные приложения остальной части приложения и содержит большую часть бизнес-логики вашего приложения.
  • Вам следует создавать репозитории, даже если они содержат только один источник данных.
  • В небольших приложениях вы можете разместить типы слоев данных в пакете data или модуле.
Используйте четко определенный уровень пользовательского интерфейса .
Настоятельно рекомендуется
Уровень пользовательского интерфейса отображает данные приложения на экране и служит основной точкой взаимодействия с пользователем.
  • В небольших приложениях вы можете разместить типы слоев данных в ui или модуле пользовательского интерфейса.
Дополнительные рекомендации по созданию слоев пользовательского интерфейса можно найти здесь .
Уровень данных должен предоставлять данные приложения с помощью репозитория.
Настоятельно рекомендуется

Компоненты на уровне пользовательского интерфейса, такие как компонуемые объекты, действия или модели представления, не должны напрямую взаимодействовать с источником данных. Примеры источников данных:

  • Базы данных, хранилище данных, SharedPreferences, API Firebase.
  • Поставщики местоположения GPS.
  • Поставщики данных Bluetooth.
  • Поставщик статуса сетевого подключения.
Используйте сопрограммы и потоки .
Настоятельно рекомендуется
Используйте сопрограммы и потоки для взаимодействия между уровнями.

Больше лучших практик сопрограмм здесь .

Используйте слой домена .
Рекомендуется в больших приложениях
Используйте уровень предметной области , варианты использования, если вам нужно повторно использовать бизнес-логику, которая взаимодействует с уровнем данных в нескольких моделях представления, или вы хотите упростить сложность бизнес-логики конкретной модели представления.

Уровень пользовательского интерфейса

Роль уровня пользовательского интерфейса — отображать данные приложения на экране и служить основной точкой взаимодействия с пользователем. Вот несколько рекомендаций для уровня пользовательского интерфейса:

Рекомендация Описание
Следуйте однонаправленному потоку данных (UDF) .
Настоятельно рекомендуется
Следуйте принципам однонаправленного потока данных (UDF) , согласно которым модели представления предоставляют состояние пользовательского интерфейса с помощью шаблона наблюдателя и получают действия от пользовательского интерфейса посредством вызовов методов.
Используйте AAC ViewModels , если их преимущества применимы к вашему приложению.
Настоятельно рекомендуется
Используйте AAC ViewModels для обработки бизнес-логики и получения данных приложения, чтобы представить состояние пользовательского интерфейса в пользовательском интерфейсе (Compose или представления Android).

Дополнительные рекомендации по ViewModel см. здесь.

Ознакомьтесь с преимуществами ViewModels здесь.

Используйте коллекцию состояний пользовательского интерфейса с учетом жизненного цикла.
Настоятельно рекомендуется
Соберите состояние пользовательского интерфейса из пользовательского интерфейса с помощью соответствующего построителя сопрограмм, учитывающего жизненный цикл: repeatOnLifecycle в системе View и collectAsStateWithLifecycle в Jetpack Compose.

Узнайте больше о repeatOnLifecycle .

Узнайте больше о collectAsStateWithLifecycle .

Не отправляйте события из ViewModel в пользовательский интерфейс.
Настоятельно рекомендуется
Немедленно обработайте событие в ViewModel и вызовите обновление состояния с результатом обработки события. Подробнее о событиях пользовательского интерфейса здесь .
Используйте приложение с одним действием.
Рекомендуется
Используйте фрагменты навигации или компоновку навигации для навигации между экранами и создания глубоких ссылок на ваше приложение, если ваше приложение имеет более одного экрана.
Используйте Jetpack Compose .
Рекомендуется
Используйте Jetpack Compose для создания новых приложений для телефонов, планшетов, складных устройств и Wear OS.

В следующем фрагменте показано, как собирать состояние пользовательского интерфейса с учетом жизненного цикла:

Просмотры

class MyFragment : Fragment() {

    private val viewModel: MyViewModel by viewModel()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Process item
                }
            }
        }
    }
}

Сочинить

@Composable
fun MyScreen(
    viewModel: MyViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
}

Модель представления

Модели представления отвечают за предоставление состояния пользовательского интерфейса и доступ к уровню данных. Вот несколько лучших практик для ViewModels:

Рекомендация Описание
Модели ViewModels не должны зависеть от жизненного цикла Android.
Настоятельно рекомендуется
Модели ViewModels не должны содержать ссылки на какой-либо тип, связанный с жизненным циклом. Не передавайте Activity, Fragment, Context или Resources в качестве зависимости. Если чему-то нужен Context в ViewModel, вам следует тщательно оценить, находится ли он на правильном уровне.
Используйте сопрограммы и потоки .
Настоятельно рекомендуется

ViewModel взаимодействует со слоями данных или домена, используя:

  • Kotlin потоки для получения данных приложения,
  • suspend функции для выполнения действий с использованием viewModelScope .
Используйте ViewModels на уровне экрана.
Настоятельно рекомендуется

Не используйте ViewModels в повторно используемых частях пользовательского интерфейса. Вы должны использовать ViewModels в:

  • Составные элементы на уровне экрана,
  • Действия/фрагменты в представлениях,
  • Пункты назначения или графики при использовании Jetpack Navigation .
Используйте классы-держатели простого состояния в повторно используемых компонентах пользовательского интерфейса.
Настоятельно рекомендуется
Используйте классы-держатели простого состояния для решения сложных задач в повторно используемых компонентах пользовательского интерфейса. Поступая таким образом, государство можно поднять и контролировать извне.
Не используйте AndroidViewModel .
Рекомендуется
Используйте класс ViewModel , а не AndroidViewModel . Класс Application не следует использовать в ViewModel. Вместо этого переместите зависимость в пользовательский интерфейс или на уровень данных.
Раскройте состояние пользовательского интерфейса.
Рекомендуется
Модели ViewModels должны предоставлять данные пользовательскому интерфейсу через одно свойство, называемое uiState . Если пользовательский интерфейс отображает несколько несвязанных фрагментов данных, виртуальная машина может предоставить несколько свойств состояния пользовательского интерфейса .
  • Вам следует сделать uiState StateFlow .
  • Вам следует создать uiState с помощью оператора stateIn с политикой WhileSubscribed(5000) (пример), если данные поступают в виде потока данных из других уровней иерархии.
  • В более простых случаях, когда нет потоков данных, поступающих с уровня данных, допустимо использовать MutableStateFlow , представленный как неизменяемый StateFlow (пример) .
  • Вы можете выбрать ${Screen}UiState как класс данных, который может содержать данные, ошибки и сигналы загрузки. Этот класс также может быть запечатанным классом, если разные состояния являются исключительными.

В следующем фрагменте показано, как предоставить состояние пользовательского интерфейса из ViewModel:

@HiltViewModel
class BookmarksViewModel @Inject constructor(
    newsRepository: NewsRepository
) : ViewModel() {

    val feedState: StateFlow<NewsFeedUiState> =
        newsRepository
            .getNewsResourcesStream()
            .mapToFeedState(savedNewsResourcesState)
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = NewsFeedUiState.Loading
            )

    // ...
}

Жизненный цикл

Ниже приведены некоторые рекомендации по работе с жизненным циклом Android :

Рекомендация Описание
Не переопределяйте методы жизненного цикла в действиях или фрагментах.
Настоятельно рекомендуется
Не переопределяйте методы жизненного цикла, такие как onResume в действиях или фрагментах. Вместо этого используйте LifecycleObserver . Если приложению необходимо выполнить работу, когда жизненный цикл достигает определенного Lifecycle.State , используйте API repeatOnLifecycle .

В следующем фрагменте показано, как выполнять операции в определенном состоянии жизненного цикла:

Просмотры

class MyFragment: Fragment() {
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
            override fun onResume(owner: LifecycleOwner) {
                // ...
            }
            override fun onPause(owner: LifecycleOwner) {
                // ...
            }
        }
    }
}

Сочинить

@Composable
fun MyApp() {

    val lifecycleOwner = LocalLifecycleOwner.current
    DisposableEffect(lifecycleOwner, ...) {
        val lifecycleObserver = object : DefaultLifecycleObserver {
            override fun onStop(owner: LifecycleOwner) {
                // ...
            }
        }

        lifecycleOwner.lifecycle.addObserver(lifecycleObserver)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(lifecycleObserver)
        }
    }
}

Обработка зависимостей

Существует несколько рекомендаций, которые следует соблюдать при управлении зависимостями между компонентами:

Рекомендация Описание
Используйте внедрение зависимостей .
Настоятельно рекомендуется
Используйте лучшие практики внедрения зависимостей , в основном внедрение конструктора, когда это возможно.
При необходимости перейдите к компоненту.
Настоятельно рекомендуется
Область действия — контейнер зависимостей , если тип содержит изменяемые данные, которые необходимо совместно использовать, или тип требует больших затрат на инициализацию и широко используется в приложении.
Используйте Хилт .
Рекомендуется
Используйте Hilt или ручное внедрение зависимостей в простых приложениях. Используйте Hilt , если ваш проект достаточно сложен. Например, если у вас есть:
  • Несколько экранов с ViewModels — интеграция
  • Использование WorkManager — интеграция
  • Расширенное использование навигации, например ViewModels, привязанных к навигационному графу, — интеграция.

Тестирование

Ниже приведены некоторые рекомендации по тестированию :

Рекомендация Описание
Знайте, что тестировать .
Настоятельно рекомендуется

Если проект не такой же простой, как приложение hello world, вам следует протестировать его, как минимум, с помощью:

  • Модульное тестирование моделей представлений, включая потоки.
  • Объекты уровня данных модульного теста. То есть репозитории и источники данных.
  • Тесты навигации пользовательского интерфейса, которые полезны в качестве регрессионных тестов в CI.
Предпочитайте подделки издевательствам.
Настоятельно рекомендуется
Подробнее читайте в разделе «Использование тестовых двойников» в документации Android .
Тестирование StateFlows.
Настоятельно рекомендуется
При тестировании StateFlow :

Для получения дополнительной информации ознакомьтесь с руководством «Что тестировать в Android DAC» .

Модели

Вам следует соблюдать следующие рекомендации при разработке моделей в ваших приложениях:

Рекомендация Описание
Создавайте модели для каждого слоя в сложных приложениях.
Рекомендуется

В сложных приложениях создавайте новые модели в разных слоях или компонентах, когда это имеет смысл. Рассмотрим следующие примеры:

  • Удаленный источник данных может сопоставить модель, которую он получает по сети, с более простым классом, содержащим только те данные, которые нужны приложению.
  • Репозитории могут сопоставлять модели DAO с более простыми классами данных, используя только ту информацию, которая необходима уровню пользовательского интерфейса.
  • ViewModel может включать модели уровня данных в классы UiState .

Соглашения об именах

При присвоении имени вашей кодовой базе вы должны учитывать следующие рекомендации:

Рекомендация Описание
Методы именования.
Необязательный
Методы должны быть глагольной группой. Например, makePayment() .
Именование свойств.
Необязательный
Свойства должны быть существительными. Например, inProgressTopicSelection .
Именование потоков данных.
Необязательный
Когда класс предоставляет поток Flow, LiveData или любой другой поток, соглашение об именах — get{model}Stream() . Например, getAuthorStream(): Flow<Author> Если функция возвращает список моделей, имя модели должно быть во множественном числе: getAuthorsStream(): Flow<List<Author>>
Именование реализаций интерфейсов.
Необязательный
Имена реализаций интерфейсов должны быть осмысленными. Используйте префикс Default , если лучшее имя не найдено. Например, для интерфейса NewsRepository у вас может быть OfflineFirstNewsRepository или InMemoryNewsRepository . Если вы не можете найти хорошее имя, используйте DefaultNewsRepository . Поддельные реализации должны иметь префикс Fake , как в FakeAuthorsRepository .