آموزش اندروید-فصل ۲۸-۵: تزریق وابستگی با Dagger2

در بخش سوم آموزش MVP کمی درباره تزریق وابستگی یا Dependency Injection گفتیم:

آموزش اندروید-فصل ۲۸-۳: تست برنامه‌های MVP

در آن مطلب برای یادگیری بیشتر موضوع تزریق وابستگی به مقاله «کری» ارجاع داده شده بود. این مطلب ترجمه فارسی مقاله کری درباره تزریق وابستگی است. البته بخش‌هایی از مقاله کری به علت مرور زمان نیاز به تصحیح داشت که انجام دادم.

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

مقدمه

وقتی که شی‌ای دارید که به یک شی دیگر نیاز دارد یا برای انجام کارش به آن وابسته است، شما با مسأله «وابستگی» مواجه هستید. وابستگی‌ها را می‌توان با اجازه دادن به شی وابسته برای ساختن شی‌ای که به آن وابسته است یا با استفاده از یک «شی سازنده» یا factory object حل کرد. اما وقتی از تزریق وابستگی حرف می‌زنیم، وابستگی‌ها بدون این که لازم باشد توسط شی ساخته شود در اختیارش قرار می‌گیرد و به این ترتیب نیازی نیست خود شی از روی آن‌ها نمونه‌سازی کند. با این کار نرم‌افزار اصطلاحاً اتصال ضعیف می‌شود و نگهداری شوندگی برنامه بیشتر می‌شود.

این آموزش از آخرین نسخه Dagger یعنی Dagger2 استفاده می‌کند. Dagger2 کدباز است و در گیت‌هاب منتشر شده است.

پیش‌نیازها

آخرین نسخه اندروید استودیو باید در دستگاه شما نصب شده باشد.

۱- رابط برنامه‌نویسی Dagger2

Dagger2 چندد حاشیه‌نوشت ویژه دارد:

  • @Module: کلاسی که متدهای آن وابستگی‌ها را عرضه می‌کنند.
  • @Provides: برای متدهای داخل Module که وابستگی‌ها را عرضه می‌کنند.
  • @Inject: برای درخواست یک وابستگی
  • @Component: یک اینترفیس رابط بین ماژول‌ها و تزریق.

این‌ها مهم‌ترین حاشیه‌نوشت‌هایی است که در آغاز کار با Dagger2 لازم است با آن‌ها آشنا باشید. در این مطلب نحوه استفاده از این‌ها را در برنامه اندروید به شما آموزش خواهم داد.

۲-جریان کارها در Dagger2

برای پیاده‌سازی صحیح Dagger2 باید این گام‌ها را بردارید:

  • مشخص کردن شی وابسته و وابستگی‌های آن
  • ساختن یک کلاس با حاشیه‌نوشت @Module و استفاده از حاشیه‌نوشت @Provides برای هر متدی که یک وابستگی را برمی‌گرداند.
  • درخواست برای دسترسی به شی وابسته با استفاده از حاشیه نوشت‌ها
  • ساختن یک شی از روی اینترفیسی با حاشیه‌نوشت @Component و اضافه کردن کلاس‌های با حاشیه‌نوشت @Module که در گام دوم ساخته‌اید.

تحلیل وابستگی‌ها از زمان اجرا به زمان کامپایل تغییر می‌کند و این یعنی برعکس برخی از کتابخانه‌ها مانند Guice از مشکلات احتمالی در زمان توسعه آگاه می‌شوید. قبل از استفاده از کتابخانه Dagger2 باید اندروید استودیو را برای استفاده از کلاس‌های تولید شده توسط Dagger2 آماده کنید.

۳- آماده‌سازی محیط اندروید استودیو

گام ۱

با استفاده از اندروید استودیو پروژه جدیدی بسازید. من نام پروژه را TutsplusDagger گذاشتم.

dagger-2-type-a-name-for-the-project

گام ۲

Minimum SDK پروژه را ۱۰ بگذارید تا برنامه شما بر روی (تفریباً) همه دستگاه‌های اندروید موجود اجرا شود.

dagger-2-select-minimun-sdk

گام ۳

اکتیویتی خالی یا Blank را انتخاب کنید. برای این آموزش نیازی به اکتیویتی ویژه ندارید.

dagger-2-choose-template

گام ۴

نام اکتیویتی را MainActivity گذاشته و Finish را کلیک کنید.

dagger-2-name-for-activity

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

۴- تنظیم کردن گریدل

گام ۱

باید فایل build.gradle پروژه را به شکل زیر تغییر دهیم:

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.1'
        classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
    }
}

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

گام ۲

فایل build.gradle موجود در شاخه app پروژه را باز کنید و آن را مانند کد زیر تغییر دهید:

apply plugin: 'com.android.application'
apply plugin: 'com.neenbedankt.android-apt'

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        applicationId "com.androidheroes.tutsplusdagger"
        minSdkVersion 10
        targetSdkVersion 21
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:21.0.3'

    // Dagger dependencies
    apt "com.google.dagger:dagger-compiler:2.7"
    provided 'org.glassfish:javax.annotation:10.0-b28'
    compile "com.google.dagger:dagger:2.7"
}

در ابتدای فایل پلاگین جدید را معرفی کردم. مطمئن شوید که پلاگین جدید (com.neenbedankt.android-apt) پایین‌تر از پلاگین اندروید تعریف شده باشد. اگر این کار را نکنید، در زمان همگام سازی پروژه با فایل‌های بیلد گریدل با خطا روبرو خواهید شد.

در dependencies این‌ها را اضافه کردم:

  • کتابخانه Dagger2
  • کامپایلر Dagger برای تولید کد

حاشیه‌نوشت‌های javax.annotation برای حاشیه‌نوشت‌هایی که بیرون از Dagger به آن‌ها نیاز داریم.

بعد از بروزرسانی تنظیمات Dagger باید پروژه را با بیلد گریدل همگام کنید. برای این کار روی دکمه‌ای که در عکس زیر مشخص شده است کلیک کنید.

dagger-2-gradle-sync

حالا پروژه‌ای خالی دارید که می‌توانید از آن برای ساخت برنامه‌تان استفاده کنید. مطمئن شوید که گام‌های بالا را به درستی طی کرده‌اید. حالا می‌توانیم به سراغ پیاده‌سازی پروژه نمونه برویم.

۵- پیاده‌سازی Dagger2

گام ۱: تعیین اشیای وابسته

برای این آموزش، می‌خواهم با دو کلاس کار کنم، Vehicle (وسیله نقلیه) و Motor. کلاس Motor وابستگی ندارد ولی کلاس Vehicle کلاس وابسته است. ساخت کلاس‌های مدل را در یک پکیج یا بسته جدید به نام model آغاز می‌کنم.

کلاس Motor این شکلی است:

package com.androidheroes.tutsplusdagger.model;

/**
 * Created by kerry on 14/02/15.
 */
public class Motor {

    private int rpm;

    public Motor(){
        this.rpm = 0;
    }

    public int getRpm(){
        return rpm;
    }

    public void accelerate(int value){
        rpm = rpm + value;
    }

    public void brake(){
        rpm = 0;
    }
}

این کلاس فقط یک ویژگی دارد که rpm (دور در دقیقه) نامیده می‌شود که از آن در متدهای accelerate (گاز دادن و شتاب گرفتن) و brake (ترمز گرفتن) استفاده خواهیم کرد.

کلاس Vehicle این شکلی است:

package com.androidheroes.tutsplusdagger.model;

/**
 * Created by kerry on 14/02/15.
 */
public class Vehicle {

    private Motor motor;

    public Vehicle(Motor motor){
        this.motor = motor;
    }

    public void increaseSpeed(int value){
        motor.accelerate(value);
    }

    public void stop(){
        motor.brake();
    }

    public int getSpeed(){
        return motor.getRpm();
    }
}

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

گام ۲: ساخت کلاس @Module

حالا باید کلاسی با حاشیه‌نوشت @Module بسازیم. از این کلاس برای عرضه وابستگی‌ها استفاده می‌شود.. برای این کار یک پکیج جدید به نام module می‌سازیم (تا همه چیز را مرتب نگه داریم) و در داخل آن یک کلاس به شکل زیر اضافه می‌کنیم:

package com.androidheroes.tutsplusdagger.module;

import com.androidheroes.tutsplusdagger.model.Motor;
import com.androidheroes.tutsplusdagger.model.Vehicle;

import javax.inject.Singleton;

import dagger.Module;
import dagger.Provides;

/**
 * Created by kerry on 14/02/15.
 */

@Module
public class VehicleModule {

    @Provides @Singleton
    Motor provideMotor(){
        return new Motor();
    }

    @Provides @Singleton
    Vehicle provideVehicle(Motor motor){
        return new Vehicle(motor);
    }
}

همانطور که در گام اول گفتم Vehicle برای عملکرد درست به Motor وابسته است. به همین دلیل است که با دو تا provider نیاز داریم، یکی برای Motor (کلاس مدل ناوابسته) و دیگری برای Vehicle (که وابسته به Motor) است.

فراموش نکنید که هر provider یا متد باید حاشیه‌نوشت @Provides داشته باشد و خود کلاس هم حاشیه‌نوشت @Module. حاشیه‌نوشت @Singleton مشخص می‌کند که فقط یک نمونه از هر شی وجود داشته باشد.

گام ۳: درخواست برای وابستگی‌ها در کلاس وابسته

حالا که عرضه‌کننده (provider) برای کلاس‌های مدل دارید، باید آن‌ها را درخواست کنید. حاشیه‌نوشت @Inject بالای سازنده کلاس Vehicle به Dagger اعلام می‌کند که اشیای ساخته شده از این کلاس قابل تزریق به کلاس‌های دیگر هستند:

