تزریق وابستگی (Dependency Injection) توی اندروید در نگاه اول ساده به نظر میرسه، مخصوصاً وقتی از Hilt استفاده میکنی. اما به محض اینکه برنامهات رشد میکنه و به یه معماری چند-ماژولی و بزرگ تبدیل میشه، تازه میفهمی که Hilt یه سری قابلیت داره که فوقالعاده قدرتمندن.
توی این مقاله، 7 تا مفهوم پیشرفته Hilt رو بررسی میکنیم که توسعهدهندگان حرفهای ازشون برای ساختن اپهای مقیاسپذیر و بزرگ استفاده میکنن.
1. قدرت @EntryPoint
@EntryPoint بهت این امکان رو میده که از کلاسهایی که Hilt نمیتونه مستقیماً بهشون تزریق کنه (مثل موارد زیر)، به Hilt دسترسی پیدا کنی:
- WorkManager Worker
- BroadcastReceiver
- ContentProvider
- Services
این ویژگی به شدت مهم و کاربردیه!
مثال: تزریق Repository توی یک Worker
@EntryPoint
@InstallIn(SingletonComponent::class)
interface WorkerEntryPoint {
fun repo(): MyRepository
}
// نحوه استفاده
val entryPoint = EntryPoints.get(applicationContext, WorkerEntryPoint::class.java)
val repo = entryPoint.repo()
این روش از نشتی حافظه (Memory Leak) جلوگیری میکنه و ساختار تزریق وابستگیتون رو تمیز نگه میداره.
2. کامپوننتهای سفارشی و کنترل Scope وابستگیها
همینطور که اپها بزرگ میشن، تیمها اغلب نیاز پیدا میکنن که کنترل دقیقتری روی طول عمر وابستگیهاشون داشته باشن.
Hilt این امکان رو میده که Scopeهای سفارشی بسازی تا وابستگیهای مربوط به Featureها یا ماژولهای مختلف رو از هم جدا کنی.
مثال: تعریف یک Scope سفارشی:
@Scope
@Retention(AnnotationRetention.RUNTIME)
annotation class FeatureScope
Scopeهای سفارشی باعث بهبود این موارد میشن:
- مصرف حافظه
- ماژولار سازی (Modularization)
- جداسازی در سطح Feature
این روش توی معماریهای چند-ماژولی خیلی رایجه.
یه مثال کوچیک می زنم و رد میشم که متوجه بشین این مبحث چقدر مهمه و هرموقع لازم داشتید، راجعش تو نت تحقیق کنید. مثلا ما تو اپمون کاربر رو لاگین می کنیم و سبد خرید و پرداخت اینهاش رو نگه داری می کنیم، حالا می خوام اگه لاگ اوت شد خودکار همه این ها از روی رم پاک بشه. اینجا میشه یه اسکوپ user تعریف کرد و هرچی مربوط به اون هست رو اورد زیر مجموعه این اسکوپ:
// تعریف اسکوپ مخصوص کاربر
@Scope
annotation class UserScope
@UserScope
class UserManager @Inject constructor() {
// وقتی کاربر لاگ اوت کرد، این object هم از مموری پاک میشه
}
حالا ممکن هم هست بخوایم در سطح فیچر جداسازی کنیم ، یعنی چی ؟ یعنی مثلا بگیم ویژگی پرداخت این ها زیر مجموعه اش هستند، ویژگی چت ….
3. @AssistedInject برای پارامترهای Run-time
Hilt نمیتونه مقادیری رو که در زمان اجرا به صورت پویا به دست میآد (مثل IDها، توکنها یا آرگومانها) رو تزریق کنه.
این دقیقاً جاییه که @AssistedInject حیاتی میشه.
مثال: ViewModel با پارامتر پویا
class DetailViewModel @AssistedInject constructor(
private val repo: Repo,
@Assisted val userId: String // این رو در زمان اجرا پاس میدیم
) {
@AssistedFactory
interface Factory {
fun create(userId: String): DetailViewModel
}
}
اپهای بزرگی که Flowهای ناوبری پویا دارن، خیلی به این ویژگی وابستهن.
4. Multibindings (IntoSet / IntoMap)
Multibinding برای پیادهسازی موارد زیر در معماریهای سطح Enterprise ضروریه:
- سیستمهای پلاگین
- هندلرهای رویداد (Event Handlers)
- رجیستریهای Repository
- چندین استراتژی مختلف پشت یک اینترفیس
مثال: تزریق چندین Logger مختلف به داخل یک Set
@Module
@InstallIn(SingletonComponent::class)
object LoggingModule {
@Provides
@IntoSet // همین یک خط جادو میکنه!
fun provideFileLogger(): Logger = FileLogger()
@Provides
@IntoSet
fun provideNetworkLogger(): Logger = NetworkLogger()
}
با این کار، سیستم لاگ گرفتن برنامهتون به راحتی قابل گسترش میشه، بدون اینکه نیاز باشه کد موجود رو تغییر بدید.
5. وصل کردن اینترفیس به ایمپلمنتیشن (@Binds)
این یکی از قویترین الگوها برای Clean Architecture هستش.
مثال:
@Binds
abstract fun bindRepo(
impl: UserRepoImpl
): UserRepo // میگی این اینترفیس رو با این ایمپلمنتیشن ارائه بده
مزایا:
- وابستگی کم (Low Coupling)
- انتزاع (Abstraction) واضح
- تست کردن راحتتر
- جداسازی درست ماژولها
اپهای بزرگ مقیاس برای حفظ قابلیت نگهداری (Maintainability) به این روش نیاز دارن.
6. تزریق خودکار SavedStateHandle در Hilt ViewModels
وقتی Hilt به صورت خودکار SavedStateHandle رو ارائه میده، کار با آرگومانهای Navigation و بازیابی حالت (State Restoration) بسیار تمیزتر میشه.
مثال:
@HiltViewModel
class DetailVM @Inject constructor(
private val repo: Repo,
private val savedStateHandle: SavedStateHandle // خود Hilt اینو میاره
) : ViewModel() {
val userId = savedStateHandle["userId"] ?: ""
}
**دیگه لازم نیست برای این کار Factoryهای دست و پا گیر بنویسی.**
7. Override کردن وابستگیها مخصوص تست (Hilt Testing)
برای کدبیسهای بزرگ، نوشتن Bindingهای مخصوص تست واقعاً نجاتبخشه.
مثال: Override کردن یک وابستگی برای UI Test
@HiltAndroidTest
class LoginTest {
@BindValue // با این annotation یه وابستگی فیک جایگزین میکنی
val fakeRepo = FakeLoginRepository()}
این روش به ماژولهای اصلی برنامهتون اجازه میده تمیز باقی بمونن، در حالی که تستها کنترل کامل روی سیستم تزریق وابستگی دارن.
حرف آخر:
Hilt خیلی بیشتر از فقط تزریق وابستگی پایهایه.
این ویژگیهای پیشرفتهای که گفتیم هستن که واقعاً Hilt رو برای اپهای بزرگمقیاس و چند-ماژولی، آماده Production و قدرتمند میکنن.


دیدگاهتان را بنویسید