Viewmodel에서 asset의 파일을 읽어온다거나 getString(), getDrawble()을 사용하려면 Context가 필요하다. 기존에는 MainActivity에 getContext()라는 함수를 하나 만들어줬고 ViewModel에서 이 함수를 호출해 application context를 가져와 사용하는 방법으로 구현했지만, 이 경우에는 다른 액티비티에선 이 viewmodel을 사용할 수 없는 문제가 생긴다.
Data Layer에서 Context가 필요할땐, 외부에서 의존성을 주입하는 형태로 해결 할 수 있다.
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val factory = MyViewModelFactory.getInstance(application)
viewModel = ViewModelProvider(this, factory)[MainViewModel::class.java]
}
}
우선 메인 액티비티부터 보면, MyViewModelFactory에서 인스턴스를 가져와 ViewModelProvider에 넘겨주어 우리가 앞으로 사용할 viewModel을 만들었다.
class MyViewModelFactory private constructor(
private val application: Application,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = when {
modelClass.isAssignableFrom(MainViewModel::class.java) -> createMainViewModel() as T
else -> throw IllegalArgumentException("UnknownViewModelclass:${modelClass.name}")
}
private fun createMainViewModel(): MainViewModel {
val sharedPreferences = MySharedPreferences.getInstance(application)
val assetLoader = MyAssetLoader.getInstance(application)
return MainViewModel(sharedPreferences, assetLoader)
}
companion object {
private var instance: MyViewModelFactory? = null
fun getInstance(
application: Application,
): MyViewModelFactory {
val cached = instance
return cached ?: MyViewModelFactory(application).also { created -> instance = created }
}
}
}
getInstance()은 싱글턴으로 객체의 인스턴스를 생성하기 위함이다. ViewModelProvider에 값을 넘겨줄 때 이 클래스의 create()가 호출되는 것 같다. create()는
ViewModelProvider(this, factory)[MainViewModel::class.java]
이 부분의 [MainViewModel::class.java] 이 값을 비교해서 createMainViewModel()를 호출할 것인지 말건지를 결정하는 것 같다. (이 부분은 더 찾아보자)
createMainViewModel()에서 필요한 클래스에 context를 넣어 인스턴스를 생성하고 생성된 이 인스턴스를 viewmodel에 다시 넘겨주어 생성한다.
최종적으로 만들어진 viewmodel이 메인액티비티의 viewmodel 멤버 변수로 들어간다.
class MainViewModel(
private val mySharedPreferences: MySharedPreferences,
private val myRepository: MyAssetLoader,
) : ViewModel()
class MyAssetLoader private constructor(private val resources: Resources) {
fun loadAsset(filePath: String): String {
val inputStream = resources.assets.open(filePath)
val size = inputStream.available()
val bytes = ByteArray(size)
inputStream.read(bytes)
return String(bytes)
}
companion object {
private var instance: MyAssetLoader? = null
fun getInstance(
application: Application,
): MyAssetLoader {
val cached = instance
return cached ?: MyAssetLoader(application.resources)
.also { created -> instance = created }
}
}
}
class MySharedPreferences private constructor(context: Context) {
private val sharedPreferences =
context.getSharedPreferences("prefs", Context.MODE_PRIVATE)
private val editor =
sharedPreferences.edit()
fun saveText(text: String) {
editor.putString(PREFERENCE_TEXT_KEY, text)
editor.commit()
}
fun loadText(): String? =
sharedPreferences.getString(PREFERENCE_TEXT_KEY, null)
companion object {
private const val PREFERENCE_TEXT_KEY = "PREFERENCE_TEXT_KEY"
private var instance: MySharedPreferences? = null
fun getInstance(
application: Application,
): MySharedPreferences {
val cached = instance
return cached ?: MySharedPreferences(application.applicationContext)
.also { created -> instance = created }
}
}
}