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을 사용하는 방법이다.

 

 

 

참고

https://www.geeksforgeeks.org/assisted-dependency-injection-in-viewmodel-with-dagger-hilt-in-android/

'안드로이드 > 아키텍처' 카테고리의 다른 글

MVC, MVP, MVVM, MVI 패턴  (0) 2022.08.02
복사했습니다!