تزریق وابستگی در اندروید با Hilt به همراه پروژه عملی

  ‏سطح پیشرفته
‏  41 دقیقه
۲۹ اسفند ۱۳۹۹
تزریق وابستگی در اندروید با Hilt به همراه پروژه عملی

تزریق وابستگی در اندروید یکی از مباحث مهم در یادگیری اندروید است که به کنترل کامپوننت‌ها و کتابخانه‌های مختلف در پروژه‌های ما کمک می‌کند. تزریق وابستگی در اندروید با استفاده از ابزارهای مختلفی انجام می‌شود. شاید درباره تزریق وابستگی در اندروید با Hilt شنیده باشید. فریمورک Hilt، یکی از فریمورک‌های محبوب اندروید است که مزایا و ویژگی‌های کاربردی زیادی دارد.

از این رو قصد داریم شما را با فریمورک Hilt آشنا کرده و طریقه‌ی تزریق وابستگی در اندروید با Hilt را به صورت پروژه محور آموزش دهیم. اگر تا کنون با فریمورک Hilt کار نکرده‌اید و قصد یادگیری عملی آن را دارید، تا انتهای این مقاله با وب سایت آموزش برنامه نویسی سون لرن همراه باشید.

فهرست محتوای این مقاله

فریمورک Hilt چیست و چه کاربردی دارد؟

توسعه دهندگان اندروید در هنگام توسعه اپلیکیشن‌های خود از کتابخانه‌ها و فریمورک‌های مختلفی استفاده می‌کنند تا سرعت توسعه را بالاتر برده و کدنویسی را آسان‌تر کنند. Hilt یک فریمورک تزریق وابستگی در اندروید است که سعی در جلوگیری از تکرار کد‌ها و همچنین کنترل وابستگی‌های پروژه دارد.

Hilt یک روش استاندارد برای تزریق وابستگی ارائه می‌دهد که وابستگی‌های هر کلاس را در پروژه‌های اندرویدی مدیریت می‌کند و به چرخه حیات (lifecycle) کلاس‌ها و کامپوننت‌ها متصل است. فریمورک Hilt ساده شده‌ی فریمورک Dagger (فریمورک Dagger چیست؟) می‌باشد که مزایایی همچون توسعه پذیری و تست پذیری را دارا است.

فریمورک هیلت، تزریق وابستگی را در زمان اجرا (compile-time) انجام می‌دهد که باعث سرعت بیشتر و کارایی بهتر در اپلیکیشن می‌شود. در ادامه به بررسی بیشتر فریمورک Hilt میپردازیم.

کامپوننت‌ها در Hilt

در پروژه‌های اندرویدی هنگامی که بخواهیم به Hilt اعلام کنیم که کدام وابستگی در چه کلاس‌هایی قابل استفاده باشد و تا چه زمانی بتوانیم از آن استفاده کنیم، باید با استفاده از حاشیه نویسی (annotation) آن را مشخص کنیم.

همچنین با استفاده از حاشیه نویسی مشخص می‌کنیم که کدام وابستگی در کدام کلاس‌ها قابل مشاهده باشد. هر کامپوننت در فریمورک Hilt برای قسمتی از کلاس‌های اندرویدی ما به کار می‌رود. به جدول زیر توجه کنید:

کامپوننت‌ها در فریمورک Hilt

طول عمر کامپوننت‌ها در Hilt

به طور کلی مدت حیات (lifetime) هر کامپوننت به زمان ایجاد و از بین رفتن کلاس‌های اندرویدی بستگی دارد. این موضوع در تزریق وابستگی با Hilt دو مزایا دارد:

  1. وابستگی‌ها محدود به زمان حیات کامپوننت می‌شود. یعنی از زمان ایجاد تا زمان از بین رفتن آن کامپوننت.
  2. در زمان حیات کامپوننت باعث می‌شود هیچگاه آن وابستگی null نباشد.

شباهت‌ها و تفاوت‌های Dagger و Hilt

مقایسه Hilt با Dagger

