تخفیف ویژه

آموزش dagger در اندروید

دسته بندی: برنامه نویسی
سطح مقاله: پیشرفته
زمان مطالعه: 34 دقیقه
۲۳ مرداد ۱۳۹۹

تزریق وابستگی یا Dependency Injection بسته به اندازه‌ی پروژه‌های اندرویدی شما می‌تواند مشکل ساز باشد و برای همین Dagger مدتی هست که معرفی شده است و برنامه نویسان اندروید با فریم ورک Dagger کار کرده‌اند یا حداقل چیزی درباره‌اش شنیده‌اند. در مقاله آموزش dagger در اندروید ما با ویژگی‌ها و کارایی‌های آن آشنا می‌شویم. همراه ما باشید تا با آموزش Dagger در اندروید، اطلاعاتی درباره‌ی آن کسب کنید و بتوانید با آن کار کنید و از ویژگی‌های آن بهره ببرید.

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

dagger در اندروید چیست

به طور خلاصه ابزاری برای تولید کدی جهت مدیریت وابستگی بین اشیاء مختلف است.

Dagger یک Dependency Injector برای جاوا و اندروید می‌باشد. Dagger یک فریم ورک (Framework) برای تزریق وابستگی (Dependency Injection) می‌باشد که در زمان کامپایل (Compile) اجرا می‌شود. Dagger از کدها به هیچ عنوان در زمان اجرا (runtime) استفاده نمی‌کند، تمام تحلیل‌های خود را در زمان کامپایل انجام می‌دهد و کد منبع جاوا را تولید می‌کند.

همان‌طور که می‌دانید Dagger با استفاده از حاشیه نویسی (annotations) کار می‌کند، که به آن کمک می‌کند تا تمام کد مورد نیاز برای کار را بخواند و تولید کند. همه‌ی حاشیه نویسی‌های مختلفی که در Dagger استفاده می‌شود، راهی برای گفتن آن چیز هایی هست که Dagger نمی‌داند و برای رسیدن به هدف خود و ایجاد کدی که در غیر این صورت باید خودتان بنویسید از Annotations استفاده می‌شود.

تزریق وابستگی به شما مزایای زیر را ارائه می‌دهد:
  • قابلیت استفاده مجدد از کدهای نوشته شده
  • سهولت در اصلاح کد
  • سهولت در تست نویسی

آموزش Dagger در اندروید

تفاوت‌های Dagger 1 با Dagger 2 

به طور خلاصه Dagger 2 نسخه‌ی بهبود یافته‌ی Dagger 1 می‌باشد و مشکلاتی را که درون Dagger 1 بود تا حدودی حل کرده است.

مشکلات dagger 1:

  • Ugly generated code
  • Runtime graph composition
  • Inefficient graph creation
  • Partial traceability
  • Map-like API

و راه حل‌های Dagger 2 برای مشکل‌های Dagger 1:

  • Compile-time validation of the entire graph
  • Easy debugging; entirely concrete call stack for provision and creation
  • Fully traceable
  • POJO API
  • Performance

و از مشکلات Dagger 2 می‌توان به انعطاف پذیری کم آن اشاره کرد.

Dagger 2 از Annotationsهای زیر استفاده می‌کند :

  • [email protected] و [email protected] : کلاس‌ها و متد هایی را برای وابستگی به ما تحویل می‌دهند.
  • [email protected] : درخواست dependencies می‌باشد که قابل استفاده در field ،constructor یا method است.
  • [email protected] : ماژول‌های انتخاب شده را فعال کرده و برای انجام تزریق وابستگی استفاده می‌شود.

Dagger 2 از کد تولید شده برای دستیابی به فیلدها استفاده می‌کند. بنابراین استفاده از فیلد‌های خصوصی برای تزریق field مجاز نیست.

آموزش Dagger در اندروید

