Dagger2 with RepositoryPattern (Room and Retrofit)

Muhammad
5 min readDec 10, 2017

--

Okay , in system design there are atleast 2 billion ways to achieve a goal so this is by no ways the only way to do it. The quality of a solution in my opinion is that which leverages all facilities of the framework among other factors.

I have seen a lot of implementations of Dagger2 interacting with various data sources however a synergy of dagger with true Repository pattern is still elusive in a dry google search (dry search = a pretty non creative stubborn search by just typing dagger repository rxjava room etc)

This would be essentially a merger of the Repository Pattern as implemented by google samples for a standard android application and the well known ServiceGenerator pattern (replaced by a dagger module). It also presents a clean way to perform a login and ask dagger to inject a the logged in user application wide. Pretty neat eh ?.

For our repositories we need the following to be true .

  1. Dagger should create all repositories at the very beginning of the system.
  2. All repositories should contain separate classes that talk to the remote and local sources.
  3. Room database shouldn’t be created more than once (I smell Dagger here)
  4. Retrofit instance shouldn’t be created more than once (I smell Dagger here too.)
  5. A remote repository should get its relevant retrofit services object (the retrofit services interface)
  6. A logged in user should be available in the entirety of the application once he has been confirmed. His access token (Yup this is oauth) should also be automatically inserted in all subsequent api hits.

The System Diagram

You would need to come back to this diagram a few times to make sense of the technobabble below. I am pretty lazy and an active critic of all the latest flowcharting software hence I would sketch out the system via my SPEN.

A small representation of the integral parts of the module design.

The AppComponent.

Since this is not a dagger tutorial so I would just be skimming over the irrelevant details. My intention here would be to show how is Retrofit and Room configured , how the repositories are made and how do they work (via a login example)

The AppComponent creates the following modules among others.

  1. RepositoryModule
  2. LoggedUserModule
  3. ActivityBindingModules

It looks like this

@Singleton
@Component(modules =
{
AndroidSupportInjectionModule.class,
ApplicationModule.class,
LoggedUserModule.class,
ActivityBindingModule.class,
RepositoryModule.class,})
public interface AppComponent extends AndroidInjector<ChallengerHuntApplication> {



@Component.Builder
interface Builder {

@BindsInstance
AppComponent.Builder application(Application application);

AppComponent build();
}
}

Repository Module

This module is the one which houses our repositories and the lead protagonist of the article Retrofit. These are the modules inside the Repository Module.

  1. DatabaseModule
  2. RetrofitModule
  3. SystemRepositoryModule
  4. AccountsRepositoryModule
  5. UserRepositoryModule
  6. Other Repositories of the system like Student Repository , Department Repository etc

Retrofit Module

Lets just see the code

private static final String BASE_URL = "http://xyz/appname/";

@Provides
@Singleton
@NonNull
OkHttpClient.Builder provideOkHttp() {
return new OkHttpClient.Builder();
}
@Provides
@Singleton
@NonNull
RequestHeaders provideRequestHeaders() {
return new RequestHeaders(new AccessToken(), "en", "application/json");
}

@Provides
@Singleton
@NonNull
RequestInterceptor providesRequestInterceptor(@NonNull RequestHeaders requestHeaders) {
return new RequestInterceptor(requestHeaders);
}
@Provides
@Singleton
@NonNull
Retrofit provideRetrofit(OkHttpClient.Builder httpClient, RequestInterceptor requestInterceptor) {
//add logger
HttpLoggingInterceptor logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY);
httpClient.addInterceptor(logging);
httpClient.addInterceptor(requestInterceptor);

//add retro builder
Retrofit.Builder retroBuilder= new Retrofit.Builder()
.baseUrl(BASE_URL)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create());

retroBuilder.client(httpClient.build());

//create retrofit - only this instance would be used in the entire application
Retrofit retrofit = retroBuilder.build();
return retrofit;

}
// API Services@Provides
@Singleton
@NonNull
UserServices provideUserServices(
Retrofit retrofit) {
return retrofit.create(UserServices.class);
}

@Provides
@Singleton
@NonNull
AccountsServices provideAccountServices(
Retrofit retrofit) {
return retrofit.create(AccountsServices.class);
}