همانطور که در بخش قبلی اشاره کردیم، فریمورک Hilt ساده و خلاصه شده‌ی فریمورک Dagger است که کدنویسی را برای برنامه نویسان اندروید راحت‌تر می‌کند. در فریمورک Dagger ما نیاز به کدنویسی بیشتری برای تزریق وابستگی به پروژه‌ی خود را داشتیم. از این رو فریمورک Hilt معرفی شد تا بتواند جایگزین خوبی برای فریمورک Dagger باشد.

اما شباهت‌ها و تفاوت‌های دو فریمورک Hilt با Dagger چیست؟ در ادامه به پاسخ این سوال می‌پردازیم.

شباهت‌های Hilt و Dagger

شباهت‌هایی که در دو فریمورک Hilt و Dagger وجود دارد:

  • هر دو فریمورک در هنگام کامپایل تزریق وابستگی را انجام می‌دهند.
  • از آنجایی که Hilt ساده شده‌ی Dagger است، هر دو از حاشیه نویسی برای نشانه گذاری استفاده می‌کنند.

تفاوت‌های Hilt و Dagger

تفاوت‌هایی که در دو فریمورک Hilt و Dagger وجود دارد:

  • در فریمورک Dagger ما یک کلاس برای کامپوننت‌هایمان ایجاد می‌کنیم که شامل همه‌ی کامپوننت‌ها شده که به اصطلاح کلاسی به عنوان Factory در اپلیکیشن شناخته می‌شود. همچنین باید اینترفیس Application Context را در کلاس Application خود تزریق (inject) کنیم. در حالی که ما در فریمورک Hilt چنین کلاسی نداریم و تنها لازم است در کلاس Application پروژه خود از حاشیه نویسی HiltAndroidApp@ استفاده کنیم.
@HiltAndroidApp
class MyApplication : Application(){
}
  • در Dagger نیاز داشتیم که حاشیه نویس‌هایی را برای ایجاد محدودیت بر روی وابستگی‌ها ایجاد کرده و از آن‌ها استفاده کنیم. اما در Hilt ما این حاشیه نویس‌ها را به صورت آماده در اختیار داریم و می‌توانیم از آن استفاده کنیم. مانند ViewModelScoped@ یا FragmentScoped@.
  • در Dagger ما نیاز به تعریف حاشیه نویسی تحت عنوان ContributesAndroidInjector داشتیم. این حاشیه نویس زیرکامپوننت‌هایی (SubComponent) را معرفی و تولید می‌کرد که باعث جلوگیری از تکرار کد‌ها در قسمت‌های مختلف اپلیکیشن می‌شد. اما در اپلیکیشن‌های بزرگ نوشتن این حاشیه نویس، خود به خود باعث تکرار کد‌ها و باعث شلوغی بی دلیل می‌شود. در Hilt ما نیاز به چنین حاشیه نویسی نداریم. در عوض Hilt حاشیه نویس AndroidEntryPoint@ را معرفی کرده است.
  • در Dagger هنگامی که می‌خواستیم از ViewModelها استفاده کنیم، تا زمانی مشکلی نداشتیم که ViewModelها پارامتری را در متد سازنده (Contractor) نداشته باشند. زیرا باید کلاسی تحت عنوان ViewModelFactory ساخته می‌شد. اما در تزریق وابستگی در اندروید با Hilt ما با استفاده از حاشیه نویس HiltViewModel@ می‌توانیم پارامتر‌های متد سازنده را تامین کرده و به ViewModel پاس بدهیم. در واقع دیگر نیازی به ساخت کلاس ViewModelFactory نیست.

حاشیه نویس‌ها در فریمورک Hilt

در ادامه قصد داریم شما را با حاشیه نویس‌های مختلف در فریمورک Hilt آشنا کنیم.

حاشیه نویس HiltAndroidApp@

