Как использовать часть функциональности одного feature модуля в другом?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование функциональности feature-модуля в другом модуле
В современной Android-архитектуре с многомодульной структурой часто возникает необходимость разделить функционал на изолированные feature-модули, но при этом обеспечить возможность их взаимодействия. Вот основные подходы для решения этой задачи:
1. Создание общего модуля зависимостей
Самый чистый подход — выделение общих компонентов в отдельный модуль (обычно называемый core, shared или base), который затем подключается как зависимость в оба feature-модуля.
// В shared модуле
interface UserRepository {
suspend fun getUser(id: String): User
}
class UserRepositoryImpl @Inject constructor(
private val apiService: ApiService
) : UserRepository {
override suspend fun getUser(id: String): User {
return apiService.getUser(id)
}
}
2. Использование интерфейсов через Dagger/Hilt
Наиболее популярный подход в комбинации с DI-фреймворками:
// В feature-auth модуле
interface AuthProvider {
fun isUserLoggedIn(): Boolean
suspend fun getCurrentUser(): User?
}
// В feature-profile модуле
class ProfileViewModel @Inject constructor(
private val authProvider: AuthProvider
) : ViewModel() {
fun loadProfile() {
if (authProvider.isUserLoggedIn()) {
// Загружаем профиль
}
}
}
3. Dynamic Feature Modules с Play Core Library
Для динамических модулей используйте Play Feature Delivery:
// В базовом модуле
val featureInstallManager = FeatureInstallManagerFactory.create(context)
suspend fun isFeatureAvailable(moduleName: String): Boolean {
return featureInstallManager
.getFeatureModules()
.any { it.name == moduleName && it.installationState == INSTALLED }
}
// Запрос установки модуля
val request = SplitInstallRequest.newBuilder()
.addModule("feature_payments")
.build()
splitInstallManager.startInstall(request)
4. Публикация API через :api модули
Распространенный паттерн — разделение каждого feature-модуля на:
:feature-auth(implementation):feature-auth-api(только интерфейсы)
// В feature-auth-api/build.gradle.kts
plugins {
id("com.android.library")
}
dependencies {
// Минимальные зависимости, только то, что нужно для API
}
// В feature-profile/build.gradle.kts
dependencies {
implementation(project(":feature-auth-api"))
// НЕ implementation(project(":feature-auth"))
}
5. Использование Navigation Component для межмодульной навигации
Для навигации между feature-модулями:
// В базовом модуле создаем граф навигации
<navigation
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="@id/mainFragment">
<include app:graph="@navigation/feature_auth_graph" />
<include app:graph="@navigation/feature_profile_graph" />
</navigation>
// Динамическое включение графов
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController
val graph = navController.navInflater.inflate(R.navigation.main_nav)
graph.addAll(navController.navInflater.inflate(R.navigation.feature_auth))
navController.graph = graph
6. Event Bus / Shared Flow для коммуникации
Для слабосвязанной коммуникации между модулями:
// В core модуле
object AppEventBus {
private val _events = MutableSharedFlow<AppEvent>()
val events = _events.asSharedFlow()
suspend fun emit(event: AppEvent) {
_events.emit(event)
}
}
sealed class AppEvent {
data class UserLoggedIn(val user: User) : AppEvent()
object UserLoggedOut : AppEvent()
}
// В feature-auth (отправка)
AppEventBus.emit(AppEvent.UserLoggedIn(user))
// В feature-profile (получение)
AppEventBus.events
.filterIsInstance<AppEvent.UserLoggedIn>()
.collect { user ->
updateProfile(user)
}
7. Service Locator Pattern
Альтернатива DI для простых случаев:
object ServiceLocator {
private val services = mutableMapOf<Class<*>, Any>()
fun <T : Any> register(service: T, clazz: Class<T>) {
services[clazz] = service
}
@Suppress("UNCHECKED_CAST")
fun <T> get(clazz: Class<T>): T {
return services[clazz] as T
}
}
// Регистрация в feature-auth
ServiceLocator.register(AuthManager(), AuthManager::class.java)
// Использование в feature-profile
val authManager = ServiceLocator.get(AuthManager::class.java)
Критерии выбора подхода
-
Степень связанности:
- Для тесной интеграции → DI с интерфейсами
- Для слабой связи → Event Bus / Shared Flow
-
Динамичность:
- Статические модули → Простые зависимости
- Dynamic Features → Play Core Library
-
Масштаб проекта:
- Маленькие проекты → Service Locator
- Крупные проекты → Четкое разделение на api/implementation модули
-
Тестируемость:
- DI подходы легче мокать и тестировать
- Глобальные Event Bus сложнее тестировать изолированно
Рекомендация: Для большинства production-проектов оптимальным является комбинация Dagger/Hilt с выделением API модулей плюс Navigation Component для навигации. Это обеспечивает хороший баланс между изоляцией модулей и возможностью их взаимодействия, поддерживает тестируемость и соответствует принципам чистой архитектуры.