본문 바로가기
개발/Android

[Android] ViewModel Factory에 관하여

by tempus 2022. 5. 4.
반응형

ViewModel을 생성할 때는 일반적으로 2가지 경우가 있습니다.

① 파라미터가 있는 경우

② 파라미터가 없는 경우

 

일반적으로 파라미터가 없는 경우는 아래와 같이 사용해주면 됩니다. (lifecycle-extensions 모듈 필요)

class SimpleViewModel : ViewModel(){
		/** Content **/
}

생성하는 것은 2가지 방법으로 생성이 가능합니다.

class MainActivity : AppCompatActivity() {
 
    private lateinit var simpleViewModel: SimpleViewModel
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
		//1번 방법 
        simpleViewModel = ViewModelProvider(this).get(SimpleViewModel::class.java)
        
        //2번 방법 
        simpleViewModel = ViewModelProvider(this, ViewModelProvider.NewInstanceFactory()).get(SimpleViewModel::class.java)
    }
}

ViewModelProvider.NewInstanceFactory() 같은 경우는 ViewModelProvider.Factory 인터페이스를 구현하고 있습니다. 1번 또는 2번 방법을 자유롭게 사용하면 됩니다. 아니면 위임을 통해 생성하는 방법이 있습니다.

 

하지만 지금 관심이 있는 것은 파라미터가 있는 경우의 ViewModel 생성입니다. 지금까지는 Hilt나 Koin을 사용해서 생성을 했지만 없는 경우에는 어떻게 하는지 궁금하게 되었습니다. 그래서 찾아보니 다음과 같은 방법을 사용했습니다.

 

예제 코드는 아래의 Google에서 제공하는 Android 코드를 가지고 하였습니다.

https://developer.android.com/codelabs/kotlin-android-training-view-model?hl=ko#0 

 

Android Kotlin Fundamentals: 5.1 ViewModel  |  Android Developers

In this codelab, you learn how to use ViewModel to enable data to survive configuration changes such as screen rotations in your Android Kotlin app.

developer.android.com

 

일단 파라미터가 있는 ViewModel을 생성하기 위해 Custom Factory가 필요했습니다. 이 경우 ViewModelProvider.Factory를 implements 하여 구현을 해야 합니다.

 

create함수를 오버 라이딩하고 modelClass가 ScoreViewModel를 구현하였다면 해당 viewModel 클래스를 반환해줍니다. 만약 ScoreViewModel 클래스가 아니라면 exceoption을 던져줍니다.

class ScoreViewModelFactory(private val finalScore : Int) : ViewModelProvider.Factory {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(ScoreViewModel::class.java)){
            return ScoreViewModel(finalScore) as T
        }
        throw IllegalArgumentException("unKnown ViewModel class")

    }

}
class ScoreViewModel(finalScore : Int) : ViewModel(){

}

그리고 해당 Activity나 Fragment에서 다음과 같이 사용해줍니다.

class ScoreFragment : Fragment() {

    private lateinit var viewModel: ScoreViewModel

    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.score_fragment,
                container,
                false
        )
        
        viewModel = ViewModelProvider(this, ScoreViewModelFactory(ScoreFragmentArgs.fromBundle(requireArguments()).score)).get(ScoreViewModel::class.java)


        return binding.root
    }
}

 

만약 KTX를 통한 위임 방식으로 사용한다면 object키워드를 통해 Factory 클래스를 선언과 동시에 생성해줍니다.

class ScoreFragment : Fragment() {

    private val scoreViewModel by viewModels<ScoreViewModel>(){
        object : ViewModelProvider.Factory{
            override fun <T : ViewModel?> create(modelClass: Class<T>): T {
                return ScoreViewModel(ScoreFragmentArgs.fromBundle(requireArguments()).score)  as T
            }

        }
    }


    override fun onCreateView(
            inflater: LayoutInflater,
            container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {

        // Inflate view and obtain an instance of the binding class.
        val binding: ScoreFragmentBinding = DataBindingUtil.inflate(
                inflater,
                R.layout.score_fragment,
                container,
                false
        )

        Log.i("SCORE", scoreViewModel.getScore())

        return binding.root
    }
} 

 

결론적으로 파라미터가 있는 ViewModel을 생성할 때는 Factory 클래스를 커스텀할 필요가 있다고 느꼈습니다. 하지만 이를 사용하다 보니 ViewModel마다 해당하는 Factory 클래스를 만들어 주어야 해서 보일러 플레이트 코드가 많아지는 단점이 있었습니다. 그래서 의존성 주입 라이브러리인 Hilt나 Koin을 사용하면 좋겠다는 생각이 들었습니다.

반응형

댓글


loading