본문 바로가기
프로그래밍 놀이터/안드로이드, Java

[android] Dagger2 for Android Beginners

by 돼지왕 왕돼지 2018. 12. 3.
반응형

 

@, @ Componnet, @ Module, @ Named, @ Provides, @Component modules, @Inject, @Module includes, @Qualifier, @scope, Advanced Dagger2, annotation processor, compile time dependency injection framework, constructor injection, ContextModule, CREATE, dagger, dagger2, Dagger2 annotation, Dagger2 for Android Beginners, dependency graph, dependency injection, di 의 비밀, di 장점, field injection, inject method, inversion of control, Method Injection, Reflection, static

 

-

Dagger 는 static, compile-time dependency injection framework 이다.

기존 버전(1.x)은 Square 에 의해 만들어졌고, 새 버전은 (2.x) Google 에 의해 유지보수되고 있다.

 

 

-

Hard dependency 는..

    reusability 를 감소시킨다.

    testing 을 어렵게 한다.

    코드의 scale up 이나 유지보수를 어렵게 한다.

 

 

-

Dependency 에는 다음의 type 이 있다.

 

class, interface ,method/field, direct/indirect

 

 

-

java 에서 new operator 로 instance 를 생성하면, 독립적으로 test 되기가 어렵다. 이를 dependency 라 불린다.

 

 

-

Dependency Injection 이라 함은.. 하나의 object 가 다른 object 에 dependency 형태로 제공되는 기술을 말한다.

Dependency 는 사용되는 object 를 말하고, injection 은 이 dependency 를 다른 곳으로 전달하는 것을 말한다.

Inversion of control 컨셉을 이용한다.

 

 

-

dependency injection 에 의존하는 앱에서 objects 들은 주입이 되고, 사용자가 스스로 생성하지 않는다.

 

 

-

Dagger 는 reflection 을 쓰는 것이 아니라 compile time 에 annotation processor 를 통해 코드생성을 한다.

 

 

 

 

Dagger2 annotation 을 이해하기.

 

-

@Inject 는 JSR-330 spec 을 따르며 injection 을 받는 곳을 가르킨다.

Constructor, Field, Method injection 이 있다.

public class Starts{

    @Inject

    Allies allies;

 

    @Inject // constructor injection 은 해당 class 가 @Inject 로 마킹된 곳에 자동 inject 된다. (module 에 정의하지 않아도)

    public Starts(){

 

    }

 

    @Inject

    private void prepareForWar(){

 

    }

}

 

 

-

@Componnet annotation 은 모든 것을 연결해주는 interface 이다.

@Module 과 @Inject 의 다리 역할을 한다.

@Module 에 정의된 것을 @Inject 가 정의된 곳에 inject 해주는 역할을 한다. 

 

 

-

@Component 로 마킹된 녀석은

Dagger<OriginalClassName> 으로 코드가 생성되며, create() method 를 갖는다.

( 예를 들어 ABC 라는 class(혹은 interface) 가 있고, @Component 로 마킹되면 DaggerABC class 가 생성된다. )

 

 

-

public class Starks{

    @Inject

    public Starks(){ }

}

 

public class Boltons{

    @Inject

    public Boltons(){ }

}

 

public class War{

    @Inject

    public War(Starks starks, Boltons bolton){

        ..

    }

}

 

@Component

interface BattleComponent{

    War getWar();

}

 

// 사용

BattleComponent component = DaggerBattleComponent.create();

// injecting 이 자동으로 된 녀석이 나온다.
// War 의 constructor 에 @Inject 가 마킹되었기 때문이며,
// War 의 Starks 와 Boltons 의 constructor 에 @Inject 가 마킹되어 자동으로 inject, inject 되어 나온다.

War war = component.getWar();

 

 

-

DaggerBattleComponent 안으로 jump 해서 들어가면 생성된 코드를 볼 수 있다.

 

 

-

@Module 은 inject 될 것을 provide 해주는 역할을 한다.

Android 를 예로 들면 ContextModule 이라는 클래스를 정의하면서 @Module 을 사용하면 ApplicationContext 나 Context 등을 제공해줄 수 있다.

 

 

-

@Provides 는 module 안쪽 method 에 marking 을 하는데 사용하며, dependency 를 실제로 제공해주는 역할을 한다.

 

 

-

@Module

public class BraavosModule { 

    Cash cash; 

    Soldiers soldiers; 

 

    public BraavosModule(Cash cash, Soldiers soldiers){ 

        this.cash=cash; 

        this.soldiers=soldiers; 

    } 

 

    @Provides //Provides cash dependency

    Cash provideCash(){ 

        return cash; 

    } 

 

    @Provides //provides soldiers dependency

    Soldiers provideSoldiers(){ 

        return soldiers; 

    } 

 

@Component 정의할 때 module 을 지정할 수 있다.

@Component(modules = BraavosModule.class)

interface BattleComponent{

    War getWar();

    Cash getCash();

    Soldiers getSoldiers();

}

 

사용할 때는..

DaggerBattleComponent.builder().braavosModule(new BraavosModule(cash, soldiers)).build();

// BraavosModule 을 제공해주어야 한다.

 

 

 

 

 

Advanced Dagger2

 

https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-advanced-part-i-1e14fccf2cc8

위 링크를 통해 제대로 설명, 코드, 그래프를 보는 것이 추천된다.

 

-

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);

    }

}

 

 

-

위 코드는 두 가지 문제가 두드러진다.

1. init 과정이 깔끔하지 못해 보인다.

2. Testability 가 떨어진다. 예를 들면 Picasso 도 constructor 로 inject 되는 것이 좋다.

 

 

