정상에서 IT를 외치다

[Android, MVP] 리뷰 받은 MVP 예제 with Dagger 본문

안드로이드

[Android, MVP] 리뷰 받은 MVP 예제 with Dagger

Black-Jin 2019. 4. 17. 11:00
반응형

안녕하세요. 블랙진입니다.


지난 시간 Room 을 사용한 MVP 예제에 대해 포스팅을 했습니다. 이번에는 Dagger 을 사용해 코드를 다음어 보겠습니다.



1. dagger 라이브러리 추가


dagger 버전은 링크에서 확인해 주세요. 포스팅 기준 최신 버전은 2.22.1 입니다.

// dagger
implementation "com.google.dagger:dagger:$daggerVersion"
implementation "com.google.dagger:dagger-android:$daggerVersion"
implementation "com.google.dagger:dagger-android-support:$daggerVersion"

kapt "com.google.dagger:dagger-android-processor:$daggerVersion"
kapt "com.google.dagger:dagger-compiler:$daggerVersion"



2. AppModule 만들기


ApplicationContext 를 "appContext" 이름으로 주입해주기 위해 선언해 줍니다.

@Module
class AppModule {

// 애플리케이션의 컨텍스트를 제공합니다.
// 다른 컨텍스트와의 혼동을 방지하기 위해 "appContext"라는 이름으로 구분합니다.
@Provides
@Named("appContext")
@Singleton
Context provideContext(Application application) {
return application.getApplicationContext();
}
}




3. ApiModule 만들기


GithubApiProvider 는 final class 로 Retrofit 객체를 반환하는 함수를 가지고 있습니다. 이 코드를 Dagger 를 사용해서 다듬어 보겠습니다.

