[팀 프로젝트] Yu Market 첫번째 코드 리뷰 3편 (3)

쓰리원·2022년 2월 6일
0

TeamProject_Yu Market

목록 보기
3/5
post-thumbnail

1. 코드 리뷰를 통해 무엇을 공부하고 배웠는지 같이 알아보겠습니다.


박카스팀 첫번째 코드 리뷰를 발표를 진행한 치타개발자 팀원의 velog 입니다.

https://velog.io/@18k7102dy/team-projectcode-review1


Koin (Dependency Injection) 분석하기


1. 코드 분석하기

  • 1. YUMarketApplication.kt

Application 클래스를 상속받는 YUMarketApplication 클래스를 생성합니다. onCreate() 에서 startKoin { ... } 을 호출하고 모듈을 값을 넣어줬습니다.

class YUMarketApplication : Application() {

    override fun onCreate() {
        super.onCreate()
        appContext = this

        startKoin {
            androidLogger(Level.ERROR)
            androidContext(this@YUMarketApplication)
            modules(appModule)
        }
    }
}
  • 2. AndroidManifest.xml

AndroidManifest.xml 에 android:name=".YUMarketApplication" 을 통해 만들어준 Application 클래스를 등록해야 합니다. 꼭 등록해야합니다!

  • 3. appModule.kt
val appModule = module {

    single { Dispatchers.IO }
    single { Dispatchers.Main }
    
    single<MapRepository> { DefaultMapRepository(get(), get()) }
    single<HomeRepository> { DefaultHomeRepository() }
    
    single<ResourcesProvider> { DefaultResourcesProvider(androidContext()) }
    
    single { buildOkHttpClient() }
    single { provideGsonConverterFactory() }
    single(named("map")) { provideMapRetrofit(get(), get()) }
    single { provideMapApiService(get(qualifier = named("map"))) }
    
    single { buildOkHttpClient() }
	single { provideGsonConverterFactory() }
	single(named("map")) { provideMapRetrofit(get(), get()) }
	single { provideMapApiService(get(qualifier = named("map"))) 

	viewModel { (homeListCategory: HomeListCategory) -> HomeListViewModel(homeListCategory, get()) }
	viewModel { HomeMainViewModel(get()) }
	viewModel { MainViewModel(get()) }

}

module : 주입받고자 하는 객체를 선언하고 변수에 저장을 해줍니다.

val appModule = module {}

get() : 컴포넌트 내에서 이미 생성된 의존성을 주입 받을 수 있습니다. 주입하는 반환 클래스가 한개일 경우 자동으로 매칭하여 반환해 줍니다.

예시)

single { provideMapApiService(get(qualifier = named("map"))) }

fun provideMapApiService(retrofit: Retrofit): MapApiService {
    return retrofit.create(MapApiService::class.java)
}

MapApiService로 반환 하는경우는 provideMapApiService 밖에 없으므로 get()을 하게되면 알아서 
DefaultMapRepository의 MapApiService에 싱클톤으로 만들어진 provideMapApiService 객체가 
자동으로 의존성 주입이 되게 됩니다.

single<MapRepository> { DefaultMapRepository(get(), get()) }

class DefaultMapRepository(
    private val mapApiService: MapApiService,
    private val ioDispatcher: CoroutineDispatcher
) {}

single : 앱이 실행되는 동안 계속 유지되는 싱글톤 객체를 생성합니다.

//Coroutine Dispatcher - Main, IO

single { Dispatchers.IO }
single { Dispatchers.Main }

//Repository

single<MapRepository> { DefaultMapRepository(get(), get()) }
single<HomeRepository> { DefaultHomeRepository() }

//CustomProvider

single<ResourcesProvider> { DefaultResourcesProvider(androidContext()) }
    
//Network

single { buildOkHttpClient() }
single { provideGsonConverterFactory() }
single(named("map")) { provideMapRetrofit(get(), get()) }
single { provideMapApiService(get(qualifier = named("map"))) }
    

factory : 주입될 대상 모듈의 인스턴스를 호출 하는 시점마다 생성하여 주입 합니다.

viewModel : 요청시 매번 새로운 객체를 생성하는 것은 factory와 동일하나 AAC viewModel 용으로 사용하고 있다고 Koin에 명시하는 역할로 추측됩니다. 자세한 내용은 아래에서 추가로 다루겠습니다.

2. Koin과 viewModel 분석하기

Koin Module에 viewModel혹은 factory를 선언한 다음에 by viewModel()를 사용하면 ViewModelProvider를 사용하지 않고 ViewModel을 생성할 수 있습니다.

( ViewModelProvider에 대해서는 따로 하나의 주제로 글을 작성하겠습니다. )

  • 1. Koin viewmodel component
viewModel 혹은 factory를 위와 같이 Koin Module에 선언을 해줍니다.
: ViewModel()를 상속 받은 경우에만 viewModel을 Koin Module에 선언이 가능합니다.

