들어가기 전에 아래의 사전지식이 없으면 이해에 문제가 있을 수 있습니다.
선수 지식
1. 객체 지향 프로그래밍
2. 개념-의존성-주입-DI-Dependency-Injection
3. 안드로이드 AAC ViewModel이란?
Repository Pattern 은 데이터 레이어를 앱의 나머지 부분에서 분리하는 디자인 패턴입니다. 데이터 레이어는 UI와는 별도로 앱의 데이터와 비즈니스 로직을 처리하는 앱 레이어 부분 이며, 다른 레이어에서 데이터 레이어에서 제공하는 API를 통해서 이 데이터에 액세스 할 수 있습니다. UI가 사용자에게 정보를 제공하는 동안 데이터 레이어에는 네트워킹 코드, Room 데이터베이스, 데이터 관련 오류 및 예외 처리, 데이터를 읽거나 조작하는 코드 등이 포함됩니다.
아래 그림은 공식문서의 그림으로 Activity와 Fragment와 같은 앱 구성요소가 ViewModel로 Repositories를 통해 데이터 소스에 접근하는 계층을 보여줍니다.
안드로이드 공식문서의 위 그림을 보면, 데이터가 있는 여러 저장소(Local, Remote)를 필요한 공통 특성들을 추상화하여 중앙 집중처리 방식을 구성합니다. 즉, Data의 출처에 관계 없이 동일한 인터페이스로 데이터에 접근할 수 있도록 하는 패턴입니다. 그래서 Repositories는 데이터 소스(ex: 로컬 데이터, 웹 서비스 API로 부터 데이터, 캐시 데이터) 간의 충돌을 해결하고 이 데이터의 변경사항을 중앙 집중화할 수 있습니다. 그리고 DataSource들 Local 이면 (Room, SQlite), Remote면 서버와 api 통신하는 특정한 목적들을 알 수 없이 우리는 필요한 데이터만 요청하게 되므로, 캡슐화 되게 됩니다.
Repository 모듈은 데이터 작업을 처리하고 여러 백엔드 API 사용을 한곳에서 가능하게 합니다. 일반적인 앱에서 저장소[Repository 가 아니라도 data 계층에 접근하는 것들]는 네트워크에서 데이터를 가져올지 아니면 로컬 데이터베이스에 캐시된 결과를 사용할지 결정하는 로직을 구현합니다. MVVM의 경우 뷰 모델이 위와 같은 역할을 하게 됩니다.
- MVVM 에서 Repository 와 DI 라이브러리를 같이 사용하게 된다면 뷰 모델에서 Data 관련한 구현 세부정보를 교체할 수 있습니다. 이는 코드를 모듈식으로, 테스트를 가능하게 만들 수 있습니다. 쉽게 Repository에 Test 코드를 작성해서 Data를 활용하는 코드의 나머지 부분을 테스트할 수 있습니다.
- Repository는 앱 데이터의 특정 부분에 관한 단일 정보 소스 역할을 해야 합니다. 네트워크 리소스와 오프라인 캐시 등 여러 데이터 소스로 작업할 때 Repository 는 앱이 오프라인 상태일 때도 받아놓은 데이터를 사용을 할 수 있습니다. 캐싱의 경우 Room 라이브러리를 통해서 저장했다가 꺼내서 쓰기도 합니다.
개발 해서 product에 적용했던 코드를 바탕으로 예제를 작성하겠습니다.
1. RestaurantRepository interface와 구현체인 RestaurantRepositoryImpl를 만들기.
interface RestaurantRepository
@Singleton
class RestaurantRepositoryImpl @Inject constructor(
private val restaurantDataSource : RestaurantDataSource,
private val mapDataSource: MapDataSource
) : RestaurantRepository {
여기서 RestaurantDataSource 와 MapDataSource 가 DI 되고 있는 것을 확인 할 수 있습니다. DI가 잘 될 수 있게 hilt 적용을 어떻게 하는지 알아보겠습니다.
2. hilt 적용하기
@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryBindModule {
@Binds
abstract fun bindFoodApiRepository(
restaurantRepositoryImpl: RestaurantRepositoryImpl
): RestaurantRepository
}
- 1. 의존성 주입 구성
@Module 어노테이션을 사용하여 Hilt 모듈을 만들고, @InstallIn 어노테이션을 사용하여 해당 모듈을 구성할 구성 요소를 지정합니다. 이렇게 모듈을 구성하면 의존성 주입을 구성할 수 있습니다.
- 2. 앱 전역에서 사용 가능한 의존성 주입
SingletonComponent::class는 앱의 전역 컴포넌트 중 하나입니다. 이렇게 구성하면 모듈이 앱 전반에 걸쳐 사용 가능합니다. 모듈이 SingletonComponent::class에 구성되어 있으면 앱에서 필요로 하는 어느 곳에서나 해당 모듈의 의존성 주입을 사용할 수 있습니다.
위의 코드 처럼 추상클래스로 모듈을 만들어주고, 생성자에 RestaurantRepositoryImpl를 파라미터로 받고 RestaurantRepository로 반환을 해줍니다. 이렇게 hilt 모듈을 설정하게 되면 @Inject 를 해서 DI를 한 DataSource 클래스가 뷰모델 인스턴스 생성시 생성자 주입이 되게 됩니다.
3. ViewModel에 Repository 를 DI 하기.
@HiltViewModel
class MapViewModel @Inject constructor(
private val repository: RestaurantRepository
) : ViewModel() {
RestaurantRepositoryImpl 이 @Binds가 되어서, MapViewModel에 제공되게 됩니다.
4. 위와 같은 ViewModel 을 쓰는 View에서 Instance 생성하기.
@AndroidEntryPoint
class MapFragment : BaseFragment<FragmentMapBinding>() {
private val viewModel: MapViewModel by viewModels()
}
MapViewModel 인스턴스는 viewModel 호출이 되면 그 때 인스턴스가 초기화 되게 됩니다. 초기화 된 뷰모델 인스턴스는 DI 작업이 이루어진 레포지토리가 주입된 상태로 사용할 수 있게 됩니다.
추후에 관련 코드와 함께 작성 예정 입니다.
https://developer.android.com/codelabs/basic-android-kotlin-training-repository-pattern?hl=ko#3
https://developer.android.com/jetpack/guide/data-layer?hl=ko