-

많은 사람들이 DI 와 Dagger2 를 어려워하는데 그 이유는 무언가가 빠졌기 때문이다.

조각과 node 를 연결해주는 것이 빠졌다.

이 빠진 것(혹은 비밀)은 dependency graph 이다.



-

Dependency Graph 는 단순한 diagram 으로 class 간의 dependency 를 화살표와 라인으로 마킹한 것이다.

DI graph 에서 중요한 것은 dependency graph 의 꼭지이다. (참조를 하기만 하는 놈, 혹은 interface 로 사용되는 녀석)

이 친구들만 component 에서 잘 제공해주면 된다.

 

그리고 Module 에서, 그 안의 Provider 에서는 dependency 에 있는 녀석들을 param 으로 받아서 전달해주면 된다.

 

 

-

상호간의 dependency 있는 module 간은 @Module annotation 에 includes 를 사용해주면 된다.

ex) 

@Module(includes = OkHttpCLientModule.class)

 

 

-

Module 사용처(연결자)인 Component 에서는 @Component annotation 에 modules 를 사용해주면 된다.

ex) 

@Comoponent(modules = )

 

 

-

@Scope annotation 은 dagger 에게 single instance 를 만들 것을 알려준다.

DaggerComponent.build() 를 여러번 호출해도 singleton 으로 작동한다.

 

우리가 해줄 것은 다음과 같이 annotation 을 만들어서 적용하는 것이다.

@Scope

@Retention(RetentionPolicy.CLASS)

public @interface RandomuserApplicationScope{ }

 

@RandomUserApplicationScope

@Component(modules = )

public interface RandomUserComponent{ … }

 

@Module(includes = OkHttpClientModule.class)

public class RandomUserModule{

    @RandomUserApplicationScope

    @Provides

    public Retrofit retrofit(OkHttpClient okHttpClient, GsonConverterFactory gsonConverterFactory, Gson gson){

        return new Retrofit. ...

    }

}

 

 

-

안드로이드에서 주로 쓰는 Context 는 ApplicationContext 와 Activity context 가 있다.

ApplicationContext 의 경우 ContextModule 이라는 녀석을 사용해서 provide 하면 되지만 ActivityModule 은 없다. 그래서 만들어야 한다.

@Module

public class ActivityModule{

    private final Context context;

 

    ActivityModule(Activity context){

        this.context = context;

    }

 

    @RandomUserApplicationScope

    @Provides

    public Context context(){

        return context;

    }

}

 

 

-

ActivityModule 을 만들었지만 return 하는 것은 모두 Context 라..

ActivityModule 의 것을 inject 할지 ContextModule 의 것을 inject 할지 알지 못한다.

이 떄 @Named annotation 을 쓰면 된다.

 

 

-

ActivityModule 의 context provider 에는 @Named(“activity_context”) 를..

ContextModule 의 context provider 에는 @Named(“application_context”) 를 넣어준다.

 

그리고 inject 받는 곳에서도 param 앞에 @Named(“application_context”) 와 같이 써준다.

 

 

-

@Named 대신 @Qualifier 를 사용할 수도 있다.

@Qualifier

public @interface ApplicationContext { }

 

그리고 ContextModule 의 context provider 에 @ApplicationContext 를 넣어준다.

 

Inject 를 받는 param 에 @ApplicationContext 를 마킹해준다.

 

 

-

Activity level 에 dependency 를 만드는 경우.. lifecycle 에 따라 dependency 도 제거되길 바랄 수 있다. 그리고 Application 은 singleton 처럼 사용되길 바랄 것이다.

이렇게 lifecycle 이 다른 곳에 DI 를 하고 싶을 때는 각각의 component 에 대해 각각의 module 을 만드는 것이 추천된다.

 

 

-

먼저 @Scope 를 이용해 각각의 scope 를 지정해준다.

@interface MainActivityScope 와 @interface ApplicationScope

 

 

-

@Component(dependencies = RandomUserComponent.class) 

위와 같이 명시한 것은 dagger 에게 추가적인 dependency 가 필요할 경우 RandomUserComponent 를 참조하라고 이야기하는 것이다.

 

 

-

@Component 에 inject method 를 만들어 injection 을 의도적으로 주입받는 방법이 있다.

@Component(modules = MainActivityModule.class, dependencies = RandomUserComponent.class)

@MainActivityScope

public interface MainActivityComponent {

    void injectMainActivity(MainActivity mainActivity);

}

 

MainActivityComponent mainActivityComponent = DaggerMainActivityComponent.builder()

    .mainActivityModule(new MainActivityModule(this))

    .randomUserComponent(RandomUserApplication.get(this).getRandomUserApplicationComponent())

    .build();

    mainActivityComponent.injectMainActivity(this);

 

 

-

그래서!! 최종 코드는 여기를 보자.

https://github.com/Hariofspades/Dagger-2-Advanced/tree/InjectMainActivity/app/src/main/java/com/hariofspades/dagger2advanced

 

 

-

참고 자료 : https://medium.com/@harivigneshjayapalan/dagger-2-for-android-beginners-introduction-be6580cb3edb

 

더보기

@, @ Componnet, @ Module, @ Named, @ Provides, @Component modules, @Inject, @Module includes, @Qualifier, @scope, Advanced Dagger2, annotation processor, compile time dependency injection framework, constructor injection, ContextModule, CREATE, dagger, dagger2, Dagger2 annotation, Dagger2 for Android Beginners, dependency graph, dependency injection, di 의 비밀, di 장점, field injection, inject method, inversion of control, Method Injection, Reflection, static

 

 

 

반응형

댓글