Warning : 이 포스팅은 Dagger2에 대한 이해가 필요하며, Dagger2에 대해서는 다루지 않고 있습니다. Dagger2에 대한 내용은 Dagger2를 알아보자편을 참조해주세요.

AssistedInject란 무엇인가?

Square에서 만든 수동 의존성 주입도구로 Dagger2와 함께 사용된다.

팩토리패턴(Factory Pattern)은 어떤 오브젝트를 생성하기 위한 패턴으로 매개 변수화 및 종속성을 결합하는 객체를 만들기 위해 잘 설정된 패턴이다. 팩토리는 불안정할 수 있으며, 많은 보일러플레이트 코드를 포함하고 있다. Dagger와 AssitedInject를 활용하면 간단한 인터페이스로부터 자동으로 생성되는 팩토리 구현체를 통해 많은 보일러플레이트 코드를 제거할 수 있다.

AssistedInject는 언제 필요할까?

Dagger2는 컴파일 타임에 의존성 주입 관련 코드, 즉 오브젝트 그래프를 생성하는 라이브러리 이기 때문에 런타임에 의존성 주입 이후에 그래프를 수정하는 것이 어렵다. AssitedInject는 Dagger의 컴파일타임 안정성과 의존성 주입 이후 원하는 의존성을 얻을 수 있도록 도와준다. 쉽게 말해서 동적인 파라미터들과 함께 의존성 주입을 할 수 있다는 것이다. 

예를 들면 ViewModel을 Dagger와 함께 쓰려고 하면 SavedStateHandle를 Dagger 그래프에 포함 시킬 수 없는 문제가 있는데 이때 AssistedInject를 사용하면 해결할 수 있다. 또 다른 예로는WorkManager를 사용할 때 Worker의 WorkerParameters 등을 의존성 주입하기 어려운 문제가 있는데 이를 해결할 수 있다.

AssistedInject 설정하기

dagger2 설정과는 별도로 아래의 내용을 build.gradle에 추가해야 한다. 최신 버전 확인

implementation 'com.squareup.inject:assisted-inject-annotations-dagger2:0.5.2'
kapt 'com.squareup.inject:assisted-inject-processor-dagger2:0.5.2'

AssitedInject 동작 방식

일반적으로 Dagger는 다음과 같이 컴포넌트가 가지고 있는 모듈로부터 의존성을 타겟 오브젝트에 주입한다. 

일반적인 Dagger의 오브젝트 그래프

AssitedInject는 Dagger 그래프로부터 의존성을 주입받고, 이후 별도의 매개변수를 추가하여  인스턴스를 생산할 수 있는 Factory를 제공한다. 

Dagger와 AssistedInject를 같이 사용하는 오브젝트 그래프

AssitedInject 간단 구현 예제

MainActivity에서 사용자로부터 Robot의 이름을 입력받아 RobotActivity에 전달하고 출력하는 예제를 만들어보자.

예제코드는 다음과 같다. 예제코드는 github에서 확인 할 수 있다.

class RobotActivity : AppCompatActivity() {

    @Inject
    lateinit var robotFactory: Robot.Factory

    private lateinit var message: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        DaggerRobotComponent.create().inject(this)
        setContentView(R.layout.activity_robot)
        message = findViewById(R.id.message)

        val name = intent.getStringExtra("name")
        val robot = robotFactory.create(name)
        message.text = robot.say()
    }
}
@Component(modules = [RobotModule::class])
interface RobotComponent{
    fun inject(activity: RobotActivity)
}
@AssistedModule
@Module(includes = [AssistedInject_RobotModule::class])
class RobotModule {
    @Provides
    fun provideRandomId() = Random.nextInt(10000)
}
class Robot @AssistedInject constructor(@Assisted val name: String, val id: Int) {

    fun say():String = "Hello, I'm ${name}.\nMy id is $id"

    @AssistedInject.Factory
    interface Factory {
        fun create(name: String): Robot
    }
}

살펴볼 내용 주요내용은 모듈과 의존성 주입을 받을 타겟 오브젝트다. 

