갤럭시 워치에 버스 도착 정보를 띄우자!(2) - Repository pattern

김흰돌·2023년 5월 9일
0
post-thumbnail

Repository pattern이란?

안드로이드에서 Repository pattern은 앱에서 데이터 소스를 추상화하여, 앱과 데이터 소스 간의 결합도를 낮추고, 코드의 유연성과 재사용성을 높이기 위한 디자인 패턴이다.

이번에는 ViewModel에서 Repository를 사용하여 데이터를 가져와 뷰와 바인딩을 해주는 과정을 작성해보려고 한다. 이 과정을 통해 ViewModel은 액티비티나 프래그먼트와 같은 UI 컴포넌트와 직접적인 결합을 피할 수 있다.




데이터 모델을 분리하자

이번 프로젝트에선 Room을 이용해 검색 기록을 저장하고, Retrofit을 이용해 카카오맵, 버스 정보 관련 API 통신을 한다. 또, Firebase를 이용해 즐겨찾기한 버스 정보를 저장한다.

Repository pattern에서 보통 Repository 인터페이스와 그 구현체를 나누어 작성한다. 인터페이스는 데이터 소스에 대한 추상화를 담당하고, 구현체는 실제 데이터베이스와의 상호작용을 담당한다.


검색어를 저장하고, 불러오고, 삭제하는 Room Repository를 만들어보면

interface RoomRepository {
    suspend fun getAllHistory(): Flow<List<SearchHistoryEntity>>
    
    suspend fun insertHistory(searchHistoryEntity: SearchHistoryEntity)
    
    suspend fun deleteHistory(searchHistoryEntity: SearchHistoryEntity)

    suspend fun deleteAllHistory()
}

class RoomRepositoryImpl @Inject constructor(
    private val searchHistoryDao: SearchHistoryDao,
    @IoDispatcher private val ioDispatcher: CoroutineDispatcher
): RoomRepository {
    override fun getAllHistory(): Flow<List<SearchHistoryEntity>> = searchHistoryDao.getAllItem()

    override suspend fun insertHistory(searchHistoryEntity: SearchHistoryEntity) = withContext(ioDispatcher) {
        searchHistoryDao.insertItem(searchHistoryEntity)
    }

    override suspend fun deleteHistory(searchHistoryEntity: SearchHistoryEntity) = withContext(ioDispatcher) {
        searchHistoryDao.deleteItem(searchHistoryEntity)
    }

    override suspend fun deleteAllHistory() {
        searchHistoryDao.deleteAllItem()
    }
}

처럼 만들 수 있다(Hilt와 코루틴 관련 글은 포스팅 예정).


또한 카카오맵에서 키워드를 통한 시설 검색을 제공하는 API는 이렇게 작성할 수 있다.

interface KakaoMapRepository {

    suspend fun searchLocation(address: String, latitude: Double, longitude: Double) : KakaoResponse
}

class KakaoMapRepositoryImpl @Inject constructor(
    private val kakaoService: KakaoService
) : KakaoMapRepository {
    override suspend fun searchLocation(
        address: String,
        latitude: Double,
        longitude: Double
    ): KakaoResponse {
        return kakaoService.searchPlace(
                address,
                5000,
                latitude,
                longitude,
                "KakaoAK 카카오 API 키"
            ).await()
    }
}

이렇게 작성할 수 있다.

작성한 Repository 클래스를 ViewModel에서 사용하기만 하면 된다.

@HiltViewModel
class MapViewModel @Inject constructor(
    private val kakaoMapRepository: KakaoMapRepositoryImpl,
    private val roomRepository: RoomRepositoryImpl
) : ViewModel() {

    private val _searchResult = MutableLiveData<KakaoResponse>()
    val searchResult: LiveData<KakaoResponse>
        get() = _searchResult

    fun searchLocation(address: String, latitude: Double, longitude: Double) {
        viewModelScope.launch {
            _searchResult.value = kakaoMapRepository.searchLocation(address, latitude, longitude)
        }
    }

    fun insertHistory(searchHistory: String) {
        viewModelScope.launch {
            roomRepository.insertHistory(
                SearchHistoryEntity(
                    null,
                    searchHistory
                )
            )
        }
    }
}

Repository를 주입하고 만들어 뒀던 함수를 사용하면 된다.

  • ViewModle은 데이터를 가져오는 것에만 집중할 수 있고, 데이터 소스의 세부 구현에 대한 로직은 숨기므로, 코드의 가독성을 더 높일 수 있다.
  • Repository를 이용하여 데이터 소스를 변경하더라도 ViewModel의 코드를 수정하지 않아도 되는 장점이 생긴다.
  • 마지막으로 여러 데이터 소스를 쉽게 결합할 수 있으므로, 코드의 확장성 또한 높아진다.

하지만 Repository pattern을 적용하면, 데이터 소스와의 상호작용을 위한 불필요한 인터페이스 및 구현체가 추가된다. 코드의 복잡성이 증가할 수 있다.



전체 코드 보기
부족하거나 아쉬운 부분, 잘못 작성한 코드 등에 대한 피드백을 주신다면 정말 감사하겠습니다.

0개의 댓글