본 포스팅은 DroidKaigi 2018 ~ Dagger2を活用してAndroid SDKの依存関係をクリーンにする 을 기본으로 번역하여 작성했습니다
제 일본어 실력으로 인하여 오역이나 오타가 발생할 수 있습니다.
DroidKaigi 2018 Day2 14:00~ Room1
からくり(@kr9ly)
DI 사용하고 있습니까?
Dagger2
그런데 Android 앱의 경우
DI (잘) 사용하고 있습니까?
목적
샘플 레포지토리는 이쪽
https://github.com/kr9ly/dagger2-sampleapp
@Scope를 활용
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewScope {
}
대표적인 것은 @Singleton
@Scope
@Retention(RetentionPolicy.RUNTIME)
public @interface ViewScope {
}
@ViewScope
@Subcomponent(module = {FragmentManagerDelegatedModule.class})
public interface ViewScopeComponent {
ListViewModel listViewModel();
DetailViewModel detailViewModel();
}
스스로 만든 Scope용 Component를 정의한다 (여기부터 참조되는 Module의 @Provides에 스스로 만든 Scope Annotation을 부여한다)
@ViewScope
@Component
public interface ApplicationComponent {
ViewScopeComponent viewScopeComponent(
FragmentManagerDelegateModule fragmentManagerModule
);
}
Singleton -> 스스로 만든 Scope Component 생성은 Subcomponent가 편리
public class ApplicationComponentManager {
private static final WeakHashMap<Context, ApplicationComponent> components = new WeakHashMap<>();
public static synchronized ApplicationComponent get(Context context) {
Context appContext = context.getApplicationContext();
ApplicationComponent component = components.get(appContext);
if (component != null) {
return component;
}
component = DaggerApplicationComponent.builder()
.resourceProviderModule(new ResourceProviderModule(appContext))
.build();
components.put(context, component);
return component;
}
}
public abstract class DaggerBaseActivity extends AppCompatActivity {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ViewScopeComponent viewScopeComponent = ApplicationComponentManager
.get(this)
.viewScopeComponent(
new FragmentManagerDelegatedModule(
new FragmentManagerActivityDelegate(this)
)
);
onComponentPrepared(viewScopeComponent, savedInstanceState);
}
protected abstract void onComponentPrepared(ViewScopeComponent component,
@Nullable Bundle savedInstanceState);
}
장점
단점
장점
단점
@Module
public class FragmentManagerModule {
@ViewScope
@Provides
FragmentManager provideFragmentManager() {
// Activity도 Fragment도 이 메소드가 호출된다
return null;
}
}
우선 Module을 정의한다
@Module
public class FragmentManagerDelegatedModule {
private final FragmentManagerDelegate delegate;
public FragmentManagerDelegatedModule(FragmentManagerDelegate delegate) {
this.delegate = delegate;
}
@ViewScope
@Provides
FragmentManager provideFragmentManager() {
// Delegate interface를 호출
return delegate.provide();
}
}
public interface FragmentManagerDelegate {
FragmentManager provide();
}
단순하게 Provide 대상 인스턴스를 반환할 뿐
여기부터 Activity용과 Fragment용으로 구현을 나눈다
public class FragmentManagerActivityDelegate implements FragmentManagerDelegate {
private final FragmentActivity fragmentActivity;
public FragmentManagerActivityDelegate(FragmentActivity fragmentActivity) {
this.fragmentActivity = fragmentActivity;
}
@Override
public FragmentManager provide() {
return fragmentActivity.getSupportFragmentManager();
}
}
Activity의 참조를 가진다
public class FragmentManagerFragmentDelegate implements FragmentManagerDelegate {
private final Fragment fragment;
public FragmentManagerFragmentDelegate(Fragment fragment) {
this.fragment = fragment;
}
@Override
public FragmentManager provide() {
// 무심코 부모 FragmentManager를 참조하지 않도록
return fragment.getChildFragmentManager();
}
}
Fragment의 참조를 가진다
public class FragmentManagerDynamicModule {
private final Object scopeObject;
public FragmentManagerDynamicModule(Object scopeObject) {
this.scopeObject = scopeObject;
}
@ViewScope
@Provides
FragmentManager provideFragmentManager() {
if (scopeObject instanceof FragmentActivity) {
return ((FragmentActivity) scopeObject).getSupportFragmentManager();
} else if (scopeObject instanceof Fragment) {
return ((Fragment) scopeObject).getChildFragmentManager();
}
throw new IllegalStateException();
}
}
무엇이 오더라도 가능하도록 동적으로 나눈다
Context 의존을 분리
public class ResourceProvider {
private final Context context;
public ResourceProvider(Context context) {
this.context = context;
}
public String getString(@StringRes int resId) {
return context.getString(resId);
}
public String getString(@StringRes int resId, Object... formatArgs) {
return context.getString(resId, formatArgs);
}
}
예를 들면 string 등의 Resource에 접근하는 클래스
@Module
public class ResourceProviderModule {
private final Context appContext;
public ResourceProviderModule(Context appContext) {
this.appContext = appContext;
}
@Singleton
@Provides
public ResourceProvider provideResourceProvider() {
return new ResourceProvider(appContext);
}
}
장점
단점
@Module
public class AppContextModule {
private final Context appContext;
public AppContextModule(Context appContext) {
this.appContext = appContext;
}
@Singleton
@Provides
public Context provide() {
return appContext;
}
}
이런 Module을 정의해둔다
@Singleton
public class ResourceProvider {
private final Context context;
@Inject
public ResourceProvider(Context context) {
this.context = context;
}
public String getString(@StringRes int resId) {
return context.getString(resId);
}
public String getString(@StringRes int resId, Object... formatArgs) {
return context.getString(resId, formatArgs);
}
}
생성자에 @Inject, 클래스 정의에 @Singleton
장점
단점
장단점이 있으니 프로덕션에 맞는 방법을 선택합시다
화면 이동을 정리
public interface TransitionHandler {
void startActivity(IntentBuilder intentBuilder);
}
Intent에 대해서도 생성용 interface를 정의
public interface IntentBuilder {
Intent build(Context context);
}
public class ActivityTransitionHandler implements TransitionHandler {
private final FragmentActivity activity;
public ActivityTransitionHandler(FragmentActivity activity) {
this.activity = activity;
}
@Override
public void startActivity(IntentBuilder intentBuilder) {
activity.startActivity(intentBuilder.build(activity));
}
}
public class FragmentTransitionHandler implements TransitionHandler {
private final Fragment fragment;
public FragmentTransitionHandler(Fragment fragment) {
this.fragment = fragment;
}
@Override
public void startActivity(IntentBuilder intentBuilder) {
fragment.startActivity(intentBuilder.build(fragment.getContext()));
}
}
Delegate Pattern 사용하면 간단하게 Module내에서 분리할 수 있습니다
public interface IntentBuilder {
Intent build(Context context);
}
public class SimpleIntentBuilder implements IntentBuilder {
private final Class<? extends Activity> targetActivityClass;
public SimpleIntentBuilder(Class<? extends Activity> targetActivityClass) {
this.targetActivityClass = targetActivityClass;
}
@Override
public Intent build(Context context) {
return new Intent(context, targetActivityClass);
}
}
Context가 필요한 패턴도 Intent 생성하는 측은 Context에 의존하지 않을 수 있다
타입마다 호출할 메소드가 다르고, 괜찮은 느낌으로 파라매터를 가지고 다니기 어려운 경우의 대응 방법 (Bundle 도 같아요)
public interface ExtraEntry {
void setExtra(Intent intent);
}
예를들면 이런 interface를 정의
public class StringExtraEntry implements ExtraEntry {
private final String key;
private final String value;
public StringExtraEntry(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public void setExtra(Intent intent) {
intent.putExtra(key, value);
}
}
public class SimpleIntentBuilder implements IntentBuilder {
private final List<ExtraEntry> extras = new ArrayList<>();
private final Class<? extends Activity> targetActivityClass;
public SimpleIntentBuilder(Class<? extends Activity> targetActivityClass) {
this.targetActivityClass = targetActivityClass;
}
public void putExtra(String key, String value) {
extras.add(new StringExtraEntry(key, value));
}
@Override
public Intent build(Context context) {
Intent intent = new Intent(context, targetActivityClass);
for (ExtraEntry extra : extras) {
extra.setExtra(intent);
}
return intent;
}
}
Activity/Fragment의 Callback Event 정리
Activity/Fragment에서 DI Container 내부의 세계에 접근할 필요가 자주 나온다
@Override
public void onStart() {
// DI Container 밖의 세계에 의존하고 있다
viewModel.onStart();
}
결과 로직이 밖으로 새어나가는 경향 (이 정도로 끝나면 좋지만)
@Module
public class AacLifecycleModule {
// FragmentActivity/Fragment
private final LifecycleOwner lifecycleOwner;
public AacLifecycleModule(LifecycleOwner lifecycleOwner) {
this.lifecycleOwner = lifecycleOwner;
}
@ViewScope
@Provides
Lifecycle provideAacLifecycle() {
return lifecycleOwner.getLifecycle();
}
}
Lifecycle을 Provide하는 Module을 정의한다
public class ListViewModel implements LifecycleObserver {
@Inject
public ListViewModel(Lifecycle lifecycle ){
lifecycle.addObserver(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
// do something
}
}
사용쪽은 이런 형태
자세하게는 https://developer.android.com/topic/libraries/architecture/lifecycle.html
장점
단점
public interface LifecycleCallback {
// 기저 interface를 준비해두는 것으로 등록 메소드에 아무거나 넣지않도록 한다
}
public interface OnStartCallback extends LifecycleCallback {
void onStart();
}
@ViewScope
public class LifecycleCallbackController {
private final List<OnStartCallback> onStartCallbackList = new ArrayList<>();
...
@Inject
public LifecycleCallbackController() {
}
public void register(LifecycleCallback callback) {
if (callback instanceof OnStartCallback) {
onStartCallbackList.add((OnStartCallback) callback);
}
... }
public void onStart() {
for (OnStartCallback onStartCallback : onStartCallbackList) {
onStartCallback.onStart();
}
}
@ViewScope
@Subcomponent(
modules = {
FragmentManagerDelegatedModule.class,
AacLifecycleModule.class
}
)
public interface LifecycleComponent {
LifecycleCallbackController lifecycleCallbackController();
}
public abstract class DaggerBaseActivity extends AppCompatActivity {
private LifecycleCallbackController lifecycleCallbackController;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// lifecycleComponent를 초기화
lifecycleCallbackController = lifecycleComponent.lifecycleCallbackController();
}
@Override
protected void onStart() {
super.onStart();
// Callback 호출
lifecycleCallbackController.onStart();
}
public class DetailViewModel implements OnStartCallback {
@Inject
public DetailViewModel(
LifecycleCallbackController lifecycleCallbackController){
lifecycleCallbackController.register(this);
}
@Override
public void onStart() {
// 뭔가를 한다
}
}
public class DetailViewModel {
@Inject
public DetailViewModel(LifecycleCallbackController lifecycleCallbackController ){
lifecycleCallbackController.register(new OnStartCallback() {
@Override
public void onStart() {
// 뭔가를 한다
}
});
}
}
깊이가 깊어진다&불필요한 inner class 생성된다
장점
단점
이상 Better Practice 모음였습니다
https://github.com/kr9ly/dagger2-sampleapp
comments powered by Disqus
Subscribe to this blog via RSS.
LazyColumn/Row에서 동일한 Key를 사용하면 크래시가 발생하는 이유
Posted on 30 Nov 2024