Модули функций и ядра
Проводите границы модулей
«Модули функций и ядра» — бесплатный урок Android Academy на CoddyKit. Это урок 2 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Android Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Android Academy содержит 4 уроков всего.
Два вида модулей
В большинстве модульных приложений Android код организуют в два основных вида модулей: модули feature и core, а сверху добавляют тонкий модуль :app.
- Модули feature содержат отдельную пользовательскую часть приложения — экран или сценарий.
- Модули core содержат общую инфраструктуру, используемую многими функциями.
В этом уроке Вы узнаете, как правильно проводить эти границы.
Анатомия модуля функции
Модуль функции, например :feature:profile, содержит всё необходимое одной функции: экраны Compose, её ViewModel и состояние интерфейса. Он устроен вертикально: ему принадлежит весь срез — от интерфейса до модели представления.
Он зависит от модулей core для общих компонентов, но не должен зависеть от других модулей функций.
// feature/profile/ProfileScreen.kt
@Composable
fun ProfileScreen(viewModel: ProfileViewModel = hiltViewModel()) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
when (state) {
is ProfileUiState.Loading -> CircularProgressIndicator()
is ProfileUiState.Success -> ProfileContent((state as ProfileUiState.Success).user)
is ProfileUiState.Error -> ErrorMessage()
}
}ViewModel функции
У каждой функции есть собственный ViewModel. Он получает данные через репозиторий из модуля core и предоставляет состояние интерфейса. Модулю функции не нужно знать, как загружаются данные, — ему достаточно контракта репозитория.
// feature/profile/ProfileViewModel.kt
@HiltViewModel
class ProfileViewModel @Inject constructor(
private val userRepository: UserRepository // from :core:data
) : ViewModel() {
val uiState: StateFlow<ProfileUiState> =
userRepository.observeUser()
.map { ProfileUiState.Success(it) }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), ProfileUiState.Loading)
}Анатомия модуля core
Модуль core устроен горизонтально: он предоставляет одну возможность, используемую разными функциями. Распространённые модули core:
:core:model— простые классы данных, общие для всего приложения:core:network— клиенты Retrofit/Ktor:core:database— настройка Room:core:data— репозитории, объединяющие сеть и базу данных:core:designsystem— тема и повторно используемые компонуемые элементы
// core/model/User.kt
data class User(
val id: String,
val name: String,
val avatarUrl: String
)Модуль системы дизайна
:core:designsystem — один из наиболее часто используемых модулей. Он содержит MaterialTheme, цветовые схемы, типографику и повторно используемые компонуемые элементы, например кнопки и карточки. Каждая функция использует одинаковый внешний вид без копирования кода.
// core/designsystem/AppTheme.kt
@Composable
fun AppTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
content: @Composable () -> Unit
) {
val colors = if (darkTheme) DarkColors else LightColors
MaterialTheme(
colorScheme = colors,
typography = AppTypography,
content = content
)
}Модуль данных владеет репозиториями
Модуль :core:data предоставляет интерфейсы репозиториев, от которых зависят функции, скрывая реализацию. Обычно он зависит от :core:network и :core:database, объединяя их в единый источник истины.
// core/data/UserRepository.kt
interface UserRepository {
fun observeUser(): Flow<User>
suspend fun refresh()
}
// core/data/OfflineFirstUserRepository.kt
internal class OfflineFirstUserRepository @Inject constructor(
private val api: UserApi, // :core:network
private val dao: UserDao // :core:database
) : UserRepository {
override fun observeUser(): Flow<User> = dao.observe().map { it.toUser() }
override suspend fun refresh() { dao.upsert(api.fetch().toEntity()) }
}Лёгкие model и designsystem
Модули самого низкого уровня должны зависеть от как можно меньшего числа компонентов. В идеале :core:model вообще не имеет зависимостей от Android — только обычные классы данных Kotlin. Благодаря этому его можно использовать где угодно, а сборка выполняется быстро.
Если бы :core:model начал зависеть от Retrofit или Room, каждый модуль, использующий ваши классы данных, также подтягивал бы эти тяжёлые библиотеки.
// core/model/build.gradle.kts
plugins {
id("myapp.jvm.library") // pure Kotlin, no Android
}
// No Retrofit, no Room, no Compose here — just data classesТонкий модуль :app
Модуль :app — это сборщик. В нём должно быть совсем немного логики: класс Application, единственный MainActivity, корневой NavHost и настройка внедрения зависимостей. Все настоящие экраны находятся в модулях функциональностей.
Тонкий модуль приложения означает, что большинство изменений происходит в функциональностях, поэтому модуль приложения редко пересобирается.
// app/MainActivity.kt
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AppTheme { // from :core:designsystem
AppNavHost() // routes into :feature:* screens
}
}
}
}Как провести хорошие границы
Как решить, что должно стать отдельным модулем? Вот несколько практических правил:
- Функциональность — это экран или сценарий, который пользователь может назвать (Главная, Профиль, Оформление заказа).
- Модуль ядра — это возможность, повторно используемая в двух и более функциональностях.
- Если двум функциональностям нужен один и тот же код, переместите его ниже, в модуль ядра.
- Если модуль делает слишком много несвязанных вещей, разделите его.
Необязательно: разделение API и реализации
В больших приложениях функциональность иногда разделяют на открытый :feature:profile:api (интерфейсы, маршруты навигации) и закрытый :feature:profile:impl (экраны, модели представления). Другие функциональности зависят только от небольшого api, но не от реализации.
Это продвинутый подход; для большинства приложений одного модуля на функциональность вполне достаточно. Просто имейте в виду, что для очень больших кодовых баз существует такой шаблон.
// Other features see only the contract, not the screens
// feature/home depends on :feature:profile:api
interface ProfileEntry {
val route: String
fun NavGraphBuilder.register(navController: NavController)
}Собираем граф
Вот как соединяются уровни в нашем примере. Обратите внимание: зависимости всегда направлены только вниз: app -> feature -> data -> network/database -> model.
// :app -> :feature:home, :feature:profile
// :feature:home -> :core:data, :core:designsystem
// :feature:profile -> :core:data, :core:designsystem
// :core:data -> :core:network, :core:database, :core:model
// :core:network -> :core:model
// :core:database -> :core:model
// :core:model -> (nothing)Быстрая проверка
Модулям :feature:home и :feature:profile нужно получать и кэшировать данные пользователя. Где должен находиться код репозитория?
Итоги: модули функциональностей и ядра
Вы научились разделять код на два уровня:
- Модули функциональностей — вертикальные срезы (экран + ViewModel + состояние интерфейса).
- Модули ядра — горизонтальные возможности (model, network, database, data, designsystem).
- Модуль
:appостаётся тонким и только собирает функциональности. - Общий код перемещается вниз, в ядро;
:core:modelне зависит от Android и остаётся лёгким.
Далее вы научитесь управлять зависимостями между этими модулями и поддерживать чистоту графа.
Часто задаваемые вопросы
Урок «Модули функций и ядра» бесплатный?
Да — полный текст урока «Модули функций и ядра» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Android Academy, подпишись на CoddyKit PRO. Курс Android Academy содержит 4 уроков всего.
Чему я научусь в уроке «Модули функций и ядра»?
Проводите границы модулей Ты практикуешь Android Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Android Academy?
Предыдущий опыт не требуется. Android Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 2 из 4.
Сколько времени занимает урок «Модули функций и ядра»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Android Academy?
Да. Каждый урок Android Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Зачем нужна модульность
- Модули функций и ядра
- Управление зависимостями модулей
- Навигация между модулями