در همه‌ی پروژه‌های اندرویدی ایجاد کلاس Application برای پروژه یکی از موضوعات اصلی می‌باشد. در فریمورک Hilt برای اینکه بتوانیم دسترسی کامل به تمامی ماژول‌ها و کامپوننت‌ها را فراهم کنیم، باید در کلاس Application خود حاشیه نویس HiltAndroidApp@ را اضافه کنیم. این حاشیه نویس وظیفه ایجاد کلاس‌های پایه برای اپلیکیشن جهت استفاده از وابستگی‌ها در قسمت‌های مختلف اپلیکیشن را برعهده دارد.

برای این منظور در کلاس Application پروژه خود به صورت زیر عمل می‌کنید:

@HiltAndroidApp
class App : Application() {
    override fun onCreate() {
        super.onCreate()
            ....
    }
}

حاشیه نویس AndroidEntryPoint@

بعد از حاشیه نویسی کلاس Application، باید کلاس‌هایی که از آن ارث می‌برند را با حاشیه نویس AndroidEntryPoint@ نشانه گذاری کنید. زیرا با استفاده از این کار شما می‌توانید وابستگی‌های خود را در کلاس‌های مورد نظرتان تزریق و استفاده کنید.

کلاس‌هایی که از این حاشیه نویس استفاده می‌کنند عبارتند از:

  1. View
  2. Activity
  3. Fragment
  4. Service
  5. BroadcastReceiver

مثال زیر نمونه‌‎ای از پیاده سازی این حاشیه نویس است:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

نکته: توجه داشته باشید این حاشیه نویس در اکتیویتی‌هایی که از ComponentActivity و همچنین فرگمنت‌هایی که از androidx library fragment ارث می‌برند، قابل استفاده است.

حاشیه نویس Module@

این حاشیه نویس، استاندارد شده‌ی ماژول‌ها در Dagger بوده و تنها تفاوت آن اضافه شدن حاشیه نویس InstallIn@ است. با استفاده از حاشیه نویس InstallIn@ می‌توانیم مشخص کنیم کدام ماژول در کدام بخش از اپلیکیشن قابل استفاده باشد. به عنوان مثال استفاده از حاشیه نویس SingletonComponent به این معنی است که این ماژول در سر تا سر اپلیکیشن مورد استفاده قرار می‌گیرد.

@Module
@InstallIn(SingletonComponent::class)
class MyModule {
      ....
}

در ماژول‌ها می‌توانیم فانکشن‌های  خود را تعریف کنیم. به عنوان مثال:

  @Provides
  @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient) = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create())
        .client(okHttpClient)
        .baseUrl(baseUrl)
        .build()

حاشیه نویس Provides@ برای همه فانکشن‌هایی که در ماژول‌ها نوشته می‌شوند، اجباری است.

حاشیه نویس Singleton@ به معنی آن است که در طول حیات این کامپوننت، فقط و فقط یک نمونه (Instance) از این وابستگی ایجاد و مورد استفاده قرار می‌گیرد.

حاشیه نویس HiltViewModel@

همانطور که از اسم این annotation مشخص است، این حاشیه نویس در ViewModel‌های پروژه نشانه گذاری می‌شود.

به عنوان مثال:

@HiltViewModel
class MainViewModel : ViewModel() {
      ....
}

برای استفاده از ViewModel‌ها در View‌ها نیز باید به صورت زیر عمل کنید:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    ....
    private val mainViewModel: MainViewModel by viewModels()
    ....
}

حاشیه نویس Inject@

این حاشیه نویس پرکاربردترین حاشیه نویس در فریمورک Hilt است. با استفاده از این حاشیه نویس می‌توانید وابستگی مورد نظر را در کلاس خود اضافه و استفاده کنید. به عنوان مثال:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    @Inject
    private val user : User
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

همچنین می‌توان از این حاشیه نویس در کانستراکتور نیز استفاده کرد:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {
   ....
}

هنگامی که این حاشیه نویس را فراخوانی می‌کنید، فریمورک Hilt در پس زمینه این وابستگی را در صورت وجود ایجاد کرده و به پروژه شما تزریق می‌کند.