@Provides
@Singleton
@NonNull
SystemServices provideSystemServices(
Retrofit retrofit) {
return retrofit.create(SystemServices.class);
}

The RequestInterceptor is the global application request interceptor which would attach like a parasite (too strong a word I know ) on every request being put forth by retrofit. Here is what it looks like.

public class RequestInterceptor implements Interceptor {
private RequestHeaders requestHeaders;

public RequestInterceptor(RequestHeaders requestHeaders) {
this.requestHeaders = requestHeaders;
}

@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request.Builder builder = original.newBuilder().
header("Authorization", "Bearer " + requestHeaders.getAccessToken().getAccessToken()).
header("Accept-Language", requestHeaders.getLanguage()).
method(original.method(), original.body());
Request newRequest = builder.build();

return chain.proceed(newRequest);
}
}

The RequestHeader is a simple POJO that would be used by any component of our system to modify the headers that are being sent. A use case can be the LoginPresenter changing the access token header after a successful login.

The service interfaces

The service interfaces mentioned in the Module like UserServices (Intented to be used in the UserRepository), AccountServices(Intented to be used in AccountRepository) etc take a fully baked retrofit instance and create the service implementation object. Our remote repositories would then simply take these service impls and start firing against it. This is as neat as I can imagine it to be. This is where we inject the services impl object

/**
* This is used by Dagger to inject the required arguments into the {
@link UserRepository}.
*/
@Module
public class UserRepositoryModule {


@Singleton
@Provides
@Local
UserDataSource provideUserLocalDataSource(AppDatabase appDatabase) {
return new UserLocalDataSource(appDatabase);
}

@Singleton
@Provides
@Remote
UserDataSource provideUserRemoteDataSource(UserServices userServices) {
return new UserRemoteDataSource(userServices);
}




}

Logged In User

If we achieve all the above we have got a beautiful independence in terms of reference availability throughout the system. The most important thing that we need from dagger and I guess the place where dagger would be the busiest would be us asking the currently logged in user from it.

We will start by creating this module directly in the AppComponent so that it is available in the entirety of the system .

@Module
public class LoggedUserModule {

@Provides
@Singleton
@NonNull
LoggedUser provideLoggedInUser() {
return new LoggedUser();
}


}

The LoggedUser is a POJO that just keeps within itself the logged in user .

public class LoggedUser {
private User user;

public User getLoggedInUser() {
return user;
}

public void setLoggedInUser(User user) {
this.user = user;
}

}

If this appears to be superfluous or unnecessary to the very observant (one could argue we could simply expose the User object , rather then making it inside a container like LoggedUser) go through this.

In our application class , we would ask Dagger to create the LoggedUser. There is not much to talk about here. You simply fill in the logged user here and inject it in whatever component you’d like.

Also you would need to reload the access token in the RequestHeader object (assuming you are a normal human being who is using oAuth) when the system begins.

public class ChallengerHuntApplication extends DaggerApplication {

private AppComponent appComponent; //this is the central object distributor


@Inject
@Nonnull
LoggedUser loggedUser;
@Inject
@Nonnull
UserRepository userRepository;
@Inject
@Nonnull
RequestHeaders requestHeaders;

@Override
public void onCreate() {
super.onCreate();
if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree());
}
initializeApplication();
}

private void initializeApplication() {
//load the current user into the system
User user = userRepository.
getLoggedInUser().
subscribeOn(SchedulerProvider.getInstance().computation()).
blockingGet();
loggedUser.setLoggedInUser(user);

//load the current access token into all requests
if (user != null)
requestHeaders.setAccessToken(user.getAccessToken());

}




@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
appComponent = DaggerAppComponent.builder().application(this).build();
return appComponent;
}

Conclusion

The ease with which objects are available to you whenever you need them , is of paramount consequences. Once you start using Dagger 2 , you would see that it takes away all the stress out of development. What else do we have in a piece of software other than object creation and destruction? We have manipulation. Manipulation usually never causes crashes , surprises or late night calls . Its improper object creation , distribution and destruction that plagues a developers life. You no longer need to make your own factories OR suffer the menace of Singletons.

You are free.

--

--