آشنایی با Dagger 2 در اندروید – قسمت صفرم


آشنایی با فریم ورک تزریق وابستگی در اندروید

تزریق وابستگی با Dagger 2

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

تقریبا تمام برنامه های اندرویدی متکی به ساخت object هایی هستند که خود این نمونه گیری وابستگی هایی بیرون از برنامه را به همراه دارد. مثلا برنامه ای را در نظر بگیرید که از api یک سایت هواشناسی استفاده می کند که احتمالا برای اتصال به شبکه از کتابخانه ای مثل Retrofit بهره می برد. برای استفاده از این کتابخانه باید از کتابخانه مثل Gson هم برای واکشی داده ها استفاده کرد. به علاوه کلاس هایی که با هدف اعتبار سنجی داده ها یا استفاده از حافظه پنهان ساخته شده باشند نیازمند دسترسی به shared preferences یا امکان دیگری برای ذخیره سازی، به منظور ساخت یک نمونه جدید است. همین نمونه جدید، در خود زنجیره ای از وابستگی ها را به همراه دارد.

Dagger 2 همین وابستگی ها را تجزیه و تحلیل می نماید. بطور خودکار با تولید کدهای مربوطه، به شکل مناسبی آنها را به هم مربوط می سازد. در حالی که برای تزریق وابستگی framework های دیگری وجود دارند، بسیاری از آنها در اتصال به xml محدودیت هایی دارند. یا به بازیابی برخی وابستگی ها، حین اجرای برنامه نیازمندند.

نقطه اتکای فریم ورک dagger 2 استفاده از پروسه annotation گذاری جاواست.  هم چنین در زمان کامپایل کد وابستگی ها بازیابی می شوند. به همین خاطر این framework بعنوان یکی از بهینه ترین فریم ورک ها برای تزریق وابستگی به شمار می رود.

فواید استفاده از dagger 2

  • ساده سازی دسترسی به نمونه های ساخته شده

درست مثل کتابخانه ButterKnife که ساخت ارجاع به view و event handlers و همینطور منابع را ساده تر ساخت، dagger 2 نیز امکان دسترسی به نمونه های مشترک را ساده تر نمود. برای مثال وقتی برای یک بار نمونه های singleton خود در dagger تعریف می کنیم، از آن پس می توانیم فیلدها را با یک @Inject ساده تعریف کنیم. مثل نمونه زیر :

public class MainActivity extends Activity {
   @Inject MyWheatherApiClient mWheatherApiClient;
   @Inject SharedPreferences sharedPreferences;

   public void onCreate(Bundle savedInstance) {
       // assign singleton instances to fields
       InjectorClass.inject(this);
   } 
  • ساخت تنظیمات ساده برای dependency های پیچیده

در ساخت object های موجود در برنامه، یک ترتیب ضمنی وجود دارد. Dagger 2 به گراف حاصل از قرارگیری dependency ها در برنامه نگاه می کند و بهترین کدی را که هم از نظر فهم و هم هنگام trace ساده باشد، تولید می کند. همین عملکرد موجب صرفه جویی در زمان و جلوگیری از خطوط زیاد کد تکراری می گردد.

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

