Assisted Injection
안드로이드 프로젝트에서 Dagger-Hilt 종속성 주입을 사용하는 경우 일반적으로 클래스에 종속성 객체를 주입할 때 지정된 매개변수를 자동으로 전달하여 이를 구성한다. 그러나 런타임 중에 일부 매개변수를 수동으로 제공하여 객체를 구성해야 하는 경우가 발생할 수 있다. Dagger-Hilt에서는 Assisted Injection
기능을 통해 이 방법을 제시하고 있다.
예제를 통한 Assisted Injection 사용 방법 이해
한 사용자의 플레이리스트를 화면을 위한 UserPlayListFragment에 삽입될 UserPlayListViewModel이 있다고 예를 들어 설명해보자. 이 Fragment가 화면에 표시될 때 유저의 플레이리스트 목록을 가져와 UI에 표시하기 위해 해당 API를 호출해야 하므로 getUserPlaylist() 메서드르 init{}에서 호출한다고 했을 때, getUserPlaylist()에서는 GetUserPlaylistUseCase invoke 메소드를 추가로 호출한다. 따라서 이 뷰모델에서 GetUserPlaylistUseCase 객체가 필요하고 Dagger-Hilt 도움으로 뷰모델 생성자에 GetUserPlaylistUseCase를 주입하고 있다.
하지만 실제로 사용자의 플레이리스트를 가져오려면 getUserPlaylist() 함수에는 런타임시 해당 userId를 전달해야 하며 ViewModel이 해당 Fragment에서 인스턴스화 즉시 userId를 가져와야 한다. 하지만 어떻게 가능할까? 이 때 사용하는 게 Assisted Injection이다.
/**
* 런타임 중에 의존성(userId)을 추가하기 위해
* @Inject 대신 @AssistedInject를 사용한다
*/
class UserPlaylistViewModel @AssistedInject constructor(
private val getUserPlaylistUseCase: GetUserPlaylistUseCase,
//런타임 중 추가해야 하는 의존성에 @Assisted을 붙여야 한다
@Assisted private val userId: Int
) : ViewModel {
init {
getUserPlaylist(userId)
}
private fun getUserPlaylist(userId: String) {
.
.
val response = getUserPlaylistUseCase(userId)
}
/**
* 이 뷰모델의 팩토리이다
* Dagger-Hilt가 이를 알게 하기 위해
* @AssistedFactory 어노테이션을 붙여야 한다.
*/
@AssistedFactory
interface IdAssertedFactory {
fun create(id: Int): UserPlaylistViewModel
}
companion object {
/**
* 이 메서드를 companion object에 추가함으로써
* Fragment/Activity에서 해당 메소드에 접근 할 수 있다.
*/
fun provideFactory(
assistedFactory: AssistedFactory,
userId: Int
): ViewModelProvider.Factory {
object : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
//IdAssertedFactory의 create 메소드를 통해 실제 이 뷰모델의 인스턴스를 생성한다
return assistedFactory.create(userId) as T
}
}
}
}
}
예제 코드를 보면 일반 Inject와 비교하여 AssistedInject 경우 몇 가지 변경 사항이 있다
- 생성자 주입을 위해
@Inject
대신@AssistedInject
를 사용한다 - 런타임에 제공해야 하는 인수는
@Assisted
로 주석 처리해야 한다.
위의 변경 사항을 수행해도 UserPlayListViewModel을 직접 Activity/Fragment에 주입하기 위해서는 팩토리를 사용하여 실제 클래스의 인스턴스을 생성하기 때문에 먼저 팩토리를 생성해야 한다. 위의 예시에서 IdAssertedFactory 인터페이스이다.
AssistedFactory
@AssistedFactory
로 팩토리에 어노테이션을 단다. 이 어노테이션은 Dagger-Hilt에 이 인터페이스가 Assisted Injection이 필요한 뷰 모델 클래스의 인스턴스를 생성하는 데 사용된다는 것을 알려준다.- 이 팩토리 내부에서 뷰 모델 클래스의 인스턴스를 반환하는 역할을 하는
create
라는 이름의 메소드를 만들고 런타임시 제공되는 인수 userId만 수락한다.
이제 UserPlayListFragment 예제를 살펴 보도록 하자
@AndroidEntryPoint
class UserPlayFragment : Fragment() {
private lateinit var binding: UserPlayFragmentBinding
@Inject
lateinit var idAssertedFactory: UserPlaylistViewModel.IdAssertedFactory
private val viewModel: UserPlaylistViewModel by viewModels {
UserPlaylistViewModel.providesFactory(
assistedFactory = idAssertedFactory,
val userId : Int = intent.extras?.get(USER_PLAYLIST_ACTIVITY_KEY) as Int
)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = UserPlayFragmentBinding.inflate(inflater, container, false)
return binding.root
}
companion object {
const val USER_PLAYLIST_ACTIVITY_KEY = "userId"
}
userId를 인텐트를 통해 전달 받았다고 가정해보자. 먼저 IdAssertedFactory 팩토리를 주입했다. 그리고 코틀린의 위임 속성 키워드 by
를 사용하여 뷰모델을 인스턴스화한 다음 UserPlayListViewModel.providesFactory() 메서드를 호출하여 위에서 주입한 IdAssertedFactory 및 수동으로 지원하는 userId를 인수 값으로 넣는다. 이러한 방법으로 런타임에 제공된 userId를 UserPlayListViewModel에 주입할 수 있다. 이것이 런타임에 객체/종속성을 구성하기 위해 인수를 전달할 수 있게 해주는 Dagger-Hilt와 함께 Assisted Dependency Injection을 사용하는 방법이다.
참고
'안드로이드 > 아키텍처' 카테고리의 다른 글
MVC, MVP, MVVM, MVI 패턴 (0) | 2022.08.02 |
---|