Введение
В многомодульных приложениях Android существует проблема организации зависимости gradle. Каждая зависимость указывается отдельно. Примерно вот так
dependencies { implementation("androidx.core:core-ktx:1.13.1") implementation("androidx.appcompat:appcompat:1.7.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.8.4") implementation("androidx.activity:activity-compose:1.9.1") implementation(platform("androidx.compose:compose-bom:2024.08.00")) implementation("androidx.compose.ui:ui") implementation("androidx.compose.ui:ui-graphics") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.compose.material3:material3") implementation("androidx.navigation:navigation-compose:2.8.0") debugImplementation("androidx.compose.ui:ui-tooling") implementation("com.google.dagger:hilt-android:2.51.1") kapt("com.google.dagger:hilt-android-compiler:2.51.1") kapt("androidx.hilt:hilt-compiler:1.2.0") implementation(project(":mymodule")) ... }
Конечно в реальных проектах зависимостей может быть больше в разы. В данном примере я лишь хочу продемонстрировать Декларативный Подход в организации зависимостей.
Проблема в том что, из модуля в модуль приходится дублировать такой код. Конечно не забывая проверить, так как зависимости могут быть различными в каждом модуле.
Зависимости могут конфликтовать друг с другом или применяться различные версии. Что, очевидно, не хорошо.
Конечно есть решения, которые немного облегчают написание подобного кода.
Это описание зависимостей в toml файле или вынесение зависимостей в глобальные переменные с помощью Groovy или Kotlin Dsl.
После применения этих подходов код будет выглядеть вот так
dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.composeActivity) implementation(libs.composeBom) implementation(libs.androidx.ui) implementation(libs.androidx.ui.graphics) implementation(libs.androidx.ui.tooling.preview) implementation(libs.androidx.material3) implementation(libs.composeNavigation) debugImplementation(libs.androidx.ui.tooling) implementation(libs.hilt.android) kapt(libs.hilt.android.compiler) kapt(libs.androidx.hilt.compiler) implementation(project(":mymodule")) ... }
Но, по моему мнению, подобное решение не решает проблему процедурной организации зависимостей.
Стало лучше? Ответ — нет. Да, мы решили проблему конфликтов. И теперь зависимости вынесены в глобальные переменные. Но это не решило проблему дублирования кода. А также код у нас по-прежнему написан в процедурном стиле. Мы подключаем зависимости одну за одной.
Плюс каждый модуль получает абсолютную свободу в подключении зависимостей.
Давайте ее немного ограничим.
Пример будет показан с применением Kotlin Dsl, но это не принципиально. Аналогичного результата можно достичь и с помощью Groovy gradle.
Добавим extension в модуль Kotlin Dsl
import org.gradle.api.artifacts.Dependency import org.gradle.api.artifacts.dsl.DependencyHandler fun DependencyHandler.implementation(dependency: String) { add("implementation", dependency) } fun DependencyHandler.implementation(dependency: Dependency) { add("implementation", dependency) } fun DependencyHandler.kapt(dependency: String) { add("kapt", dependency) } fun DependencyHandler.testImplementation(dependency: String) { add("testImplementation", dependency) } fun DependencyHandler.androidTestImplementation(dependency: String) { add("androidTestImplementation", dependency) } fun DependencyHandler.androidTestImplementation(dependency: Dependency) { add("androidTestImplementation", dependency) } fun DependencyHandler.debugImplementation(dependency: String) { add("debugImplementation", dependency) }
Возможно, список extension не полный. Но вы можете легко его дополнить или изменить.
Теперь создадим extension зависимостей
fun DependencyHandler.Android() { implementation(AppDependencies.Android.androidxCooreKtx) implementation(AppDependencies.Android.androidxAppcompat) implementation(AppDependencies.Android.androidxLifecycleRuntimeKtx) } fun DependencyHandler.Compose() { implementation(AppDependencies.Compose.composeActivity) implementation(platform(AppDependencies.Compose.composeBom)) implementation(AppDependencies.Compose.composeUi) implementation(AppDependencies.Compose.composeUiGraphics) implementation(AppDependencies.Compose.composeUiToolingPreview) implementation(AppDependencies.Compose.composeMaterial3) implementation(AppDependencies.Compose.composeNavigation) debugImplementation(AppDependencies.Compose.composeUiTooling) } fun DependencyHandler.Hilt() { implementation(AppDependencies.Hilt.hiltAndroid) kapt(AppDependencies.Hilt.androidxHiltCompiler) kapt(AppDependencies.Hilt.hiltAndroidCompiler) } fun DependencyHandler.Project(projectName: String) { implementation(project(projectName)) } object AppDependencies { object Android { private const val coreKtx = "1.13.1" private const val appCompat = "1.7.0" private const val lifecycleRuntimeKtx = "2.8.4" const val androidxCooreKtx = "androidx.core:core-ktx:${coreKtx}" const val androidxAppcompat = "androidx.appcompat:appcompat:${appCompat}" const val androidxLifecycleRuntimeKtx = "androidx.lifecycle:lifecycle-runtime-ktx:${lifecycleRuntimeKtx}" } object Hilt { private const val hilt = "2.51.1" private const val hiltAndroidX = "1.2.0" const val hiltAndroid = "com.google.dagger:hilt-android:${hilt}" const val hiltAndroidCompiler = "com.google.dagger:hilt-android-compiler:${hilt}" const val androidxHiltCompiler = "androidx.hilt:hilt-compiler:${hiltAndroidX}" } object Compose { private const val composeBomVersion = "2024.08.00" private const val activityComposeVersion = "1.9.1" private const val composeNavigationVersion = "2.8.0" const val composeMaterial3 = "androidx.compose.material3:material3" const val composeUi = "androidx.compose.ui:ui" const val composeUiGraphics = "androidx.compose.ui:ui-graphics" const val composeUiTooling = "androidx.compose.ui:ui-tooling" const val composeUiToolingPreview = "androidx.compose.ui:ui-tooling-preview" const val composeBom = "androidx.compose:compose-bom:${composeBomVersion}" const val composeActivity = "androidx.activity:activity-compose:${activityComposeVersion}" const val composeNavigation = "androidx.navigation:navigation-compose:${composeNavigationVersion}" } }
В итоге gradle файл теперь выглядит так
dependencies { Android() Compose() Hilt() Project(":mymodule") }
Вывод
Такой подход позволяет комбинировать зависимости под нужды проекта и обладает рядом преимуществ:
-
есть возможность управлять зависимостями(implementation, kapt, androidTestImplementation и тд)
-
сокращает количество кода
-
логика зависимостей инкапсулируется в функциях расширения
-
возможность переиспользвания
-
модули подключают зависимости только те зависимости, которые относятся к предметной области(конечно, если запретить подключать зависимости напрямую)
-
декларативный подход
ссылка на оригинал статьи https://habr.com/ru/articles/845694/
Добавить комментарий