  • عمر نمونه ها

به راحتی می توان تعیین کرد که آیا لازم است objectی تا انتهای برنامه زنده بماند یا خیر. علاوه بر این به راحتی می توان با نفوذ dagger 2 نمونه هایی با چرخه حیات (life cycle) کوتاه تری تعریف کرد. مثل تعیین زمان زنده ماندن یک session یا چرخه حیات اکتیویتی.

پیاده سازی

اندروید استودیو بطور پیش فرض اجازه ساخت کلاسی که از کدهای dagger 2 استفاده کند را نمی دهد. افزودن امکان annotationProcessor یا عملیات حاشیه نویسی، فایل هایی را به IDE اضافه می کند و وضوح بیشتری را در کد حاصل می کند.

dependencies {
  implementation 'com.google.dagger:dagger-android:2.20'
  implementation 'com.google.dagger:dagger-android-support:2.20' // if you use the support libraries
  annotationProcessor 'com.google.dagger:dagger-android-processor:2.20'
  annotationProcessor 'com.google.dagger:dagger-compiler:2.20'
}

توجه نمایید کلید واژه compileOnly اشاره به وابستگی هایی دارد که فقط در زمان کامپایل شدن برنامه لازم هستند. کامپایلر به واسطه dagger کدی را با هدف ساخت گرافی از dependency ی کلاس های سورس کد شما تولید می کند. این کلاس ها هم زمان با کامپایل برنامه به IDE افزوده می گردند.

annotationProcessor که به واسطه پلاگین gradle اندروید فهمیده می شود، وظیفه افزودن این کلاس ها را ندارد. بلکه فقط برای انجام پروسه annotation یا حاشیه نویسی استفاده می شوند.

 

ساخت singleton

فرض کنید بدون استفاده از هیچ گونه فریم ورکی برای تزربق وابستگی قصد داشتید تا بخش کلاینت برنامه را بنویسید:

OkHttpClient client = new OkHttpClient();

// Enable caching for OkHttp
int cacheSize = 10 * 1024 * 1024; // 10 MiB
Cache cache = new Cache(getApplication().getCacheDir(), cacheSize);
client.setCache(cache);

// Used for caching authentication tokens
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);

// Instantiate Gson
Gson gson = new GsonBuilder().create();
GsonConverterFactory converterFactory = GsonConverterFactory.create(gson);

// Build Retrofit
Retrofit retrofit = new Retrofit.Builder()
                                .baseUrl("https://api.github.com")
                                .addConverterFactory(converterFactory)
                                .client(client)  // custom client
                                .build();
تعریف singleton برنامه

نیاز دارید تا مشخص نمایید در ساخت یک ماژول dagger 2 چه object هایی جزیی از زنجیره dependency هستند.  برای مثال اگر هدف ما ساخت یک نمونه از Retrofit باشد که برای همه اکتیویتی ها و فرگمنت های برنامه در دسترس بماند، باید dagger را از وجود این نمونه retrofit باخبر ساخت.

برای دسترسی به حافظه پنهان برنامه یا Caching، به context برنامه نیاز است. ماژول اولیه dagger در برنامه، AppModule.java برای آماده سازی این ارجاع ساخته می شود. در این کلاسی متدی را با حاشیه نویسی @Provides تعریف می کنیم تا dagger از وجود این متد به عنوان متد سازنده برای Application باخبر شود.

@Module
public class AppModule {

    Application mApplication;

    public AppModule(Application application) {
        mApplication = application;
    }

    @Provides
    @Singleton
    Application providesApplication() {
        return mApplication;
    }
}

در این مرحله کلاسی به نام NetModule.java می سازیم و با حاشیه نوسی @Module بالای آن، dagger را برای ساخت نمونه های لازم آماده سازیم.

متدهایی با مقدار برگشتی مورد نیاز ما در برنامه نیز با حاشیه نگاری @Provides مشخص می شوند. بدیهی ست با حاشیه نویسی @Singleton در برنامه برای dagger مشخص می شود نمونه ای که تولید می کند فقط باید همین یک بار در برنامه ساخته باشد. در این مثال ما از SharedPreferences، Gson، Cache، OkHttpClient و Retrofit بعنوان نوع مقدار برگشتی متدها در نظر گرفته شده اند که هر کدام می توانند خود، جزیی از زنجیره dependencyها باشند.

@Module
public class NetModule {

    String mBaseUrl;
    
    // Constructor needs one parameter to instantiate.  
    public NetModule(String baseUrl) {
        this.mBaseUrl = baseUrl;
    }

    // Dagger will only look for methods annotated with @Provides
    @Provides
    @Singleton
    // Application reference must come from AppModule.class
    SharedPreferences providesSharedPreferences(Application application) {
        return PreferenceManager.getDefaultSharedPreferences(application);
    }

    @Provides
    @Singleton
    Cache provideOkHttpCache(Application application) { 
        int cacheSize = 10 * 1024 * 1024; // 10 MiB
        Cache cache = new Cache(application.getCacheDir(), cacheSize);
        return cache;
    }