val appModule = module {

viewModel { (homeListCategory: HomeListCategory) -> HomeListViewModel(homeListCategory, get()) }
viewModel { HomeMainViewModel(get()) }
viewModel { MainViewModel(get()) }

factory { (homeListCategory: HomeListCategory) -> HomeListViewModel(homeListCategory, get()) }
factory { HomeMainViewModel(get()) }
factory { MainViewModel(get()) }

}

Koin에서 module {}에 선언된 viewModel() 함수를 살펴보면 생성될 ViewModel의 인스턴스를 아래 함수 내용을 보면 factory()를 통해 관리 하게됩니다. factory()로 주입될 대상 모듈의 인스턴스를 호출 하는 시점마다 생성하여 주입 합니다. 그래서 factory()나 viewModel() 둘 중 하나로 선언해도 결과론 적으로는 차이가 없습니다.

inline fun <reified T : ViewModel> Module.viewModel(
    qualifier: Qualifier? = null,
    override: Boolean = false,
    noinline definition: Definition<T>
): BeanDefinition<T> {
    val beanDefinition = factory(qualifier, override, definition)
    beanDefinition.setIsViewModel()
    return beanDefinition
}
  • 2. viewModel Dependency Injection

by viewModel() : by를 통해 viewModel() 함수에 초기화를 위임 하여 ViewModel 인스턴스를 주입 받습니다.

getViewModel() : ViewModel 인스턴스를 직접 가져옵니다.

예시)

class SomeFragment: Fragment() {
  val viewModel1: firstViewModel by viewModel()
  val viewModel2: secondViewModel = getViewModel()
  ...
}

by viewModel() 과 getViewModel() 의 차이점은 lazy 위임 여부 입니다. 아래 코드를 통해 확인할 수 있습니다.

< viewModel >

inline fun <reified T : ViewModel> ViewModelStoreOwner.viewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): Lazy<T> {
    return lazy(LazyThreadSafetyMode.NONE) { getViewModel<T>(qualifier, parameters) }
}

< getViewModel >

inline fun <reified T : ViewModel> ViewModelStoreOwner.getViewModel(
    qualifier: Qualifier? = null,
    noinline parameters: ParametersDefinition? = null
): T {
    return getViewModel(T::class, qualifier, parameters)
}
  • 3. Koin & ViewModel with injection params

Koin은 ViewModel 의 주입 이전에 인스턴스 생성시 생성자에 변수를 선언하는 작업을 할 수 있습니다. 추후에 viewModel을 의존성 주입을 할 때 parametersOf() 통해 파라미터를 전달 할 수 있습니다.

val appModule = module {

viewModel { (homeListCategory: HomeListCategory) -> HomeListViewModel(homeListCategory, get()) }

}

override val viewModel by viewModel<HomeListViewModel> {
    parametersOf(homeListCategory)
}
  • 4. shareViewModel Dependency Injection
    (아직 YUmarket 프로젝트에 미적용)

주소 하나로 데이터를 공유하면서 공통으로 쓰일 ViewModel 인스턴스는 액티비티에 있는 여러개의 프래그먼트 에서 같이 주입되어 사용 할 수 있습니다.

class FirstFragment: Fragment() {
  val viewModel: CommonViewModel by shareViewModel()
}

class SecondFragment: Fragment() {
  val viewModel: CommonViewModel by shareViewModel()
}
  • 5. ViewModel with SavedStateHandle
    (아직 YUmarket 프로젝트에 미적용 내용 추가는 추후에 사용시 할 것)

3. Outro

이로써 Koin을 어떻게 쓰는건지 그리고 왜 쓰는건지 알고 활용할 수 있게 되었습니다. 특히 viewModel의 활용에 대해서 좀 더 심도깊은 이해를 할 수 있었습니다.

Koin에 대한 심층적인 분석을 이로써 마치겠습니다. 추후에 Hilt랑 비교하여 Koin은 다시 다뤄볼 예정입니다.

이것으로 첫 번째 발표자 치타개발자님과의 공부 내용을 끝마치고 다음 내용은 두 번째 발표자 errored_pasta님의 발표 내용을 바탕으로 작성을 이어서 하겠습니다.

다음 내용은 property에 대한것으로 backing property로 데이터를 어떻게 관리했는지 입니다.


4. reference :

https://charlezz.medium.com/viewmodel%EC%9D%B4%EB%9E%80-%EB%AC%B4%EC%97%87%EC%9D%B8%EA%B0%80-viewmodel-%EC%B4%88%EB%B3%B4%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%80%EC%9D%B4%EB%93%9C-e1be5dc1ac18

https://thdev.tech/androiddev/2020/07/13/Android-Fragment-ViewModel-Example/

profile
가장 아름다운 정답은 서로의 협업안에 있다.

0개의 댓글