تعریف ارائه دهندگان وابستگی ((Defining dependency providers(object providers)

اصطلاح dependency injection context معمولاً برای توصیف مجموعه اشیاء قابل تزریق استفاده می‌شود.

در Dagger 2، کلاس هایی که با [email protected] حاشیه نویسی شده اند، مسئولیت تهیه‌ی اشیاء قابل تزریق را دارند. چنین کلاس هایی می‌توانند متد هایی را که با [email protected] حاشیه نویسی شده است، تعریف کنند. اشیاء برگشت یافته از این متدها برای تزریق وابستگی در دسترس هستند.

با [email protected] متدهای حاشیه نویسی می‌توانند وابستگی را از طریق پارامترهای متد بیان کنند. این وابستگی‌ها در صورت امکان توسط Dagger 2 برآورده می‌شوند.

تعریف وابستگی  ((Defining dependencies (object consumers)

شما از حاشیه نویسی [email protected] برای تعریف یک وابستگی استفاده می‌کنید. اگر یک Constructor را با [email protected] حاشیه نویسی کنید، Dagger 2 می‌تواند نمونه ای از این شی را برای تحقق وابستگی‌ها استفاده کند. این کار برای جلوگیری از ساخت بیشتر متدهای Provides برای این اشیاء انجام شده است.

ارتباط Consumers و ارائه دهندگان

 در یک Interface استفاده می‎شود. چنین رابطی توسط Dagger 2 برای تولید کد استفاده می‌شود. الگوی پایه برای کلاس تولید شده این است که، Dagger به عنوان پیشوند و پس از آن با نام رابط استفاده می‌شود. این یک کلاس تولید می‌کند که یک متد ساخته و امکان پیکربندی اشیاء را بر اساس پیکربندی داده شده فراهم می‌کند. متدهای تعریف شده در Interface برای دسترسی به اشیاء تولید شده در دسترس هستند.

یک Interface @Component  ارتباط بین ارائه دهنده‌ی اشیاء (ماژول ها) و اشیاء را بیان می‌کند که وابستگی را بیان می‌کند.

Scope annotations

می توانید از حاشیه نویسی [email protected] استفاده کنید تا نشان دهید فقط باید یک نمونه از شی وجود داشته باشد.

فیلد‌ها در Dagger

Dagger 2 به صورت خودکار فیلدها را تزریق نمی‌کند. همچنین نمی‌تواند فیلد‌های خصوصی را تزریق کند. اگر می‌خواهید از تزریق فیلد استفاده کنید، باید روشی را در رابط  [email protected] خود تعریف کنید که نمونه ای از آن را به عنوان پارامتر تزریق می‌کند.

استفاده از Dagger در اندروید استودیو

برای فعال کردن Dagger 2 وابستگی‌های زیر را به پرونده‌ی build.gradle خود اضافه کنید.
implementation 'com.google.dagger:dagger:2.15'
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
بسیاری از مؤلفه‌های اندروید (Android Components)، به عنوان مثال Activities، توسط چارچوب Android انجام می‌شود و در کد ما نیست. این امر باعث می‌شود تامین وابستگی از طریق Constructorها به مؤلفه‌های Android دشوار شود.
اگر می‌خواهید از کلاس‌های پکیج dagger.android مانند کلاس DaggerActivity استفاده کنید، وابستگی‌های زیر را نیز به پرونده‌ی build.gradle خود اضافه کنید و اگر می‌خواهید Activities components مانند Activities یا Fragments را تزریق کنید، این مورد نیز لازم است.
implementation 'com.google.dagger:dagger-android:2.15'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.10'
اگر می‌خواهید از Support Library با Dagger 2 استفاده کنید، باید مورد زیر را نیز به build.gradle اضافه کنید.
implementation 'com.google.dagger:dagger-android-support:2.10'
اگر خطای مشابه را دریافت می‌کنید: (Conflict with dependency 'com.google.code.findbugs:jsr305' in project ':app')
می‌توانید مورد زیر را به پرونده‌ی app / build.gradle خود اضافه کنید.
android {
    configurations.all {
        resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
    }
}

مثالی از Dagger به همراه RxJava و Retrofit با معماری MVVM

پیکربندی پروژه:
def supportVersion = '27.1.1'
def retrofitVersion = '2.3.0'
def rxJavaVersion = '2.0.1'
def butterKnifeVersion = '8.8.1'
def daggerVersion = '2.15'

dependencies {
    ...

    implementation "android.arch.lifecycle:extensions:1.1.1"

    implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-gson:$retrofitVersion"
    implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofitVersion"
    implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion"

    implementation "io.reactivex.rxjava2:rxandroid:$rxJavaVersion"
    implementation "io.reactivex.rxjava2:rxjava:$rxJavaVersion"

    implementation "com.jakewharton:butterknife:$butterKnifeVersion"
    implementation 'com.android.support:support-v4:27.1.1'
    annotationProcessor "com.jakewharton:butterknife-compiler:$butterKnifeVersion"

    implementation "com.google.dagger:dagger:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-compiler:$daggerVersion"
    implementation "com.google.dagger:dagger-android-support:$daggerVersion"
    annotationProcessor "com.google.dagger:dagger-android-processor:$daggerVersion"

    ...
}
ما به صورت Package by Feature کار خواهیم کرد. این باعث می‌شود کد شما modular و قابل کنترل‌تر شود.

Dagger چیست

راه اندازی Retrofit

public interface RepoService {

    @GET("orgs/Google/repos")
    Single<List<Repo>> getRepositories();

    @GET("repos/{owner}/{name}")
    Single<Repo> getRepo(@Path("owner") String owner, @Path("name") String name);
}
Base Fragment ،Base Activity ،Application Class را تنظیم می‌کنیم.

BaseApplication.java

public class BaseApplication extends DaggerApplication {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
        ApplicationComponent component = DaggerApplicationComponent.builder().application(this).build();
        component.inject(this);

        return component;
    }
}

BaseActivity.java

public abstract class BaseActivity extends DaggerAppCompatActivity {

    @LayoutRes
    protected abstract int layoutRes();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(layoutRes());
        ButterKnife.bind(this);
    }
}
ما از  abstract LayoutRes() function برای دادن Id طرح (Resource Layout) استفاده می‌کنیم.

BaseFragment.java

public abstract class BaseFragment extends DaggerFragment {

    private Unbinder unbinder;
    private AppCompatActivity activity;

    @LayoutRes
    protected abstract int layoutRes();

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(layoutRes(), container, false);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        activity = (AppCompatActivity) context;
    }

    @Override
    public void onDetach() {
        super.onDetach();
        activity = null;
    }

    public AppCompatActivity getBaseActivity() {
        return activity;
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        if(unbinder != null) {
            unbinder.unbind();
            unbinder = null;
        }
    }
}
Dagger 2 Component & Modules را تنظیم می‌کنیم.

ApplicationComponent.java

@Singleton
@Component(modules = {ContextModule.class, ApplicationModule.class, AndroidSupportInjectionModule.class, ActivityBindingModule.class})
public interface ApplicationComponent extends AndroidInjector<DaggerApplication> {

    void inject(BaseApplication application);

    @Component.Builder
    interface Builder {
        @BindsInstance
        Builder application(Application application);
        ApplicationComponent build();
    }
}
همان‌طور که مشاهده می‌کنید، ما فقط AndroidSupportInjectionModule ، ActivityBindingModule و ViewModelModule را در پارامتر ماژول نوشتیم. ما ماژول‌های مورد نیاز دیگر را که Activity یا Fragment نیاز خواهد داشت، خواهیم نوشت.

ActivityBindingModule.java

@Module
public abstract class ActivityBindingModule {

    @ContributesAndroidInjector(modules = {MainFragmentBindingModule.class})
    abstract MainActivity bindMainActivity();
}

MainFragmentBindingModule.java

@Module
public abstract class MainFragmentBindingModule {

    @ContributesAndroidInjector
    abstract ListFragment provideListFragment();

    @ContributesAndroidInjector
    abstract DetailsFragment provideDetailsFragment();
}

ApplicationModule.java

@Singleton
@Module(includes = ViewModelModule.class)
public class ApplicationModule {

    private static final String BASE_URL = "https://api.github.com/";

    @Singleton
    @Provides
    static Retrofit provideRetrofit() {
        return new Retrofit.Builder().baseUrl(BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }

    @Singleton
    @Provides
    static RepoService provideRetrofitService(Retrofit retrofit) {
        return retrofit.create(RepoService.class);
    }
}

ContextModule.java

@Module
public abstract class ContextModule {

    @Binds
    abstract Context provideContext(Application application);
}

ViewModelModule.java

@Singleton
@Module
abstract class ViewModelModule {

    @Binds
    @IntoMap
    @ViewModelKey(ListViewModel.class)
    abstract ViewModel bindListViewModel(ListViewModel listViewModel);

    @Binds
    @IntoMap
    @ViewModelKey(DetailsViewModel.class)
    abstract ViewModel bindDetailsViewModel(DetailsViewModel detailsViewModel);

    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory factory);
}

ساخت Custom ViewModel Factory

ViewModelFactory کارخانه‌ای است که ViewModelProvider.Factive را به منظور ارائه‌ی نمونه‌های ViewModel به کلاس‌های Fragment مصرف کننده گسترش می‌دهد. ما آن کلاس را با Inject ،ViewModelModule کرده‌ایم.

ViewModelFactory.java

@Singleton
public class ViewModelFactory implements ViewModelProvider.Factory {

    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    public ViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @NonNull
    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
ViewModel را راه اندازی می‌کنیم.

ListViewModel.java

public class ListViewModel extends ViewModel {

    private final RepoRepository repoRepository;
    private CompositeDisposable disposable;

    private final MutableLiveData<List<Repo>> repos = new MutableLiveData<>();
    private final MutableLiveData<Boolean> repoLoadError = new MutableLiveData<>();
    private final MutableLiveData<Boolean> loading = new MutableLiveData<>();

    @Inject
    public ListViewModel(RepoRepository repoRepository) {
        this.repoRepository = repoRepository;
        disposable = new CompositeDisposable();
        fetchRepos();
    }

    LiveData<List<Repo>> getRepos() {
        return repos;
    }
    LiveData<Boolean> getError() {
        return repoLoadError;
    }
    LiveData<Boolean> getLoading() {
        return loading;
    }

    private void fetchRepos() {
        loading.setValue(true);
        disposable.add(repoRepository.getRepositories().subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread()).subscribeWith(new DisposableSingleObserver<List<Repo>>() {
                    @Override
                    public void onSuccess(List<Repo> value) {
                        repoLoadError.setValue(false);
                        repos.setValue(value);
                        loading.setValue(false);
                    }

                    @Override
                    public void onError(Throwable e) {
                        repoLoadError.setValue(true);
                        loading.setValue(false);
                    }
                }));
    }

    @Override
    protected void onCleared() {
        super.onCleared();
        if (disposable != null) {
            disposable.clear();
            disposable = null;
        }
    }
}
Fragment را ایجاد می‌کنیم.

ListFragment.java

public class ListFragment extends BaseFragment implements RepoSelectedListener {

    @BindView(R.id.recyclerView) RecyclerView listView;
    @BindView(R.id.tv_error) TextView errorTextView;
    @BindView(R.id.loading_view) View loadingView;

    @Inject ViewModelFactory viewModelFactory;
    private ListViewModel viewModel;

    @Override
    protected int layoutRes() {
        return R.layout.screen_list;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(ListViewModel.class);

        listView.addItemDecoration(new DividerItemDecoration(getBaseActivity(), DividerItemDecoration.VERTICAL));
        listView.setAdapter(new RepoListAdapter(viewModel, this, this));
        listView.setLayoutManager(new LinearLayoutManager(getContext()));

        observableViewModel();
    }

    @Override
    public void onRepoSelected(Repo repo) {
        DetailsViewModel detailsViewModel = ViewModelProviders.of(getBaseActivity(), viewModelFactory).get(DetailsViewModel.class);
        detailsViewModel.setSelectedRepo(repo);
        getBaseActivity().getSupportFragmentManager().beginTransaction().replace(R.id.screenContainer, new DetailsFragment())
                .addToBackStack(null).commit();
    }

    private void observableViewModel() {
        viewModel.getRepos().observe(this, repos -> {
            if(repos != null) listView.setVisibility(View.VISIBLE);
        });

        viewModel.getError().observe(this, isError -> {
            if (isError != null) if(isError) {
                errorTextView.setVisibility(View.VISIBLE);
                listView.setVisibility(View.GONE);
                errorTextView.setText("An Error Occurred While Loading Data!");
            }else {
                errorTextView.setVisibility(View.GONE);
                errorTextView.setText(null);
            }
        });

        viewModel.getLoading().observe(this, isLoading -> {
            if (isLoading != null) {
                loadingView.setVisibility(isLoading ? View.VISIBLE : View.GONE);
                if (isLoading) {
                    errorTextView.setVisibility(View.GONE);
                    listView.setVisibility(View.GONE);
                }
            }
        });
    }
}
RecyclerView Adapter را ایجاد می‌کنیم.

RepoListAdapter.java

public class RepoListAdapter extends RecyclerView.Adapter<RepoListAdapter.RepoViewHolder>{

    private RepoSelectedListener repoSelectedListener;
    private final List<Repo> data = new ArrayList<>();

    RepoListAdapter(ListViewModel viewModel, LifecycleOwner lifecycleOwner, RepoSelectedListener repoSelectedListener) {
        this.repoSelectedListener = repoSelectedListener;
        viewModel.getRepos().observe(lifecycleOwner, repos -> {
            data.clear();
            if (repos != null) {
                data.addAll(repos);
                notifyDataSetChanged();
            }
        });
        setHasStableIds(true);
    }

    @NonNull
    @Override
    public RepoViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.view_repo_list_item, parent, false);
        return new RepoViewHolder(view, repoSelectedListener);
    }

    @Override
    public void onBindViewHolder(@NonNull RepoViewHolder holder, int position) {
        holder.bind(data.get(position));
    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    @Override
    public long getItemId(int position) {
        return data.get(position).id;
    }

    static final class RepoViewHolder extends RecyclerView.ViewHolder {

        @BindView(R.id.tv_repo_name) TextView repoNameTextView;
        @BindView(R.id.tv_repo_description) TextView repoDescriptionTextView;
        @BindView(R.id.tv_forks) TextView forksTextView;
        @BindView(R.id.tv_stars) TextView starsTextView;

        private Repo repo;

        RepoViewHolder(View itemView, RepoSelectedListener repoSelectedListener) {
            super(itemView);
            ButterKnife.bind(this, itemView);
            itemView.setOnClickListener(v -> {
                if(repo != null) {
                    repoSelectedListener.onRepoSelected(repo);
                }
            });
        }

        void bind(Repo repo) {
            this.repo = repo;
            repoNameTextView.setText(repo.name);
            repoDescriptionTextView.setText(repo.description);
            forksTextView.setText(String.valueOf(repo.forks));
            starsTextView.setText(String.valueOf(repo.stars));
        }
    }
}
جمعبندی :
در مقاله آموزش Dagger در اندروید ، با Dagger و کارایی آن آشنا شدیم و متوجه شدیم که استفاده از Dagger چه مزایایی برای توسعه اپلیکیشن‌های اندرویدی دارد. اگر درباره‌ی این مقاله سوال و نظری دارید خوشحال می‌شویم که با ما و کاربران سون لرن به  اشتراک بگذارید.

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

چه امتیازی به این مقاله می دید؟

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

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

ارسال دیدگاه
خوشحال میشیم دیدگاه و یا تجربیات خودتون رو با ما در میون بذارید :