   @Provides 
   @Singleton
   Gson provideGson() {  
       GsonBuilder gsonBuilder = new GsonBuilder();
       gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES);
       return gsonBuilder.create();
   }

   @Provides
   @Singleton
   OkHttpClient provideOkHttpClient(Cache cache) {
      OkHttpClient.Builder client = new OkHttpClient.Builder();
      client.cache(cache);
      return client.build();
   }

   @Provides
   @Singleton
   Retrofit provideRetrofit(Gson gson, OkHttpClient okHttpClient) {
      Retrofit retrofit = new Retrofit.Builder()
                .addConverterFactory(GsonConverterFactory.create(gson))
                .baseUrl(mBaseUrl)
                .client(okHttpClient)
                .build();
        return retrofit;
    }
}

در این قسمت مهم ترین نکته وجود مواردی بعنوان برگشتی تابع هاست که خود dependency به شمار می روند و باید با حاشیه نویسی @Provides مشخص شوند. نمونه ساخته شده از جنس Retrofit خود وابسته به نمونه از جنس های Gson و OkHttpClient می باشد. پس می توان متدی نوشت که بعنوان ورودی مقادیری از جنس gson و okhttpClient بپذیرد. همین روش کار و حاشیه نگاری @Provides برای این متد باعث می شود تا dagger در جریان وجود یک dependency یا وابستگی به OkHttpClient و Gson برای ساخت نمونه Retrofit قرار بگیرد.

اهداف تزریق وابستگی

Dagger مسیری برای ساخت فیلدها در اکتیوتی ها، فرگمنت ها یا سرویس ها در نظر می گیرد تا ایجاد ارجاع با حاشیه نویسی @Inject و قرار دادن آن برای متدها و صدا زدن تابع inject()، به سادگی ممکن شود. صدا زدن متد inject() باعث می شود تا dagger متوجه شود که باید singleton ها را در گراف dependency ها قرار دهد تا بتواند نوع مقدار بازگشتی منطبق با توابع را بیابد. اگر این کار را با موفقیت انجام دهد ارجاعی به فیلد مورد نظر نسبت می دهد.

در این مثال dagger قصد دارد تا providerی که نوع MyWeatherApiClinet و SharedPreferences تولید می کند را بیابد:

public class MainActivity extends Activity {
   @Inject MyWeatherApiClinet  mWeatherApiClient;
   @Inject SharedPreferences sharedPreferences;

  public void onCreate(Bundle savedInstance) {
       // assign singleton instances to fields
       InjectorClass.inject(this);
   } 

کلاسی که در dagger 2 عملیات تزریق وابستگی را پیاده می کند component نام دارد. هدف این کلاس ساخت ارجاعاتی برای اکتیویتی ها، فرگمنت ها یا سرویس هاست که به singleton ها در برنامه دسترسی داشته باشند. باید این کلاس با حاشیه نویسی @Component مشخص شود.

توجه داشته باشید مواردی که مجاز به درخواست این dependency ها در ماژول هستند باید در همین کلاس با متدهای inject() جداگانه تعریف شوند.

@Singleton
@Component(modules={AppModule.class, NetModule.class})
public interface AppComponent {
   void inject(MainActivity activity);
   // void inject(MyFragment fragment);
   // void inject(MyService service);
}

دقت کنید کلاس های پایه برای این اهداف کافی نیستند. Dagger با تکیه بر مقدار بازگشتی توابع کار را پیش می برد. در این رفرنس می توانید پیشنهاداتی برای طرز کار بهتر را مطالعه نمایید.

قسمت تولید کد

مهم ترین بخش dagger 2 کتابخانه ای ست که کد را برای هر قسمتی که با حاشیه نویسی اینترفیس @Component مشخص شده تولید می کند. شما می توانید نام این کلاس را با پیش وند Dagger مشخص کنید. (مثل DaggerWeatherApiComponent.java) که مسئول نمونه سازی از گراف dependency ها در برنامه، و انجام عملیات تزریق برای فیلدهایی ست که با حاشیه نویسی @Inject مشخص شده اند.

 

در این مقاله به آشنایی و بررسی کلیات dagger 2 پرداختیم. توضیحات و بررسی جزئیات بیشتر درباره dagger 2 می تواند موضوع مقالات بعدی باشد.

 

به این پست امتیاز دهید

روی ستاره های کلیک کنید و امتیاز بدید

میانگین امتیاز / 5. تعداد:

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

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

Enter Captcha Here : *

Reload Image