تزریق وابستگی یا Dependency Injection بسته به اندازهی پروژههای اندرویدی شما میتواند مشکل ساز باشد و برای همین Dagger مدتی هست که معرفی شده است و برنامه نویسان اندروید با فریم ورک Dagger کار کردهاند یا حداقل چیزی دربارهاش شنیدهاند. در مقاله آموزش dagger در اندروید ما با ویژگیها و کاراییهای آن آشنا میشویم. همراه ما باشید تا با آموزش Dagger در اندروید، اطلاعاتی دربارهی آن کسب کنید و بتوانید با آن کار کنید و از ویژگیهای آن بهره ببرید.
به طور خلاصه ابزاری برای تولید کدی جهت مدیریت وابستگی بین اشیاء مختلف است.
Dagger یک Dependency Injector برای جاوا و اندروید میباشد. Dagger یک فریم ورک (Framework) برای تزریق وابستگی (Dependency Injection) میباشد که در زمان کامپایل (Compile) اجرا میشود. Dagger از کدها به هیچ عنوان در زمان اجرا (runtime) استفاده نمیکند، تمام تحلیلهای خود را در زمان کامپایل انجام میدهد و کد منبع جاوا را تولید میکند.
همانطور که میدانید Dagger با استفاده از حاشیه نویسی (annotations) کار میکند، که به آن کمک میکند تا تمام کد مورد نیاز برای کار را بخواند و تولید کند. همهی حاشیه نویسیهای مختلفی که در Dagger استفاده میشود، راهی برای گفتن آن چیز هایی هست که Dagger نمیداند و برای رسیدن به هدف خود و ایجاد کدی که در غیر این صورت باید خودتان بنویسید از Annotations استفاده میشود.
به طور خلاصه Dagger 2 نسخهی بهبود یافتهی Dagger 1 میباشد و مشکلاتی را که درون Dagger 1 بود تا حدودی حل کرده است.
مشکلات dagger 1:
و راه حلهای Dagger 2 برای مشکلهای Dagger 1:
و از مشکلات Dagger 2 میتوان به انعطاف پذیری کم آن اشاره کرد.
Dagger 2 از Annotationsهای زیر استفاده میکند :
Dagger 2 از کد تولید شده برای دستیابی به فیلدها استفاده میکند. بنابراین استفاده از فیلدهای خصوصی برای تزریق field مجاز نیست.
اصطلاح dependency injection context معمولاً برای توصیف مجموعه اشیاء قابل تزریق استفاده میشود.
در Dagger 2، کلاس هایی که با Module@ حاشیه نویسی شده اند، مسئولیت تهیهی اشیاء قابل تزریق را دارند. چنین کلاس هایی میتوانند متد هایی را که با Provides@ حاشیه نویسی شده است، تعریف کنند. اشیاء برگشت یافته از این متدها برای تزریق وابستگی در دسترس هستند.
با Provides@ متدهای حاشیه نویسی میتوانند وابستگی را از طریق پارامترهای متد بیان کنند. این وابستگیها در صورت امکان توسط Dagger 2 برآورده میشوند.
یک Interface @Component ارتباط بین ارائه دهندهی اشیاء (ماژول ها) و اشیاء را بیان میکند که وابستگی را بیان میکند.
implementation 'com.google.dagger:dagger:2.15'
annotationProcessor 'com.google.dagger:dagger-compiler:2.10'
implementation 'com.google.dagger:dagger-android:2.15'
annotationProcessor 'com.google.dagger:dagger-android-processor:2.10'
implementation 'com.google.dagger:dagger-android-support:2.10'
android {
configurations.all {
resolutionStrategy.force 'com.google.code.findbugs:jsr305:1.3.9'
}
}
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"
...
}
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);
}
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;
}
}
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);
}
}
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;
}
}
}
@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();
}
}
@Module
public abstract class ActivityBindingModule {
@ContributesAndroidInjector(modules = {MainFragmentBindingModule.class})
abstract MainActivity bindMainActivity();
}
@Module
public abstract class MainFragmentBindingModule {
@ContributesAndroidInjector
abstract ListFragment provideListFragment();
@ContributesAndroidInjector
abstract DetailsFragment provideDetailsFragment();
}
@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);
}
}
@Module
public abstract class ContextModule {
@Binds
abstract Context provideContext(Application application);
}
@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);
}
@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);
}
}
}
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;
}
}
}
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);
}
}
});
}
}
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));
}
}
}
اگر به یادگیری بیشتر در زمینهی برنامه نویسی اندروید علاقه داری، با شرکت در دورهی آموزش برنامه نویسی اندروید در کمتر از یکسال به یک توسعهدهنده اندروید همه فن حریف تبدیل میشوی که آمادهی استخدام، دریافت پروژه و حتی پیادهسازی اپلیکیشن خودت هستی.
آخه این چه طرز توضیح دادنه
دیفالت گوگل ترنسلیت بهتر ترجمه میکرد.