public final class GithubApiProvider {

public static GithubApi provideGithubApi() {
return new Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.client(provideOkHttpClient(provideLoggingInterceptor()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(GithubApi.class);
}

private static OkHttpClient provideOkHttpClient(@NonNull HttpLoggingInterceptor interceptor) {
OkHttpClient.Builder b = new OkHttpClient.Builder();
b.addInterceptor(interceptor);
return b.build();
}

private static HttpLoggingInterceptor provideLoggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return interceptor;
}
}


ApiModule 이라는 final class 로 변경 했고 Dagger 을 사용해서 SingleTon 으로 만들어 주었습니다.  기존에 GithubApiProvider 에서는 Retrofit 을 매번 새로 생성하여 불필요하게 객체를 만들었습니다. 이점을 Dagger 의 SingleTon 어노테이션을 사용해서 개선하였습니다.

@Module
public final class ApiModule {

@Provides
@Singleton
GithubApi provideGithubApi(
OkHttpClient okHttpClient,
CallAdapter.Factory callAdapter,
Converter.Factory factory
) {
return new Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.client(okHttpClient)
.addCallAdapterFactory(callAdapter)
.addConverterFactory(factory)
.build()
.create(GithubApi.class);
}

@Provides
@Singleton
OkHttpClient provideOkHttpClient(HttpLoggingInterceptor interceptor) {
OkHttpClient.Builder b = new OkHttpClient.Builder();
b.addInterceptor(interceptor);
return b.build();
}

@Provides
@Singleton
HttpLoggingInterceptor provideLoggingInterceptor() {
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
return interceptor;
}

@Provides
@Singleton
CallAdapter.Factory provideCallAdapterFactory() {
return RxJava2CallAdapterFactory.createAsync();
}

@Provides
@Singleton
Converter.Factory provideConverterFactory() {
return GsonConverterFactory.create();
}

}

위와 같이 사용하여 provideGithubApi() 를 제공하게 만들었습니다. 각 객체가 어떻게 주입되는지 보기 쉽게 아래와 같이 준비했습니다.



최종 적으로  provideGithubApi() 를 사용하기 위해 OkHttpClient, CallAdapter.Factory, Converter.Factory 3가지 파라미터가 필요하고 이들 모두 ApiModule 안에 생성되어 주입되고 있는 모습입니다. 또한 SingleTon 어노테이션을 사용해 객체의 불필요한 생성을 없앴습니다.




3. RoomModule 만들기


Room 객체를 반환하는 UserDatabaseProvider 는 java의 싱글톤 패턴을 사용해서 구현했습니다. 이 코드를 Dagger 로 변경해 보겠습니다.

public class UserDatabaseProvider {

private volatile static UserDatabase INSTANCE = null;

private UserDatabaseProvider() {}

public static UserDatabase getInstance(Context context) {

if(INSTANCE == null) {
synchronized (UserDatabase.class) {
INSTANCE = Room.databaseBuilder(
context,
UserDatabase.class,
"black_jin.db")
.build();
}
}

return INSTANCE;
}
}


RoomModule로 변경하기

@Module
public final class RoomModule {

@Provides
@Singleton
UserDao provideUserDao(UserDatabase db) {
return db.getUserDao();
}

@Provides
@Singleton
UserDatabase provideUserDatabase(@Named("appContext") Context context) {
return Room.databaseBuilder(context, UserDatabase.class,
"black_jin.db")
.build();
}
}

어떻게 객체가 주입되는지 도식화 해서 보겠습니다.



여기서 특이한 점은 @Namer("appContext") 부분입니다. AppModule 에 있는 provideContext() 를 불러와서 사용했는데 이를 위해서는 AppModule 과 RoomModule 을 같은 객체 그래프 안에 설정해 주어야 합니다. 이 작업은 Componet 에서 처리합니다. (아래에서 추가 설명 하겠습니다.)



3. ActivtiyBinder 만들기


ContributesAndroidInjector 를 사용해 주입하고자 한느 Activity 를 Module 에서 정의할수 있습니다.


ContributesAndroidInjector가 궁금하다면  > ContributesAndroidInjector 사용해보기

@Module
abstract class ActivityBinder {

@ContributesAndroidInjector()
abstract MainActivity bindMainActivity();

@ContributesAndroidInjector()
abstract DetailActivity bindDetailActivity();

@ContributesAndroidInjector()
abstract RecentActivity bindRecentActivity();
}

이렇게 필요한 Module 을 전부 선언해 주었습니다. 그럼 Componet 를 만들어 Module 에서 생성한 객체를 어디로 주입할지 정합니다.



4. AppComponent 만들기

@Singleton
@Component(modules = {
AppModule.class, ApiModule.class, RoomModule.class,

AndroidSupportInjectionModule.class,
ActivityBinder.class
})
public interface AppComponent extends AndroidInjector<MyApplication> {

@Component.Builder
interface Builder {

@BindsInstance
Builder application(Application app);

AppComponent build();
}
}


안드로이드 용 Dagger 특화 기능인 AndroidInjector 를 사용해 Componet 를 만들어 주었습니다. @Componet() 를 보시면 AppModule, ApiModule, RoomModule 을 선언하여 같은 객체 그래프에 존재하게 만들어 줍니다. 이를 통해 AppModule 에 있던 객체를 RoomModule 에 주입할 수 있게 해줍니다.



5. MyApplication, BaseActivity 작업


이제 마지막으로 두가지 추가 작업을 해줍니다.


MyApplication

public class MyApplication extends DaggerApplication {

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


AndroidInjector 을 사용하기 위해 DaggerApplication 을 상속해 줍니다.



BaseActivity

public abstract class BaseActivity extends DaggerAppCompatActivity {

@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
ButterKnife.bind(this);
}

...

}


ActivityBInder 사용을 위해 DaggerAppCompatActivity 를 상속해 줍니다.



6. GithubApi, UserDao 의존성 주입하기


위 작업을 마치면 이제  GithubApi, UserDao 를 주입할 수 있게 됩니다. (힘든 여정이었네요...)

@Inject
GithubApi api;

@Inject
UserDao userDao;

MainActivity, DetailActivity, RecentActivity 에서 GithubApi, UserDao 객체가 필요한 곳에 @Inject 어노테이션 만으로 외부에서 객체가 주입됩니다!!



7. MainActivity 다른 변수 모두 dagger 를 사용해 주입하기


여기서 더 욕심을 내서 MainActivity 에서 선언하는 다른 변수들도 모두 dagger 를 사용해 주입해 보겠습니다.

private MainAdapter adapter = new MainAdapter();

private CompositeDisposable disposable = new CompositeDisposable();

private MainPresenter presenter;

@Inject
GithubApi api;

@Inject
UserDao userDao;

adapter, disposable, presenter 변수가 선언 되어 있습니다. 이를 MainModule 을 만들어서 주입해 보겠습니다.



7_1. MainAdapter 주입

@Provides
MainAdapter provideMainAdapter(MainActivity activity) {
MainAdapter adapter = new MainAdapter();
adapter.setClickListener(activity);
return adapter;
}



7_2 Presenter 주입

public MainPresenter(MainContract.View view,
GithubApi api,
UserDao userDao,
CompositeDisposable disposable) {
this.view = view;
this.api = api;
this.userDao = userDao;
this.disposable = disposable;
}