حاشیه نویسی در تزریق وابستگی با Hilt

مزایای تزریق وابستگی در اندروید با Hilt چیست؟

تزریق وابستگی در اندروید با Hilt مزایای زیادی دارد که در ادامه به آن می‌پردازیم.

جلوگیری از تکرار کد‌ها در قسمت‌های مختلف اپلیکیشن با استفاده از Hilt

موضوع جلوگیری از تکرار کد در فریمورک‌های تزریق وابستگی یکی از اهداف اصلی می‌باشد. موضوعی که فریمورک Hilt سعی در حل آن داشته، بحث پیچیده‌ی راه اندازی (Setup) کامپوننت‌ها در Dagger بوده است. به عنوان مثال ما در Dagger نیازمند تعریف کامپوننت‌ها همراه با ماژول‌ها، تعریف اینترفیس لیست (Interface List)، کدنویسی برای تعریف چرخه حیات کامپوننت‌های مختلف و... داشتیم. مسئله‌ای که Hilt آن را برطرف کرده است.

استاندارد سازی با کمک تزریق وابستگی در اندروید با Hilt

فریمورک Hilt سلسه مراتب کامپوننت‌ها را استاندارد می‌کند. کتابخانه‌ها هنگامی که با Hilt ادغام می‌شوند به راحتی می‌توانند در قسمت‌های مختلف اضافه و یا مورد استفاده قرار گیرند. این موضوع باعث می‌شود کتابخانه‌های پیچیده‌تر راحتی بهتری در ادغام شدن با Hilt را داشته باشند و در هنگام استفاده خطایی به وجود نیاید.

پیکربندی بهتر پروژه به کمک تزریق وابستگی با Hilt

در پروژه‌ها ما بسته به سناریو خود ممکن است چند Build Configuration داشته باشیم. به عنوان مثال یک نسخه دیباگ (Debug) و یک نسخه نهایی (Release). ما نیاز داریم که در نسخه دیباگ ویژگی‌هایی (Features) داشته باشیم که هنوز در نسخه نهایی آن را اضافه نکرده‌ایم. در چنین سناریویی ما نیازمند این هستیم که برای هر Build Configuration در Dagger، ماژول‌های جدا و کامپوننت‌های متفاوتی داشته باشیم. که باعث تکرار بسیار زیاد کد‌ها می‌شود در صورتی که بعضی از وابستگی‌ها تفاوتی در دو نسخه ندارند. در تزریق وابستگی در اندروید با Hilt این موضوع برطرف شده است.

تست پذیری بهتر اپلیکیشن با تزریق وابستگی در اندروید با Hilt

تست پذیری اپلیکیشن در فریمورک Dagger به دلیل مواردی که در قسمت قبلی بیان شد، به مراتب سخت‌تر از Hilt است. فریمورک Hilt به دلیل تولید کردن کد برای کامپوننت‌ها تست پذیری را آسان‌تر ساخته است. همچنین فریمورک Hilt ابزار مخصوص به خود را برای تست نویسی دارد که باعث می‌شود ماژول‌ها را بهتر مدیریت کنید. همچنین ساده ساختن مدیریت ماژول‌ها در Hilt باعث شده است که تست اپلیکیشن‌ها راحت‌تر باشد. استفاده از Dagger در تست نویسی باعث جلوگیری از تکرار کد‌ها در قسمت‌های مختلف شده و باعث قوی‌تر شدن هرچه بیشتر کد‌ها در پروژه می‌شود.

آموزش پیاده سازی تزریق وابستگی با Hilt در یک پروژه اندرویدی

در ادامه قصد داریم با استفاده از تزریق وابستگی در اندروید با Hilt پروژه‌ای را پیاده سازی کنیم.

برای پیاده سازی این پروژه شما نیازمند درک مفاهیمی همچون زبان کاتلین، معماری MVVM، کتابخانه Retrofit و کتابخانه Coroutine دارید.

