일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 한달브런치북만들기
- 안드로이드
- 1일1커밋
- 슬기로운 온라인 게임
- 프래그먼트
- 테트리스
- 지지않는다는말
- 북한살둘레길
- 목적중심리더십
- 면접
- 소프시스
- 베드트레이
- 끝말잇기
- 브런치작가되기
- 한달어스
- 커스텀린트
- 재택근무
- 한달독서
- 소프시스 밤부 좌식 엑슬 테이블
- T자형인재
- 함수형 프로그래밍
- 베드테이블
- 아비투스
- 리얼하다
- 어떻게 나답게 살 것인가
- 캐치마인드
- 목적 중심 리더십
- 자취필수템
- 좌식테이블
- 한단어의힘
- Today
- Total
정상에서 IT를 외치다
[Android, MVP, Dagger] MVP 기본 예제 - Dagger 사용하기 본문
안녕하세요. 블랙진입니다.
지난시간 MVP 기본예제에 Room을 사용하여 최근 봤던 유저 목록을 저장해보았습니다. 이번시간에는 Dagger 을 사용하여 Presenter 을 외부에서 주입하도록 해보겠습니다. 현재 MVP 를 활용한 토이프로젝트를 총 3단계에 걸쳐서 리뷰를 진행하고 있습니다.
1. MVP 기본 예제
2. Room 사용하기
3. Dagger 사용하기
기존 프로젝트에서 MainActivity 만 Dagger 를 사용하여 리뷰해 보겠습니다.
1. Dagger 설정하기
Dagger을 설정하는 방법은 포스팅을 확인해 주세요.
2. API Module 만들기
GithubApiProvider 을 사용하여 Api 를 불러왔습니다. 이를 Dagger 을 사용하여 변환해 보겠습니다.
Dagger 에서 모듈에 역활은 공급할 객체를 생성하는 것입니다. 그럼 아래 GithubAPiProvider 에서 사용한 객체를 공급해 보겠습니다.
public final class GithubApiProvider {
public static GithubApi provideGithubApi() {
return new Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
// OkHttpClient 객체를 필요로 합니다.
.client(provideOkHttpClient(provideLoggingInterceptor()))
// CallAdapter.Factory 객체를 필요로 합니다.
.addCallAdapterFactory(RxJava2CallAdapterFactory.createAsync())
// Converter.Factory 객체를 필요로 합니다.
.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;
}
}
provideGithubApi 함수는 OkhttpClient, CallAdapter.Factory, Converter.Factory 3가지 객체를 필요로 합니다. 이에 아래와 같이 ApiModule 을 만들어 줍니다.
@Module
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);
}
}
이제 OkhttpClient, CallAdapter.Factory, Converter.Factory 3가지 객체를 추가해줍니다.
@Provides
@Singleton
OkHttpClient provideOkHttpClient(HttpLoggingInterceptor interceptor) {
OkHttpClient.Builder b = new OkHttpClient.Builder();
b.addInterceptor(interceptor);
return b.build();
}
@Provides
@Singleton
//provideOkHttpClient 에 필요한 객체입니다.
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();
}
이렇게 ApiModule 을 만들어 주시면 됩니다.
3. AppModule 만들기
AppModule 에서는 어플리케이션의 컨텍스트 객체를 제공해 줄겁니다.
@Module
class AppModule {
// 애플리케이션의 컨텍스트를 제공합니다.
// 다른 컨텍스트와의 혼동을 방지하기 위해 "appContext"라는 이름으로 구분합니다.
@Provides
@Named("appContext")
@Singleton
Context provideContext(Application application) {
return application.getApplicationContext();
}
}
4. RoomModule 만들기
역시 아래 UserDatabaseProvider 에서의 역활을 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;
}
}
Dagger 변환을 위한 RoomModule
@Module
class RoomModule {
@Provides
@Singleton
UserDao provideUserDao(UserDatabase db) {
return db.getUserDao();
}
// 데이터베이스를 관리하는 객체인 UserDatabase 를 제공합니다.
// "appContext" 라는 이름으로 구분되는 Context 객체를 필요로 합니다.
@Provides
@Singleton
UserDatabase provideUserDatabase(@Named("appContext") Context context) {
return Room.databaseBuilder(context, UserDatabase.class,
"black_jin.db")
.build();
}
}
5. AppComponent 만들기
Component 는 사용할 모듈을 불러오고 어디로 주입할지 결정합니다.
// AppComponent 를 선언하면서 컴포넌트로 묶어둘 모듈을 추가합니다.
// 대거의 안드로이드 지원 모듈인 AndroidSupportInjectionModule 을 함께 추가합니다.
// AppComponent 는 AndroidInjector 인터페이스를 상속하도록 하며,
// 애플이케이션을 상속한 클래스인 MyApplication 을 타입 인자로 넣어줍니다.
//
// 대거의 안드로이드 특화 기능에 대해 더 자세한 내용이 궁금하신 분은 공식 홈페이지에서 확인해주세요
// https://google.github.io/dagger/android.html
@Singleton
@Component(modules = {
AppModule.class, ApiModule.class, RoomModule.class,
AndroidSupportInjectionModule.class,
ActivityBinder.class
})
interface AppComponent extends AndroidInjector<MyApplication> {
// AppComponent 를 생성할 때 사용할 빌더 클래스를 정의합니다.
@Component.Builder
interface Builder {
// @BindsInstance 어노테이션으로 객체 그래프에 추가할 객체를 선언합니다.
// 객체 그래프에 추가할 객체를 인자로 받고, 빌더 클래스를 반환하는 함수 형태로 선언합니다.
@BindsInstance
Builder application(Application app);
// 빌더 클래스는 컴포넌트를 반환하는 build() 함수를 반드시 포함해야 합니다.
AppComponent build();
}
}
6. ActivityBInder 만들기
/**
* 객체 그래프에 추가할 엑티비티는 해당 엑티비티를 반환하는 함수에
* @ContributesAndroidInjector 어노테이션을 추가하여 선언합니다.
*/
@Module
abstract class ActivityBinder {
@ContributesAndroidInjector
abstract MainActivity bindMainActivity();
@ContributesAndroidInjector
abstract DetailActivity bindDetailActivity();
@ContributesAndroidInjector
abstract RecentActivity bindRecentActivity();
}
AcitvityBinder 를 통해 객체 그래프에 엑티비티를 추가해 줄 수 있습니다.
위 예제에서는 Main, Detail, Recent 3개의 엑티비티가 있기 때문에 위와 같이 추가해 줍니다. 이렇게 함으로서 원하는 특정 모듈을 각 엑티비티에 주입시킬 수 있게 됩니다.
7. MyApplication 만들고 AndroidManifest 등록하기
// DaggerApplication 을 상속합니다.
public class MyApplication extends DaggerApplication {
// DaggerAppComponent 의 인스턴스를 반환합니다.
@Override
protected AndroidInjector<? extends DaggerApplication> applicationInjector() {
return DaggerAppComponent.builder().application(this).build();
}
}
DaggerApplication 만들고 Manifest 에 등록을 해줍니다. 이렇게 하면 ActivityBinder 에서 선언해주 Activity 에 AppComponent 에서 정한 모듈을 주입할 수 있게 됩니다.
8. BaseActivity 변경하기
각 Activity 에서 AppCompatActivity 를 상속하는 BaseActivity 를 사용했습니다. BaseaActivity 의 상속 값을 DaggerAppCompatActivity 로 변경해 줍니다.
public abstract class BaseActivity extends DaggerAppCompatActivity {
@Override
public void setContentView(int layoutResID) {
super.setContentView(layoutResID);
ButterKnife.bind(this);
}
}
9. MainActivity 에 Dagger 사용하기
GithubApi api = GithubApiProvider.provideGithubApi();
UserDao userDao = UserDatabaseProvider.getInstance(this).getUserDao();
MainActivity 에서 api 와 userDao 를 주입하여 사용할 수 있게 됩니다.
@Inject
GithubApi api;
@Inject
UserDao userDao;
@Inject 를 사용하여 위와 같이 사용할 수 있게 됩니다.
10 MainPresenter 주입하기
여기서 더 나아가 MainPresenter 또한 선언이 아닌 주입으로 해결해 보겠습니다. 6번에 ActivityBInder 를 통해 원하는 모듈을 원하는 Activity 에 주입할 수 있다고 했습니다. 그렇게 하기 위해 MainModule 을 만들어서 MainActivity에만 주입해 보겠습니다.
@Module
public class MainModule {
@Provides
MainAdapter provideMainAdapter(MainActivity activity) {
MainAdapter adapter = new MainAdapter();
adapter.setClickListener(activity);
return adapter;
}
@Provides
MainPresenter provideMainPresenter(MainActivity activity) {
MainPresenter presenter = new MainPresenter();
presenter.setView(activity);
return presenter;
}
}
MainActivity 에서 사용하는 MainPresenter 와 MainAdapter 을 주입하여 사용해 보겠습니다. 이미 MainAcitivty 도 ActivityBinder 에서 모듈로 선언했고 같은 Componet 안에 있기 때문에 주입이 되므로 위와같이 코딩할 수 있습니다
@ContributesAndroidInjector(modules = {MainModule.class})
abstract MainActivity bindMainActivity();
이렇게 한 후 ActivityBinder 에 MainModule 을 추가해 줍니다. 그러면 adapter 와 presenter 또한 대거를 통해 사용할 수 있게 됩니다.
@Inject
MainAdapter adapter;
@Inject
MainPresenter presenter;
11. Presenter 에서 UserDao, GithubApi 사용하기
UserDao, GithubApi는 MainAcitivty 에서 주입할 필요 없이 Presenter 에 주입하여 사용하도록 수정해 보겠습니다. 이를 위해서는 MainModule 에서 공급할 객체를 생성해 주어야 합니다.
@Provides
MainPresenter provideMainPresenter(MainActivity activity, UserDao userDao, GithubApi api) {
MainPresenter presenter = new MainPresenter(userDao, api);
presenter.setView(activity);
return presenter;
}
ProvideMainPresenter 에서 UserDao 와 GithubApi 를 추가 공급한 후 Presenter 에 넣어 주었습니다.
private UserDao userDao;
private GithubApi api;
public MainPresenter(UserDao userDao, GithubApi api) {
this.disposable = new CompositeDisposable();
this.userDao = userDao;
this.api = api;
}
MainPresenter 에서 생성자를 사용해 UserDao 와 GithubApi 또한 외부에서 주입 후 객체 선언에 초기화 하도록 수정할 수 있습니다.
아래는 변환 후 MainAcitivty 의 모습니다.
public class MainActivity extends BaseActivity
implements MainContract.View, MainAdapter.OnItemClickListener {
@Inject
MainAdapter adapter;
@Inject
MainPresenter presenter;
@BindView(R.id.recycler_view)
RecyclerView recyclerView;
@BindView(R.id.progress_view)
ProgressBar progressBar;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setTitle("RANDOM USER");
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
adapter.setClickListener(this);
presenter.setView(this);
presenter.loadData();
presenter.setRxEvent();
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.releaseView();
}
@Override
public void onClick(User user) {
// UserDatabase 에 저장합니다.
presenter.addUser(user);
// DetailActivity 로 이동합니다.
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra(DetailActivity.KEY_USER, user);
startActivity(intent);
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void setItems(ArrayList<User> items) {
adapter.setItems(items);
}
@Override
public void updateView(User user) {
adapter.updateView(user);
}
@OnClick(R.id.btn_recent_user)
void onClick() {
Intent intent = new Intent(this, RecentActivity.class);
startActivity(intent);
}
}
주요 객체는 모두 외부에서 주입하게 되었고 비즈니스 로직에 필요한 함수는 모두 Presenter 로 옮기게 되었습니다.
MainContract
public interface MainContract {
interface View extends BaseContract.View {
void showProgress();
void hideProgress();
void setItems(ArrayList<User> items);
void updateView(User user);
}
interface Presenter extends BaseContract.Presenter<View> {
@Override
void setView(View view);
@Override
void releaseView();
void loadData();
void setRxEvent();
void addUser(User user);
}
}
MainPresenter
public class MainPresenter implements MainContract.Presenter {
private MainContract.View view;
private CompositeDisposable disposable;
private UserDao userDao;
private GithubApi api;
public MainPresenter(UserDao userDao, GithubApi api) {
this.disposable = new CompositeDisposable();
this.userDao = userDao;
this.api = api;
}
@Override
public void setView(MainContract.View view) {
this.view = view;
}
@Override
public void releaseView() {
disposable.clear();
}
@Override
public void loadData() {
disposable.add(api.getUserList(Constant.RANDOM_USER_URL)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(__ -> {
view.showProgress();
})
.doOnTerminate(() -> {
view.hideProgress();
})
.subscribe(userResponse -> {
view.setItems((ArrayList<User>)userResponse.userList);
}, error -> {
Log.e("MyTag",error.getMessage());
})
);
}
@Override
public void addUser(User user) {
disposable.add(
Observable.just(user)
.subscribeOn(Schedulers.io())
.subscribe(
item -> {
Log.d("MyTag","item : " + item + " 저장");
userDao.add(item);
}
)
);
}
@Override
public void setRxEvent() {
disposable.add(
RxEvent.getInstance()
.getObservable()
.subscribe(
object -> {
if(object instanceof User) {
view.updateView((User) object);
}
}
)
);
}
}
이렇게 MainActivity 의 함수들을 Dagger 를 통해 주입하여 변경해 보았습니다.
처음 설정 및 집입장벽이 높아 보이지만 코드가 점점 커질 수록 이러한 Dagger 의 활용은 코드를 유지보수하고 한눈에 관찰하기에는 더욱 좋은 구조가 될 거 라고 생각합니다.
위 예제 코드는 링크 에서 확인하실 수 있습니다.
1. MVP 기본 예제
2. Room 사용하기
3. Dagger 사용하기
<참고자료>
mvp-dagger-architecture : 다른 방법으로 MVP를 만든 예제 입니다.
커니의 코틀린 : 주석에 쓰인 설명은 주로 커니의 코틀린 책에서의 예제를 참조하였습니다.
'안드로이드' 카테고리의 다른 글
[Android, Double Scroll] 이중 스크롤 어떻게 구성하면 좋을까? (6) | 2019.03.08 |
---|---|
[Android, TargetVersion 28] TargetVersion 28 에 따른 조치 사항 (2) | 2019.03.06 |
[Android, MVP, Room] MVP 기본 예제 - Room 활용 (0) | 2019.02.07 |
[Android, MVP] MVP 기본 예제 (0) | 2019.02.07 |
[Android, Canvas, PorterDiff] PorterDiff.Mode 사용해 모서리가 둥근 이미지 만들기 (2) | 2019.01.21 |