@Inject
public Vehicle(Motor motor){
    this.motor = motor;
}

می‌توانید از حاشیه‌نوشت @Inject برای درخواست وابستگی‌ها در متدهای سازنده، متغیرها و متدها استفاده کنید. در این مورد ما از تزریق وابستگی از طریق متد سازنده استفاده کردیم.

گام ۴- اتصال @Module ها با @Inject

ارتباط بین عرضه کننده وابستگی یعنی @Module و کلاس‌هایی که درخواست دسترسی به آن‌ها از طریق @Inject را دارند توسط @Component برقرار می‌شود که یک اینترفیس است:

package com.androidheroes.tutsplusdagger.component;

import com.androidheroes.tutsplusdagger.model.Vehicle;
import com.androidheroes.tutsplusdagger.module.VehicleModule;

import javax.inject.Singleton;

import dagger.Component;

/**
 * Created by kerry on 14/02/15.
 */

@Singleton
@Component(modules = {VehicleModule.class})
public interface VehicleComponent {

    Vehicle provideVehicle();

}

در کنار حاشیه‌نوشت @Component باید مشخص کنید که از چه ماژول‌هایی قرار است استفاده کنید. در این مورد من از VehicleModule استفاده می‌کنم که پیش از این ساخته‌ام. اگر از بیشتر از یک ماژول می‌خواهید استفاده کنید آن‌ها را با کاما (,) از هم جدا کنید.

درون این اینترفیس، برای هر شی‌ای که نیاز دارید یک متد اضافه کنید تا کتابخانه Dagger یک شی با همه وابستگی‌هایش به شما بدهد. در این مثال من یک شی Vehicle می‌خواهم، بنابراین فقط یک متد دارم.

گام ۵: استفاده از اینترفیس @Component برای گرفتن اشیا

حالا که همه اجزای کار فراهم است، می‌توانید یک نمونه از این اینترفیس بگیرید و برای دسترسی به اشیایی که می‌خواهید متدهای آن را صدا بزنید. من آن را در متد onCreate کلاس MainActivity به شکل زیر پیاده‌سازی می‌کنم:

package com.androidheroes.tutsplusdagger;

import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.widget.Toast;

import com.androidheroes.tutsplusdagger.component.DaggerVehicleComponent;
import com.androidheroes.tutsplusdagger.component.VehicleComponent;
import com.androidheroes.tutsplusdagger.model.Vehicle;
import com.androidheroes.tutsplusdagger.module.VehicleModule;

public class MainActivity extends ActionBarActivity {

    Vehicle vehicle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        VehicleComponent component = DaggerVehicleComponent.builder().vehicleModule(new VehicleModule()).build();

        vehicle = component.provideVehicle();

        Toast.makeText(this, String.valueOf(vehicle.getSpeed()), Toast.LENGTH_SHORT).show();
    }
}

وقتی که سعی می‌کنید یک شی از روی اینترفیسی با حاشیه‌نوشت @Component بسازید، باید از پیشوند Dagger استفاده کنید (در این مثال: DaggerVehicleComponent) و سپس از متد builder برای صدا زدن ماژول‌های داخل آن استفاده کنید.

جادوی کار در خط ۲۳ اتفاق می‌افتد. ما فقط یک شی از نوع Vehicle را درخواست کردیم و مسئولیت همه وابستگی‌های آن را Dagger2 به عهده گرفت. اگر خوب دقت کنید می‌بینید که از روی هیچ شی‌ای نمونه‌ای ساخته نشده و مدیریت همه کارها با Dagger2 است.

حالا می‌توانید برنامه را اجرا کنید و نتیجه کار را در دستگاه یا شبیه‌ساز ببینید. اگر کلیه مراحل این آموزش را گام به گام طی کرده باشید، می‌بینید که یک پیغام Toast نمایش داده می‌شود که اطلاعات پیش‌فرض متغیر rpm در آن است.

مؤخره

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

4 فکر می‌کنند “آموزش اندروید-فصل ۲۸-۵: تزریق وابستگی با Dagger2

  1. یونس

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

    پاسخ
  2. مهدی

    سلام و درود بی کران خدمت شما
    سایتتون بسیار عالی و اموزنده هست ممنون که مطالب رو بدون هیچ چشم داشتی به اشتراک میذارین
    میخواستم بدونم درباره معماری mvvm مطلبی میذارین توی سایت؟

    پاسخ
  3. جواد

    متاسفانه در گام ۲ یک اشتباه رخ داده

    Vehicle provideVehicle(){
    return new Vehicle(new Motor());
    }

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

    @Provides @Singleton
    Vehicle provideVehicle(Motor motor){
    return new Vehicle(motor);
    }

    ممنون میشم اصلاح کنید.

    پاسخ

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

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