قبل از شروع پروژه داخل فایل build.gradle اپلیکیشن، کتابخانه‌های زیر را اضافه می‌کنیم:

    //Activity and Fragment Extension for Hilt
    implementation "androidx.activity:activity-ktx:{last-version}"
    implementation "androidx.fragment:fragment-ktx:{last-version}"

    //Hilt Library
    implementation 'com.google.dagger:hilt-android:{last-version}'
    kapt 'com.google.dagger:hilt-android-compiler:{last-version}'

    // Networking
    implementation "com.squareup.retrofit2:converter-moshi:{last-version}"
    implementation "com.squareup.retrofit2:retrofit:{last-version}"
    implementation "com.squareup.okhttp3:okhttp:{last-version}"
    implementation "com.squareup.okhttp3:logging-interceptor:{last-version}"

    //Coroutine
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:{last-version}"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:{last-version}"
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:{last-version}"

همچنین در قسمت Top Level فایل build.gradle اپلیکیشن، این پلاگین‌ها را اضافه می‌کنیم:

apply plugin: 'dagger.hilt.android.plugin'
apply plugin: 'kotlin-kapt'

در فایل build.gradle سطح پروژه نیز خطوط کد زیر را اضافه می‌کنیم:

buildscript {
    ....
        dependencies {
        ....
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.31.2-alpha'
    }
}

در مرحله آخر، دسترسی اینترنت و دسترسی به وضعیت اینترنت را در فایل AndroidManifest.xml اضافه می‌کنیم:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

حال در قدم اول، ما پروژه را به صورت زیر پکیج بندی می‌کنیم:

ایجاد یک پروژه همراه با فریمورک Hilt

در مرحله دوم، کلاس App را ساخته و به صورت زیر آن را کد نویسی می‌کنیم:

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class App : Application() {
}

در مرحله سوم، در پکیج data ما پکیجی تحت عنوان model ایجاد کرده و دیتا کلاسی (data class) به اسم User ایجاد می‌کنیم. این کلاس به صورت زیر است:

data class User(
    val id: Int = 0,
    val name: String = "",
    val email: String = ""
)

در مرحله بعد، در پکیج data پکیجی تحت عنوان api ایجاد می‌کنیم و در آن اینترفیسی به اسم ApiService ساخته و آن را به صورت زیر کد نویسی می‌کنیم:

interface ApiService {
    @GET("users")
    suspend fun getUsers(): Response<List<User>>
}

در مرحله پنجم، پکیج دیگری به اسم repository در پکیج data افزوده و کلاس زیر را به آن اضافه می‌کنیم:

class MainRepository @Inject constructor(private val apiService: ApiService) {
    suspend fun getUsers() = apiService.getUsers()
}

حال، پکیج data ما تکمیل و آماده است.

در قدم بعد، در پکیج di (کلمه di مخفف dependency Injection است) کلاسی به اسم ApplicationModule ایجاد می‌کنیم.

@Module
@InstallIn(SingletonComponent::class)
class ApplicationModule {

    @Provides
    @Singleton
    fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
        OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .build()
    } else OkHttpClient.Builder().build()

    @Provides
    @Singleton
    fun provideRetrofit(okHttpClient: OkHttpClient) = Retrofit.Builder()
        .addConverterFactory(MoshiConverterFactory.create())
        .client(okHttpClient)
        .baseUrl("https://5e510330f2c0d300147c034c.mockapi.io/")
        .build()

    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit) = retrofit.create(ApiService::class.java)

    @Provides
    @Singleton
    fun provideApiHelper(apiHelper: ApiHelperImpl): ApiHelper = apiHelper

}

در قدم بعد، قبل از آن که به سراغ پکیج ui برویم پکیج utils را کامل می‌کنیم.

در این مرحله کلاسی به اسم NetworkStatus ایجاد می‌کنیم. این کلاس وظیفه‌ی چک کردن وضعیت اینترنت کاربر را بر عهده دارد.

@Singleton
class NetworkStatus @Inject constructor(@ApplicationContext private val context: Context) {