모듈 클래스(RobotModule)를 살펴보면 @AssitedModule이 추가된 것을 볼 수 있다. 그리고 @Module 어노테이션의 includes에 AssistedInject_RobotModule 클래스를 추가하였다.

눈치 챘겠지만, 모듈 클래스에 @AssitedModule을 추가하면 컴파일 타임에 또 다른 모듈클래스를 생성하게 된다. 이 모듈 클래스의 이름은 부모 모듈클래스의 이름을 따르며 AssitedInject_라는 접두어가 붙게 된다. 위 예제코드에서는 부모 모듈이 RobotModule이기 때문에 AssitedInject_RobotModule이라는 클래스가 생성된 것을 확인할 수 있다.

생성된 모듈 클래스는 다음과 같이 생겼다.

@Module
public abstract class AssistedInject_RobotModule {
  private AssistedInject_RobotModule() {
  }

  @Binds
  abstract Robot.Factory bind_com_charlezz_assitedinjectsample_Robot(Robot_AssistedFactory factory);
}

Robot_AssistedFactory를 Robot.Factory로 바인딩하는 메서드가 존재하는 것을 확인할 수 있다. 이러한 코드가 생성되는 이유를 알아보기 위해 타겟 오브젝트(Robot)를 살펴보자.

Robot 클래스 내부에 Robot.Factory 인터페이스가 포함된 것을 살펴볼 수 있다. 이 Factory 인터페이스에는 @AssitedInject.Factory라는 어노테이션이 붙어 있는데, 이 어노테이션을 통해 컴파일 타임에 Factory 구현체 클래스가 자동으로 생성된다. 그 구현체 클래스 이름이 바로 Robot_AssistedFactory다. 

public final class Robot_AssistedFactory implements Robot.Factory {
  private final Provider<Integer> id;

  @Inject
  public Robot_AssistedFactory(Provider<Integer> id) {
    this.id = id;
  }

  @Override
  public Robot create(String name) {
    return new Robot(
        name,
        id.get());
  }
}

Robot_AssistedFactory의 create() 메서드를 통해 Robot 인스턴스를 생성하는 것을 확인할 수 있다. 자세히 살펴보면 기존 Dagger 그래프로 부터 id를 주입받고, name은 동적인 파라미터이기 때문에 create 메서드의 매개변수로 입력 받게 된다. 이 부분이 AssistedInject 라이브러리의 핵심적인 기능으로 많은 보일러 플레이트 코드를 제거해준다.

AssistedInject 라이브러리는 메서드 시그니처를 기반으로 코드를 생성하므로, 타겟오브젝트의 @Assisted 생성자 파라미터 이름과 Factory 메서드 파라미터 반드시 일치해야한다.  그렇지 않으면 컴파일 타임에 에러가 발생한다. 위의 코드에서는 name이라는 매개변수명이 일치하는 것을 확인할 수 있다.

다시 정리하자면, AssitedInject 라이브러리를 사용하여 동적 파라미터는 주입받기 위해서는  @AssistedInject.Factory와 함께 Factory 인터페이스를 생성해야하고, 의존성 주입받는 타겟 오브젝트는 생성자 주입 부분에 대해서 @Inject 대신 @AssistedInject를 사용한다. 그리고 Factory를 구현한 클래스에 의한 동적 파라미터 주입과 대거 오브젝트 그래프에 의한 의존성 주입을 구분하기 위해 @Assisted 어노테이션을 매개변수에 사용한다.

Conclusion

AssitedInject는 Dagger를 사용하는 개발자라면 꼭 한번 둘러볼만한 흥미로운 라이브러리다. Dagger가 가진 특징 때문에 얻을 수 있는 이점이 많지만, 한편으로는 Dagger의 제약조건으로 인해 개발시 많은 어려움들에 직면했었다. AssitedInject로 몇가지 어려운 부분이 해소되어 기쁘다.

카테고리: Dagger2

0개의 댓글

답글 남기기

Avatar placeholder

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다.