@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 RecyclerView
Retrofit
- for API callsGsonBuilder
and Gson
- for working with JSONHttpLoggingInterceptor
- for logging network operationsOkHttpClient
- client for Retrofit
Picasso
- working with images in the Adapter
MainActivity
. 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 OkHttpClientModule
OkHttpClientModule
needs a ContextModule
PicassoModule
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