    fun checkNetworkMethod(): Boolean {
        var result = false
        val connectivityManager =
            context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            val networkCapabilities = connectivityManager.activeNetwork ?: return false
            val activeNetwork =
                connectivityManager.getNetworkCapabilities(networkCapabilities) ?: return false
            result = when {
                activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
                activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
                activeNetwork.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
                else -> false
            }
        } else {
            connectivityManager.run {
                connectivityManager.activeNetworkInfo?.run {
                    result = when (type) {
                        ConnectivityManager.TYPE_WIFI -> true
                        ConnectivityManager.TYPE_MOBILE -> true
                        ConnectivityManager.TYPE_ETHERNET -> true
                        else -> false
                    }
                }
            }
        }
        return result
    }
}

همچنین کلاسی تحت عنوان Resource داریم که به شکل زیر است:

data class Resource<out T>(val status: Status, val data: T?, val message: String?) {

    companion object {

        fun <T> success(data: T?): Resource<T> {
            return Resource(Status.SUCCESS, data, null)
        }

        fun <T> error(msg: String, data: T?): Resource<T> {
            return Resource(Status.ERROR, data, msg)
        }

        fun <T> loading(data: T?): Resource<T> {
            return Resource(Status.LOADING, data, null)
        }
    }
}

وظیفه‌ی این کلاس کنترل state‌های مختلف اپلیکیشن و اطلاع آن به view است.

همچنین Enum کلاسی را برای مدیریت کلاس Resource ایجاد می‌کنیم.

enum class Status {
    SUCCESS,
    ERROR,
    LOADING
}

در قدم بعد به سراغ پکیج ui می‌رویم. در این بخش برای نمایش آیتم‌های مختلف در ریسایکلر ویو (RecyclerView) کلاس Adapter و ViewHolder آن را ایجاد می‌کنیم:

class MainAdapter(private val users: ArrayList<User>) :
    RecyclerView.Adapter<MainAdapter.MainViewHolder>() {

    inner class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var username = itemView.textViewUserName
        var email = itemView.textViewUserEmail
        fun bindUsers(user: User) {
            username.text = user.name
            email.text = user.email
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MainViewHolder {
        return MainViewHolder(
            LayoutInflater.from(parent.context).inflate(R.layout.item_layout, parent, false)
        )
    }

    override fun onBindViewHolder(holder: MainViewHolder, position: Int) {
        holder.bindUsers(users[position])
    }

    override fun getItemCount(): Int = users.size

    fun addUsers(users: List<User>) {
        this.users.addAll(users)
        notifyDataSetChanged()
    }
}

در قسمت بعد ViewModel مربوط به View خود را ایجاد کرده و به صورت زیر کد نویسی می‌کنیم:

@HiltViewModel
class MainViewModel @Inject constructor(
    private val mainRepository: MainRepository,
private val networkHelper: NetworkHelper
) : ViewModel() {

    private val _users = MutableLiveData<Resource<List<User>>>()
    val users: LiveData<Resource<List<User>>>
    get() = _users

    init {
        fatchUser()
    }

    private fun fatchUser() {
        viewModelScope.launch {
            _users.postValue(Resource.loading(null))
            if (networkHelper.checkNetworkThirdMethod()) {
                mainRepository.getUsers().let {
                    if (it.isSuccessful)
                        _users.postValue(Resource.success(it.body()))
                    else
                        _users.postValue(Resource.error(it.errorBody().toString(), null))
                }
            } else
                _users.postValue(Resource.error("No Internet Connection!", null))
        }
    }
}

و در قسمت آخر، View ما به این شکل خواهد بود:

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val mainViewModel: MainViewModel by viewModels()
    private lateinit var mainAdapter: MainAdapter
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        setupUi()
        setupObserver()
    }

    private fun setupObserver() {
        mainViewModel.users.observe(this) {
            when (it.status) {
                Status.SUCCESS -> {
                    progressBar.visibility = View.GONE
                    recyclerView.visibility = View.VISIBLE
                    it.data?.let { mainAdapter.addUsers(it) }
                }
                Status.LOADING -> {
                    progressBar.visibility = View.VISIBLE
                    recyclerView.visibility = View.GONE
                }
                Status.ERROR -> {
                    progressBar.visibility = View.GONE
                    Toast.makeText(this, it.message, Toast.LENGTH_SHORT).show()
                }
            }
        }
    }

    private fun setupUi() {
        recyclerView.layoutManager = LinearLayoutManager(
            this, RecyclerView.VERTICAL, false
        )
        mainAdapter = MainAdapter(arrayListOf())
        recyclerView.adapter = mainAdapter
    }
}

