Irbisa · Шпаргалка
Dependency Injection в Android
Hilt, Dagger, Koin, scope-ы, модули, тестируемость
Что такое внедрение зависимостей и как оно связано с Dependency Inversion, IoC и service locator?
class UserRepository(private val api: UserApi, private val dao: UserDao)class UserViewModel(private val repo: UserRepository) : ViewModel() // Composition root: the one place that knows how to build everythingclass AppContainer(context: Context) { private val retrofit = Retrofit.Builder().baseUrl("https://api.example.com/").build()…Внедрение через конструктор или через поля: что вы предпочитаете и почему Hilt в Activity и Fragment использует поля?
@AndroidEntryPointclass MainActivity : AppCompatActivity() { @Inject lateinit var analytics: AnalyticsTracker // field injection, not private private val vm: MainViewModel by viewModels() // built by Hilt's factory…Что делают
@Inject,@Module,@Providesи@Componentв Dagger и когда на самом деле строится граф?class UserRepository @Inject constructor(private val api: UserApi) @Moduleobject NetworkModule { @Provides @Singleton fun retrofit(): Retrofit =…Что такое Hilt и что он добавляет поверх Dagger 2?
@HiltAndroidAppclass MyApp : Application() @HiltViewModelclass HomeViewModel @Inject constructor( private val repo: UserRepository,…Когда использовать
@Bindsвместо@Providesи можно ли держать их в одном модуле?interface AnalyticsTracker { fun log(event: String) } class FirebaseTracker @Inject constructor( private val firebase: FirebaseAnalytics) : AnalyticsTracker { override fun log(event: String) = firebase.logEvent(event, null)…Как скоупы Hilt соответствуют времени жизни компонентов и почему не стоит скоупить всё подряд?
Memory Profiler показывает три живых экземпляра
OkHttpClient, хотя этот модуль предоставляет всего один. Что пошло не так?@Module@InstallIn(SingletonComponent::class)object NetworkModule { @Provides fun okHttp(auth: AuthInterceptor): OkHttpClient = OkHttpClient.Builder().addInterceptor(auth).build()} class FeedApiClient @Inject constructor(private val client: OkHttpClient)class ImageLoader @Inject constructor(private val client: OkHttpClient)class UploadService @Inject constructor(private val client: OkHttpClient)Найдите баг: экран товара добавляет позицию в корзину, а бейдж корзины в тулбаре остаётся на 0.
@ViewModelScopedclass CartState @Inject constructor() { private val _items = MutableStateFlow<List<Item>>(emptyList()) val items: StateFlow<List<Item>> = _items.asStateFlow() fun add(item: Item) = _items.update { it + item }} @HiltViewModelclass ProductViewModel @Inject constructor(private val cart: CartState) : ViewModel() { fun addToCart(item: Item) = cart.add(item)} @HiltViewModelclass CartBadgeViewModel @Inject constructor(cart: CartState) : ViewModel() { val count: Flow<Int> = cart.items.map { it.size }}Эта
@HiltViewModelзапрашивает@ActivityContext. Что произойдёт при сборке и какойContextей нужен?@HiltViewModelclass ProfileViewModel @Inject constructor( private val repo: ProfileRepository, @ActivityContext private val context: Context) : ViewModel() { private val prefs = context.getSharedPreferences("profile", Context.MODE_PRIVATE)}Зачем нужны
@Qualifierи мультибиндинги (@IntoSet,@IntoMap), с практическим примером?@Qualifier @Retention(AnnotationRetention.BINARY)annotation class AuthClient @Module@InstallIn(SingletonComponent::class)object NetworkModule {…Тест репозитория сжигает реальные секунды и падает по таймауту на медленном CI, потому что репозиторий жёстко использует
Dispatchers.IO. Как перестроить его через DI?@Qualifier @Retention(AnnotationRetention.BINARY) annotation class IoDispatcher@Qualifier @Retention(AnnotationRetention.BINARY) annotation class ApplicationScope @Module@InstallIn(SingletonComponent::class)object CoroutinesModule {…ViewModel экрана деталей нужен
orderId, с которым экран открыли. Как значение времени выполнения попадает во внедряемый класс?class OrderLoader @AssistedInject constructor( @Assisted private val orderId: String, // runtime value private val repo: OrderRepository // from the graph) { suspend fun load(): Order = repo.load(orderId)…Что дают
Provider<T>иLazy<T>по сравнению с прямым внедрениемT?class ReportGenerator @Inject constructor( private val db: Lazy<AppDatabase>, // dagger.Lazy: opened on first get(), then cached private val sessions: Provider<Session> // a fresh Session on every get()) { fun run(): Report { val dao = db.get().reports() // the database opens only if run() is called…Найдите баг: после обновления access-токена все запросы продолжают уходить со старым токеном, пока приложение не перезапустят.
@Module@InstallIn(SingletonComponent::class)object NetworkModule { @Provides fun authToken(store: TokenStore): AuthToken = store.current() @Provides @Singleton fun client(auth: AuthInterceptor): OkHttpClient = OkHttpClient.Builder().addInterceptor(auth).build()} class AuthInterceptor @Inject constructor(private val token: AuthToken) : Interceptor { override fun intercept(chain: Interceptor.Chain): Response = chain.proceed( chain.request().newBuilder().header("Authorization", "Bearer ${token.value}").build() )}Этот сетевой модуль падает с
[Dagger/DependencyCycle]. Где цикл и как его правильно разорвать?class AuthInterceptor @Inject constructor( private val tokens: TokenRefresher) : Interceptor { /* adds the Authorization header */ } class TokenRefresher @Inject constructor(private val api: AuthApi) @Module@InstallIn(SingletonComponent::class)object NetworkModule { @Provides @Singleton fun client(auth: AuthInterceptor): OkHttpClient = OkHttpClient.Builder().addInterceptor(auth).build() @Provides @Singleton fun authApi(client: OkHttpClient): AuthApi = Retrofit.Builder().baseUrl("https://api.example.com/").client(client).build().create(AuthApi::class.java)}Как внедрить зависимости в класс, которым Hilt не владеет, например
Workerиз WorkManager, ContentProvider или объект, который создаёт сторонняя библиотека?@HiltWorkerclass SyncWorker @AssistedInject constructor( @Assisted appContext: Context, @Assisted params: WorkerParameters, private val repo: FeedRepository) : CoroutineWorker(appContext, params) {…Сборка падает с
[Dagger/MissingBinding]дляPaymentGateway. Как читать эту ошибку и искать причину?// Request chain, trimmed:// PaymentGateway is injected at CheckoutRepository(gateway, ...)// CheckoutRepository is injected at CheckoutViewModel(repo, ...)// CheckoutViewModel is requested at App_HiltComponents.ViewModelC// Bottom-up: ViewModelC asked, via CheckoutViewModel -> CheckoutRepository.…Как подменить зависимости фейками в тестах, если приложение использует Hilt?
// Test source set: replaces the real module in every Hilt test@Module@TestInstallIn( components = [SingletonComponent::class], replaces = [NetworkModule::class])…Hilt или Koin: в чём реальная разница и когда что выбирать?
Вы никогда не писали класс
Hilt_MainActivity, но он появляется в стектрейсах. Что на самом деле генерируют Hilt и Dagger при сборке?// You write:class AnalyticsTracker @Inject constructor(client: OkHttpClient) @AndroidEntryPointclass MainActivity : AppCompatActivity() { @Inject lateinit var analytics: AnalyticsTracker…Сабкомпоненты или зависимости компонентов: чем они отличаются внутри и что подходит многомодульному приложению?
// Subcomponent: generated inside the parent, sees all its bindings@Subcomponent(modules = [CheckoutModule::class])interface CheckoutComponent { fun inject(fragment: CheckoutFragment) @Subcomponent.Factory interface Factory { fun create(): CheckoutComponent }}…После выхода и входа под другим пользователем приложение на мгновение показывает заказы предыдущего. При чём тут DI-граф?
@Scope @Retention(AnnotationRetention.RUNTIME) annotation class SessionScope @SessionScope@DefineComponent(parent = SingletonComponent::class)interface SessionComponent…В Gradle-проекте из сорока модулей где Hilt на самом деле собирает граф и как это влияет на время сборки?
Во что Dagger обходится большой Kotlin-кодовой базе и почему в 2026 году команды присматриваются к Metro и kotlin-inject?