일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- java
- mvvm
- 안드로이드 mvp
- graphQL
- Design Pattern
- flutter
- Data structure
- 자바
- Kotlin
- 안드로이드
- 안드로이드 테스트
- 우분투 파이썬
- PYTHON
- 유니티
- dagger-hilt
- ubuntu python
- 웹크롤링
- Apollo Server
- unit test
- 파이썬 크롤링
- LinkedList
- prisma
- Nexus GraphQL
- Android
- 안드로이드 디자인패턴
- Android test
- Apollo GraphQL
- MVVM pattern
- 자바기초
- Dependency Injection
- Today
- Total
Hun's Blog
Android Dagger-Hilt 적용기 (4) - MVVM 본문
아래의 오픈소스를 통해서 분석하고 학습하여 개인프로젝트에 Dagger-Hilt적용
github.com/android/architecture-samples/tree/dev-hilt
MVVM에서 Model은 이전에 만들어 두었으니 View와 ViewModel을 만들어보자.
View
@AndroidEntryPoint
class MainActivity : DataBindingActivity() {
@VisibleForTesting val viewModel: MainViewModel by viewModels()
private val binding: ActivityMainBinding by binding(R.layout.activity_main)
override fun onCreate(savedInstanceState: Bundle?) {
onTransformationStartContainer()
super.onCreate(savedInstanceState)
binding.apply {
lifecycleOwner = this@MainActivity
adapter = PokemonAdapter()
vm = viewModel
}
}
}
@AndroidEntryPoint
dagget-hilt의 annotation
다음의 클래스를 먼저 보도록하겠다.
@HiltAndroidApp
class PokedexApp : Application()
공식 문서 : `Application에 @HiltApplication 으로 hilt를 설정하면 @AndroidEntryPoint 주석이 있는 다른 클래스에 종속항목을 제공할수 있다`
View를 먼저 파악하기위해 viewModel은 나중으로 미루고 ActivityMainBinding에 대해 정리해보자면
MainActivity와 연결되어있는 main_activity.xml를 다음과 같이 설정하면 DataBinding을 사용할 수 있다.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="vm"
type="com.skydoves.pokedex.ui.main.MainViewModel" />
<variable
name="adapter"
type="com.skydoves.pokedex.ui.adapter.PokemonAdapter" />
</data>
...
</layout>
액티비티에서 DataBinding을 연결하는 일반적인 코드는 다음과 같다.
private lateinit var binding: MainActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this,R.layout.main_activity)
}
다시 오픈소스 액티비티를 살펴보면 일반적으로 AppCompatActivity() 가 아닌 DataBindingActivity()를 상속받고 있다.
private val binding: ActivityMainBinding by binding(R.layout.activity_main)
위 코드가 액티비티와 xml을 연결해주고 있다.
binding() 메서드를 따라가보면 다음과 같은 로직을 만날 수 있다.
abstract class DataBindingActivity : AppCompatActivity() {
protected inline fun <reified T : ViewDataBinding> binding(
@LayoutRes resId: Int
): Lazy<T> = lazy { DataBindingUtil.setContentView<T>(this, resId) }
}
해당 클래스는 기본으로 액티비티가 상속받아 사용하는 AppCompatActivity()를 상속받고있다.
protected -> private + subclass 에서 사용할 수 있음
inline fun -> 함수를 인자로 전달하는 lamda expression 을 정의할 때 해당 로직이 자바로 변환되고나서 람다로 선언된 메서드를 사용하기 위해 객체를 생성해야하고 이는 성능을 떨어뜨릴 수 있다. 이때 inline fun 키워드를 사용하면 객체를 생성하지 않고 호출하는 위치에 복사한다. 바이트 코드의 양은 많아지지만 추가적으로 객체생성을 하지 않기에 성능저하를 막을 수 있다.
reified -> Generic에서 <T>는 런타임에서 접근할 수 없다. 접근하고 싶은 경우 메서드를 만들고 파라미터로 타입을 전달해주어야 한다. inline fun 메서드에서 refied type 을 사용할 경우 추가 메서드 없이 런타임에서 <T>에 접근할 수 있다. reified는 inline fun 조합에서 사용할 수 있다.
layz -> lateinit 과 마찬가지로 초기화를 지연시킬 때 사용한다. 두가지는 다음과같은 차이점이있다.
- lateinit은 var 타입만 가능하고 lazy는 val 타입만 가능
- lateinit은 primitive type은 불가능하나 lazy는 가능
- lateinit은 Non-null 타입만 가능하나 lazy는 둘 다 가능
- lateinit은 로컬 변수에서는 불가능 하나 lazy는 가능
lateinit 으로 선언한 코드에서 초기화 코드를 빼먹으면 런타임 에러가 발생한다. 이를 방지하기 위해 layz를 사용하는 것이 적절하다. 오픈소스의 lazy { DataBindingUtil.setContentView<T>(this, resId) } 와 같이 lazy는 블록안에 초기화코드가 함께 있다.
이제 액티비티로 돌아와 databinding이 연결되는 코드를 다시한번 살펴보자.
private val binding: ActivityMainBinding by binding(R.layout.activity_main)
private val binding: ActivityMainBinding by binding(R.layout.activity_main)
by -> 객체지향에서 상위 클래스 내용이 변경되는 경우 하위 클래스가 상위 클래스에 의존하고 있기 때문에 뜻하지않은 에러가 발생한다. 코틀린에서는 이와같은 상속으로 인한 종속성, 의존성 문제를 방지하기 위해 기본적으로 클래스가 final이다. 또한 문제를 해결하기 위한 대안으로 Delegation pattern을 지원한다.
하나의 클래스를 다른 클래스에 Delegation 하고 Delegation된 클래스가 가지는 인터페이스 메서드를 별도의 참조 없이 호출할 수 있도록 해준다는 것인데 이러한 Delegation을 by키워드를 사용하여 구현한다.
- 특정 처리를 다른 객체에게 넘기는 것을 의미함.
- 다른 객체는 클래스 내부(포함)에 가지고 있음.
ViewModel
이번엔 viewModel 을 알아보도록 하자.
@VisibleForTesting val viewModel: MainViewModel by viewModels()
@VisibleForTesting
private으로 선언시 테스트코드에서 호출할 수 없기때문에 private 키워드를 제외하였을 때 다른 코드에서 접근할 수 없도록 해당 annotation을 추가하여 테스트 코드가 아닌 다른 곳에서 호출할 수 없다는 것을 나타냄
위 코드는 공식문서에서 알려주고 있는 기본적인 viewModel() 생성방법이다.
override fun onCreate(savedInstanceState: Bundle?) {
// onTransformationStartContainer()
super.onCreate(savedInstanceState)
binding.apply {
lifecycleOwner = this@MainActivity
//adapter = PokemonAdapter()
vm = viewModel
}
}
viewModel과 dataBinding 생성이 완료되고 binding 객체에 lifecycleOwner와 viewModel을 연결한다.
주석처리된 부분은 나중에 알아보도록 하겠다.
이제 viewModel 로직을 알아보도록 하겠다.
class MainViewModel @ViewModelInject constructor(
private val mainRepository: MainRepository,
@Assisted private val savedStateHandle: SavedStateHandle
) : LiveCoroutinesViewModel() {
...
}
@ViewModelInjection
hilt의 annotation이다.
별도의 java injection 이 아닌 hilt의 annotaion을 사용하면 조금 더 편하게 viewModel에 대한 DI를 구현할 수있다.
@Assisted private val savedStateHandle: SavedStateHandle
공식문서에 위 항목을 추가해야한다고 나와있다.
SavedStateHandle
- Saved State Handle 정보가 ViewModel로 전달됨
- SavedStateViewModelFactory를 사용해야만 ViewModel을 통해서 SvaedStateHandled을 전달 가능
- SvaedStateHandled은 Key-Value로 이루어진 Map 형태
- 시스템이 프로세스를 종료하더라도 동일한 정보를 유지
- get(String) -> 값 읽기
- getLiveData(String) -> MutableLiveData가 반환, LiveData를 통해 값을 사용 가능
- set(String, Object) -> 값 쓰기
viewModel을 생성하면 Repository가 추가된다.
내용이 너무 길어지기 때문에 다음장에서 repository를 추가한 후 개인 프로젝트에 적용하여 테스트해보도록 하겠다.
참고자료
developer.android.com/training/dependency-injection/hilt-android?hl=ko
codechacha.com/ko/kotlin-inline-functions/
sungjk.github.io/2019/09/07/kotlin-reified.html
medium.com/@joongwon/kotlin-kotlin-lazy-initialization-901079296e43
velog.io/@jojo_devstory/%EC%BD%94%ED%8B%80%EB%A6%B0-Kotlin-by-by-the-way-what-is-this
'Android' 카테고리의 다른 글
[번역]Coroutines on Android (part I): Getting the background (0) | 2020.11.11 |
---|---|
Android Dagger-Hilt 적용기 (5) - Repositoy (3) | 2020.11.11 |
Android Dagger-Hilt 적용기 (3) - Network (0) | 2020.11.07 |
Android Dagger-Hilt 적용기 (2) - Local Database (0) | 2020.11.06 |
Android Dagger-Hilt 적용기 (1) - Dependency Injection (0) | 2020.11.06 |