 Presenter 생성에 필요한 파라미터는 MainContract.View, GithubApi, UserDao, CompositeDisposable 4개 입니다. 여기서 GithubApi 와 UserDao 는 이미 ApiModule 과 RoomModule 에서 만들었으므로 따로 생성할 필요가 었습니다.

@Provides
MainPresenter provideMainPresenter(MainActivity activity, GithubApi api,
UserDao userDao, CompositeDisposable disposable) {
return new MainPresenter(activity, api, userDao, disposable);
}

@Provides
CompositeDisposable provideMainDisposable() {
return new CompositeDisposable();
}

위와 같이 코드를 작업하면 됩니다. 역시 도식화 해서 보여드리겠습니다.`



MainActivity    <-   ActivityBinder

GithubApi       <-   ApiModule   

UserDao          <-   RoomModule


에서 각각의 객체가 주입이 됩니다. 아래는 MainModule 전체코드 입니다.

@Module
public class MainModule {

@Provides
MainAdapter provideMainAdapter(MainActivity activity) {
MainAdapter adapter = new MainAdapter();
adapter.setClickListener(activity);
return adapter;
}

@Provides
MainPresenter provideMainPresenter(MainActivity activity, GithubApi api,
UserDao userDao, CompositeDisposable disposable) {
return new MainPresenter(activity, api, userDao, disposable);
}

@Provides
CompositeDisposable provideMainDisposable() {
return new CompositeDisposable();
}
}

참고로 Adapter, Presenter, Disposable 은 SingleTon 어노테이션을 붙이지 않았습니다. 이는 Adapter, Presenter, Disposable 는 하나의 객체를 재사용해야 되는것이 아니기 때문입니다.


이제 MainModule 을 객체 그래프에 추가해 줘야 합니다. 이는 AcitvityBinder 에서 ContributesAdnroidInjector 를 통해 해줍니다.

@Module
abstract class ActivityBinder {

@ContributesAndroidInjector(modules = {MainModule.class})
abstract MainActivity bindMainActivity();

@ContributesAndroidInjector(modules = {DetailModule.class})
abstract DetailActivity bindDetailActivity();

@ContributesAndroidInjector(modules = {RecentModule.class})
abstract RecentActivity bindRecentActivity();
}

위 모습은 Main Module, Detail Module, Recent Module 을 모두 만들어 연결했을 때입니다. 작업을 모두 마친 MainActivity 의 변수 선언부 모습은 어떻게 될까요?



기존

private MainAdapter adapter = new MainAdapter();

private CompositeDisposable disposable = new CompositeDisposable();

private MainPresenter presenter;

@Inject
GithubApi api;

@Inject
UserDao userDao;

@BindView(R.id.recycler_view)
RecyclerView recyclerView;

@BindView(R.id.progress_view)
ProgressBar progressBar;


Dagger 사용 후

@Inject
MainAdapter adapter;

@Inject
MainPresenter presenter;

@BindView(R.id.recycler_view)
RecyclerView recyclerView;

@BindView(R.id.progress_view)
ProgressBar progressBar;

요렇게 바뀝니다. 어? 자세히 보면 기존에 선언했던 GithubApi 와 UserDao 가 사라졌습니다.  왜냐하면 두 변수는 MainPresenter 에서 사용하는 변수이고 이는 MainModule 에서 주입했기 때문에 MainActivity 에서는 presenter 만 불러와도 아무 문제 없습니다 :)


여기서 Dagger 의 장점을 눈으로 볼 수 있습니다. MainActivity 에서는 더이상 GithubApi, UserDao 에 관한 의존성이 완전히 사라지게 된겁니다!!! 오오오오오오!!!!


제가 만든 예제는 깃허브에서 확인하실 수 있습니다.

반응형
Comments