
@Module and @Provides .
RecyclerView . I will not spend much time explaining the project, I will explain in the abstract. But please disassemble the code carefully, so that the implementation of Dagger 2 in the project will be as clear and simple for you as possible.MainActivity.java - Makes requests to the API and shows the items received in RecyclerView .Model - POJO for the response from the API, created using JSON Schema to POJORandomUsersAdapter.java - Adapter for RecyclerViewRetrofit - for API callsGsonBuilder and Gson - for working with JSONHttpLoggingInterceptor - for logging network operationsOkHttpClient - client for RetrofitPicasso - working with images in the AdapterMainActivity . And each time you create a MainActivity , dependency instances will be created again and again. public class MainActivity extends AppCompatActivity { Retrofit retrofit; RecyclerView recyclerView; RandomUserAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); GsonBuilder gsonBuilder = new GsonBuilder(); Gson gson = gsonBuilder.create(); Timber.plant(new Timber.DebugTree()); HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(@NonNull String message) { Timber.i(message); } }); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient okHttpClient = new OkHttpClient() .newBuilder() .addInterceptor(httpLoggingInterceptor) .build(); retrofit = new Retrofit.Builder() .client(okHttpClient) .baseUrl("https://randomuser.me/") .addConverterFactory(GsonConverterFactory.create(gson)) .build(); populateUsers(); } private void initViews() { recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); } private void populateUsers() { Call<RandomUsers> randomUsersCall = getRandomUserService().getRandomUsers(10); randomUsersCall.enqueue(new Callback<RandomUsers>() { @Override public void onResponse(Call<RandomUsers> call, @NonNull Response<RandomUsers> response) { if(response.isSuccessful()) { mAdapter = new RandomUserAdapter(); mAdapter.setItems(response.body().getResults()); recyclerView.setAdapter(mAdapter); } } @Override public void onFailure(Call<RandomUsers> call, Throwable t) { Timber.i(t.getMessage()); } }); } public RandomUsersApi getRandomUserService(){ return retrofit.create(RandomUsersApi.class); } } MainActivity , you will notice the following problems:onCreate() method, you may find awkward initialization inside it. Of course, we can continue to initialize objects in this way in this way, but it is better to find the right way to solve the problem.Picasso inside the Adapter also interferes with testing capabilities. It would be nice to pass this dependency through the constructor. public class RandomUserAdapter extends RecyclerView.Adapter<RandomUserAdapter.RandomUserViewHolder> { private List<Result> resultList = new ArrayList<>(); public RandomUserAdapter() { } @Override public RandomUserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item_random_user, parent, false); return new RandomUserViewHolder(view); } @Override public void onBindViewHolder(RandomUserViewHolder holder, int position) { Result result = resultList.get(position); holder.textView.setText(String.format("%s %s", result.getName().getFirst(), result.getName().getLast())); Picasso.with(holder.imageView.getContext()) .load(result.getPicture().getLarge()) .into(holder.imageView); } ...... MainActivity class were needed only for you to get a little insight into the project and feel comfortable. If you go deeper, then as in any real project dependencies will be more. Let's add some more.File - to store the cacheCache - for network cacheOkHttp3Downloader - downloader using OkHttpClient to upload imagesPicasso - for processing images from the network public class MainActivity extends AppCompatActivity { Retrofit retrofit; RecyclerView recyclerView; RandomUserAdapter mAdapter; Picasso picasso; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initViews(); GsonBuilder gsonBuilder = new GsonBuilder(); Gson gson = gsonBuilder.create(); Timber.plant(new Timber.DebugTree()); File cacheFile = new File(this.getCacheDir(), "HttpCache"); cacheFile.mkdirs(); Cache cache = new Cache(cacheFile, 10 * 1000 * 1000); //10 MB HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(@NonNull String message) { Timber.i(message); } }); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient okHttpClient = new OkHttpClient() .newBuilder() .cache(cache) .addInterceptor(httpLoggingInterceptor) .build(); OkHttp3Downloader okHttpDownloader = new OkHttp3Downloader(okHttpClient); picasso = new Picasso.Builder(this).downloader(okHttpDownloader).build(); retrofit = new Retrofit.Builder() .client(okHttpClient) .baseUrl("https://randomuser.me/") .addConverterFactory(GsonConverterFactory.create(gson)) .build(); populateUsers(); } private void initViews() { recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); } private void populateUsers() { Call<RandomUsers> randomUsersCall = getRandomUserService().getRandomUsers(10); randomUsersCall.enqueue(new Callback<RandomUsers>() { @Override public void onResponse(Call<RandomUsers> call, @NonNull Response<RandomUsers> response) { if(response.isSuccessful()) { mAdapter = new RandomUserAdapter(picasso); mAdapter.setItems(response.body().getResults()); recyclerView.setAdapter(mAdapter); } } @Override public void onFailure(Call<RandomUsers> call, Throwable t) { Timber.i(t.getMessage()); } }); } public RandomUsersApi getRandomUserService(){ return retrofit.create(RandomUsersApi.class); } } 
Picasso two dependencies - OkHttp3Downloader and Context .Retrofit . He, in turn, needs two dependencies - GsonConvertFactory and OkHttpClient and so on.MainActivity and compare it with the diagram for a better understanding.RandomUsersAPI and RandomUsersApi are the same. Just a typo in the chart.RandomUsersComponent will differ in the branch above and in the examples below. I suggest you use a new thread for full compliance with the examples below, and leave the above for reference.build.gradle file. dependencies { implementation 'com.google.dagger:dagger:2.13' annotationProcessor 'com.google.dagger:dagger-compiler:2.13' } RandomUsersAPI and Picasso will be present in the Component. @Component public interface RandomUserComponent { RandomUsersApi getRandomUserService(); Picasso getPicasso(); } RandomUsersAPI and Picasso ? We use the modules.MainActivity to different modules. Looking at the dependency graph, you can decide which modules are needed.RandomUsersModule , it will provide the dependencies RandomUsersApi , GsonConverterFactory , Gson and Retrofit . @Module public class RandomUsersModule { @Provides public RandomUsersApi randomUsersApi(Retrofit retrofit){ return retrofit.create(RandomUsersApi.class); } @Provides public Retrofit retrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory, Gson gson){ return new Retrofit.Builder() .client(okHttpClient) .baseUrl("https://randomuser.me/") .addConverterFactory(gsonConverterFactory) .build(); } @Provides public Gson gson(){ GsonBuilder gsonBuilder = new GsonBuilder(); return gsonBuilder.create(); } @Provides public GsonConverterFactory gsonConverterFactory(Gson gson){ return GsonConverterFactory.create(gson); } } PicassoModule , which will provide the Picasso and OkHttp3Downloader . @Module public class PicassoModule { @Provides public Picasso picasso(Context context, OkHttp3Downloader okHttp3Downloader){ return new Picasso.Builder(context). downloader(okHttp3Downloader). build(); } @Provides public OkHttp3Downloader okHttp3Downloader(OkHttpClient okHttpClient){ return new OkHttp3Downloader(okHttpClient); } } RandomUsersModule module for Retrofit need OkHttpClient . Which, in turn, needs other dependencies. Why not make a separate module for this?OkHttpClientModule that provides the OkHttpCkient , Cache , HttpLoggingInterceptor and File . @Module public class OkHttpClientModule { @Provides public OkHttpClient okHttpClient(Cache cache, HttpLoggingInterceptor httpLoggingInterceptor){ return new OkHttpClient() .newBuilder() .cache(cache) .addInterceptor(httpLoggingInterceptor) .build(); } @Provides public Cache cache(File cacheFile){ return new Cache(cacheFile, 10 * 1000 * 1000); //10 MB } @Provides public File file(Context context){ File file = new File(context.getCacheDir(), "HttpCache"); file.mkdirs(); return file; } @Provides public HttpLoggingInterceptor httpLoggingInterceptor(){ HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() { @Override public void log(String message) { Timber.d(message); } }); httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); return httpLoggingInterceptor; } } PicassoModule and OkHttpClientModule require a Context and maybe it will be useful to us in other places. We will make a module for this purpose. @Module public class ContextModule { Context context; public ContextModule(Context context){ this.context = context; } @Provides public Context context(){ return context.getApplicationContext(); } } Context to other modules? We need to link modules that depend on each other.
includes attribute is required. This attribute includes in the current module dependencies of the modules that are referenced.RandomUsersModule needs OkHttpClientModuleOkHttpClientModule needs a ContextModulePicassoModule needs OkHttpClientModule and ContextModule . Since OkHttpClientModule already connected to ContextModule , only OkHttpClientModule can be dispensed with OkHttpClientModule //  RandomUsersModule.java @Module(includes = OkHttpClientModule.class) public class RandomUsersModule { ... } //  OkHttpClientModule.java @Module(includes = ContextModule.class) public class OkHttpClientModule { ... } //  PicassoModule.java @Module(includes = OkHttpClientModule.class) public class PicassoModule { ... } 
includes attribute, in a similar way, we can link the component and modules using the modules attribute.getRandomUserService() and getPicasso() methods), we include in the component links to the RandomUsersModule and PicassoModule modules using the modules attribute. @Component(modules = {RandomUsersModule.class, PicassoModule.class}) public interface RandomUserComponent { RandomUsersApi getRandomUserService(); Picasso getPicasso(); } 
MainActivity you can conveniently retrieve the dependencies Picasso and RandomUsersApi using the RandomUserComponent . public class MainActivity extends AppCompatActivity { RandomUsersApi randomUsersApi; Picasso picasso; .... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... RandomUserComponent daggerRandomUserComponent = DaggerRandomUserComponent.builder() .contextModule(new ContextModule(this)) .build(); picasso = daggerRandomUserComponent.getPicasso(); randomUsersApi = daggerRandomUserComponent.getRandomUserService(); populateUsers(); ... } ... } 
DaggerComponent.build() , new instances of all the objects or dependencies you have configured are created. In this case, why doesn't Dagger 2 know that I only need one copy of Picasso ? In other words, how can we tell Dagger 2 to provide us with a dependency like singleton?@Scope@Scope tells Dagger 2 to create only a single instance, even if DaggerComponent.build() is called multiple times. This makes the dependency work like singleton. To customize the desired area (Scope) you need to create your own annotation. @Scope @Retention(RetentionPolicy.CLASS) public @interface RandomUserApplicationScope { } @Retention is an annotation to indicate the point of deviation of the use of annotation. She talks about when annotation can be used. For example, with the SOURCE mark the annotation will be available only in the source code and will be discarded during compilation, with the CLASS mark the annotation will be available at compile time, but not during program operation, with the RUNTIME mark the annotation will be available during program execution. @RandomUserApplicationScope @Component(modules = {RandomUsersModule.class, PicassoModule.class}) public interface RandomUserComponent { ...} @Module(includes = OkHttpClientModule.class) public class PicassoModule { ... @RandomUserApplicationScope @Provides public Picasso picasso(Context context, OkHttp3Downloader okHttp3Downloader){ return new Picasso.Builder(context). downloader(okHttp3Downloader). build(); } ... } @Module(includes = OkHttpClientModule.class) public class RandomUsersModule { ... @RandomUserApplicationScope @Provides public Retrofit retrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory, Gson gson){ return new Retrofit.Builder() .client(okHttpClient) .baseUrl("https://randomuser.me/") .addConverterFactory(gsonConverterFactory) .build(); } ... } 
ApplicationContext and Activity context. How to provide them? You can use ContextModule to provide an ApplicationContext . Let's create another module. @Module public class ActivityModule { private final Context context; ActivityModule(Activity context){ this.context = context; } @RandomUserApplicationScope @Provides public Context context(){ return context; } } Context type and Dagger 2 will not be able to figure out how to use, an error will occur.@Named @Module public class ActivityModule { .... @Named("activity_context") @RandomUserApplicationScope @Provides public Context context(){ return context; } } @Module public class ContextModule { .... @Named("application_context") @RandomUserApplicationScope @Provides public Context context(){ return context.getApplicationContext(); } }  @Module(includes = ContextModule.class) public class OkHttpClientModule { .... @Provides @RandomUserApplicationScope public File file(@Named("application_context") Context context){ File file = new File(context.getCacheDir(), "HttpCache"); file.mkdirs(); return file; } .... } @Module(includes = OkHttpClientModule.class) public class PicassoModule { ... @RandomUserApplicationScope @Provides public Picasso picasso(@Named("application_context")Context context, OkHttp3Downloader okHttp3Downloader){ return new Picasso.Builder(context). downloader(okHttp3Downloader). build(); ... } @Named - @Qualifier@Named annotation with @Qualifier you need to create a separate annotation and use it where necessary. @Qualifier public @interface ApplicationContext {}  @Module public class ContextModule { .... @ApplicationContext @RandomUserApplicationScope @Provides public Context context(){ return context.getApplicationContext(); } } ApplicationContext created by the annotation. @Module(includes = ContextModule.class) public class OkHttpClientModule { ... @Provides @RandomUserApplicationScope public File file(@ApplicationContext Context context){ File file = new File(context.getCacheDir(), "HttpCache"); file.mkdirs(); return file; } .... } @Module(includes = OkHttpClientModule.class) public class PicassoModule { @RandomUserApplicationScope @Provides public Picasso picasso(@ApplicationContext Context context, OkHttp3Downloader okHttp3Downloader){ return new Picasso.Builder(context). downloader(okHttp3Downloader). build(); } .... } @Named annotation with @Qualifier .@Scope , to get the dependencies in a single copy. The second is @Named , to separate methods that provide dependencies of the same type. The third is @Qualifier , as an alternative to @Named .Activity level, create several components and learn how to work with them. The next article will be released in a week.Source: https://habr.com/ru/post/345372/
All Articles