در قدم بعد، به سراغ فایل‌های xml می‌رویم. activity_main.xml به این صورت خواهد بود:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.main.view.MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="gone" />

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

و item_layout.xml به شکل زیر است:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="60dp">

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewUserName"
        style="@style/TextAppearance.AppCompat.Large"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="8dp"
        android:layout_marginTop="4dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="7learn" />

    <androidx.appcompat.widget.AppCompatTextView
        android:id="@+id/textViewUserEmail"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="@+id/textViewUserName"
        app:layout_constraintTop_toBottomOf="@+id/textViewUserName"
        tools:text="7learn" />

</androidx.constraintlayout.widget.ConstraintLayout>

حال هنگامی که اپلیکیشن را بیلد و اجرا می‌کنیم سه حالت متفاوت می‌توانیم دریافت کنیم:

  1. حالت Loading در اپلیکیشن:
    Loading state در تزریق وابستگی با Hilt
  2. حالت نمایش اطلاعات در اپلیکیشن:
    Data state در تزریق وابستگی با Hilt
  3. حالت Error در اپلیکیشن که می‌تواند به دلیل قطعی اینترنت و یا مدت زمان زیاد ارسال درخواست به وجود بیاید:
    Error state در تزریق وابستگی با Hilt

آیا نیاز است که در پروژه‌هایمان از تزریق وابستگی با استفاده از Hilt استفاده کنیم؟

آیا نیاز است که در پروژه‌هایمان از Dependency Injection استفاده کنیم؟

با استفاده از Di در اندروید می‌توانیم حجم انبوهی از کتابخانه‌ها و وابستگی‌های مختلف را کنترل و مدیریت کنیم. هنگامی که پروژه وسعت پیدا کند، کنترل وابستگی‌ها در تک تک کلاس‌هایمان تقریبا غیر ممکن شده و باعث بهم ریختی در پروژه می‌شود. همچنین کنترل وابستگی‌ها در پروژه‌های اندرویدی باعث پرفورمنس بهتر در اپلیکیشن نیز می‌شود.

برنامه نویسان اندروید بعد از دغدغه‌های فراوانی که بر سر استفاده از فریمورک Dagger داشتند، با معرفی فریمورک Hilt بسیاری از مشکلات آن‌ها بدون کد نویسی توسط خودشان حل گردید. تزریق وابستگی در اندروید با Hilt بسیار آسان‌‌تر از تزریق وابستگی با Dagger است اما همچنان بعضی از توسعه دهندگان و شرکت‌ها استفاده از Dagger را پیشنهاد می‌دهند.

به نظر شما Hilt چه برتری‌های دیگری به نسبت Dagger دارد؟ شما استفاده از کدام فریمورک تزریق وابستگی را پیشنهاد می‌دهید؟ خوشحال می‌شویم نظرات، تجربیات خود را با ما و سایر کاربران سون لرن به اشتراک بگذارید.

چه امتیازی به این مقاله می دید؟
نویسنده ابوالفضل رضایی
زندگیتو مثل کتابی بساز که وقتی آخرین صفحش تموم شد، بتونی ۵۰۰ میلیون ازش بفروشی...
ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :

 

نظرات کاربران

اولین دیدگاه این پست رو تو بنویس !

ما در سون لرن با محدودسازی دسترسی آزاد به اینترنت مخالفیم     اطلاعات بیشتر