Context Injection

sumi Yoo·2022년 10월 10일
0

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()를 호출할 것인지 말건지를 결정하는 것 같다. (이 부분은 더 찾아보자)

  • Class.isAssignableFrom()은 특정 Class가 어떤 클래스/인터페이스를 상속/구현했는지 체크합니다.
  • instanceof는 특정 Object가 어떤 클래스/인터페이스를 상속/구현했는지를 체크한다.

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 }
        }
    }
}

0개의 댓글

관련 채용 정보