تزریق وابستگی یا 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های زیر استفاده می‌کند :

  • Module@ و Provides@ : کلاس‌ها و متد هایی را برای وابستگی به ما تحویل می‌دهند.
  • Inject@ : درخواست dependencies می‌باشد که قابل استفاده در field ،constructor یا method است.
  • Component@ : ماژول‌های انتخاب شده را فعال کرده و برای انجام تزریق وابستگی استفاده می‌شود.

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

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

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

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

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

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

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

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

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

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

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

Scope annotations

می توانید از حاشیه نویسی Singleton@ استفاده کنید تا نشان دهید فقط باید یک نمونه از شی وجود داشته باشد.

فیلد‌ها در Dagger

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

استفاده از 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 چه مزایایی برای توسعه اپلیکیشن‌های اندرویدی دارد. اگر درباره‌ی این مقاله سوال و نظری دارید خوشحال می‌شویم که با ما و کاربران سون لرن به  اشتراک بگذارید.

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

 
ارسال دیدگاه
ما همه سوالات و دیدگاه ها رو می خونیم و پاسخ میدیم
۱۱ دیدگاه
همایون احمدی ۱۵ مهر ۱۴۰۰، ۱۹:۱۱
سلام ممنون از آموزش جامع و خوبتون. کتابخانه ButterKnife ، اخیرا deprecate شده و بهتره از دیتا بایندینگ (Data Binding) استفاده بشه که هم خیلی راحت تره هم خوانایی کد رو افزایش میده.
سعید ۲۳ خرداد ۱۴۰۰، ۰۱:۴۶
مثالی که در آخر بحث آوردین خیلی پیچیده تر از سطح توضیحات اولیه مقالس. بیشتر قسمتهای مربوط به مثال اصلا دلیلی برای استفاده از کلاسهای خاص داده نشده
مقاله خیلی کلی و بد جمع بندی شده
نه توضیحات اولیه تکمیله خیلی سطحی و گذرا و نه بخشهای مهم مثال توضیحی داره که بشه فهمید اصلا برای چی این مدلی جلو رفته چرا فلان کلاس رو استفاده میکنه و ...
علیرضا ۲۷ اردیبهشت ۱۴۰۰، ۰۲:۱۴
درسته که مبحث پیشرفته ای هستش ولی گفتن اینکه دقیقا dagger چیه و کجا کاربرد داره رو نمیشه انقدر پیچیده کردش! اصلا متوجه مطلب نشدم، حس میکنم کلا کار رو سپردید به گوگل ترنسلیت!
کامنتای دیگه رو میخوندم زیر همشون اضافه کردید مبحث پیشرفته ای هستش نیازه خودتون یه بار کدارو اجرا کنید و درک کنید، بجز این پاسخ، ممنون میشم اگر یک جواب واضح یا منبعی واضح راجب اینکه dagger دقیقا چی هست بهم بدید.
نازنین کریمی مقدم ۲۸ اردیبهشت ۱۴۰۰، ۰۱:۲۱
درود.
اگر کامنت آخر که در جواب امیر عزیز رو خونده باشید، درمورد اینکه dagger دقیقا چیه و چجوری استفاده میشه توضیح دادم. اما باز در این کامنت تکرار میکنم.
در اصول بهینه کدنویسی معمولا مطرح میشه که بهتره که هیچ کلاسی از کلاس دیگری شی یا نمونه ای نسازه و بجاش بیاد شی مورد نیاز خودش رو از یک کلاس واسط که به همین منظور ساخته شده است دریافت بکنه. به استفاده از این کلاس واسط تزریق وابستگی میگیم و dagger یکی از ابزارهایی هست که این کار رو برامون انجام میده. پس در اصل کتابخانه dagger به منظور تزریق وابستگی استفاده میشه.
برای چگونگی دقیقش هم کد رو گذاشتیم.
امیر ۲۲ اردیبهشت ۱۴۰۰، ۱۹:۵۱
اصلا متجه نمیشم دلیل استفاده چی میتونه باشه و اگه استفاده نشه چه اتفاقی میفته ؟ و به کارگیریش همراه mvvm چجوری هست
نازنین کریمی مقدم ۲۵ اردیبهشت ۱۴۰۰، ۰۳:۴۱
درود
ببینید در اصول بهینه کدنویسی معمولا مطرح میشه که بهتره که هیچ کلاسی از کلاس دیگری شی یا نمونه ای نسازه و بجاش بیاد شی مورد نیاز خودش رو از یک کلاس واسط که به همین منظور ساخته شده است دریافت بکنه. به استفاده از این کلاس واسط تزریق وابستگی میگیم و dagger یکی از ابزارهایی هست که این کار رو برامون انجام میده.
ما یه مثال از dagger و mvvm در مقاله زدیم، با این حال برای فهم دقیق تر توصیه میکنم چندین نمونه پیاده سازی در اینترنت رو ببینید. مبحث کاملا پیشرفته هست و تمرین و دانش خوبی رو میطلبه.
هادی ۳۰ فروردین ۱۴۰۰، ۱۱:۵۳
سلام خسته نباشید
صرفا خاستم یه تشکر کنم بابت مطالبی که گذاشتید تویه سایتتون،نسبت به سایت های فارسیه دیگه که دیدم خیلی خیلی بهتر و واضح تره آموزش هاتون،البته برا کسی که دانشی از برنامه نویسی اندروید داره،قسمت معماری MVVM رو خیلی حال کردم
مسعود ۲۵ فروردین ۱۴۰۰، ۰۸:۵۹
فقط يه مقاله از لغت به لغت و خيلي آماتور ترجمه شده. مثل يه كار ترجمه دانش آموزي ميمونه(نه حتي دانشجويي) كه فقط ميخواسته نمره بگيره..
نازنین کریمی مقدم ۲۵ فروردین ۱۴۰۰، ۱۶:۴۵
درود
سطح این مقاله پیشرفته هست و زمان نسبتا خوبی رو باید برای درک کدها بگذارید و خودتون یکبار به صورت عملی انجام بدید تا بهتر متوجه شید. اگر جایی از مقاله رو متوجه نشدید، ما در بخش کامنتها به سوالات تون جواب میدیم و اگر محتوایی باید به مقاله اضافه بشه، حتما اون رو ویرایش میکنیم.
اصغر ۱۹ دی ۱۳۹۹، ۱۹:۱۴
واقعا کارتون خیلی ضعیف هست
استفاده از گوگل ترنسلیت و نوشتن صرفا یه پست
قول میدم حتی خودتون هم یکبار از روش نخوندید
درسته پست رایگان هست، و نباید شما رو سرزنش کرد، ولی انجام ندادن بعضی کارها از انجام دادنشون بهتره ( درست نکردن پست هایی بی اثر مثل این )
نازنین کریمی مقدم ۱۹ دی ۱۳۹۹، ۲۰:۴۶
درود.
دوست عزیز تمامی مقالات ما حداقل یکبار توسط تیم ویراستاری هم بررسی می شوند و سعی میکنیم به صورت شفاف هر موضوعی رو آموزش بدیم.
سطح این مقاله پیشرفته هست و زمان نسبتا خوبی رو باید برای درک کدها بگذارید و خودتون یکبار به صورت عملی انجام بدید تا بهتر متوجه شید. اگر جایی از مقاله رو متوجه نشدید، ما در بخش کامنتها به سوالات تون جواب میدیم و اگر محتوایی باید به مقاله اضافه بشه، حتما اون رو ویرایش میکنیم.
ما همواره از نظرات شما استقبال میکنیم، اما انجام ندادن بعضی کارها از انجام دادنشون بهتره (قضاوت بیجا با ایمیل فیک مثل این :) )