Proto Datastore와 Hilt 같이 사용하기 - Jetpack Compose

Shawn Kang·2023년 1월 12일
1

Jetpack Compose

목록 보기
2/5
post-thumbnail

개요

Hilt로 의존성 주입을 적용한 ViewModel에서 Proto Datastore(이하 Proto)를 클래스 생성자로 불러오려고 하니 많은 오류가 발생했다. 그 오류들을 일일히 기억하지도 못하고 기록도 안 했지만, 결과적으로는 돌아가는 코드를 작성했고 그걸 공유하고자 한다.

본 게시글은 블로그의 Jetpack Compose 시리즈에 올라온 코드를 재사용하기 때문에, 만약 이걸 읽는 누군가가 있다면 이전 글들을 같이 보는 것을 추천한다.

그리고 이건 개인적인 한탄이지만... 매번 구글링하고 StackOverflow 찾아보면서 문제를 어찌저찌 해결하기는 하면서도, 오류의 근본적인 원인과 Compose의 딥한 부분까지는 완전히 이해하지는 못하는 게 너무 아쉽다. 그래도 이렇게 무지성으로 부딪히면서 검색해보고 코드 짜보고 하면 내부 구조나 동작 방식에 대해 파편적으로나마 이해할 수 있게 되겠지, 하는 조금 긍정적인 생각은 있다.


To-do

Hilt + ViewModel + Proto의 트리오 앙상블을 위해서는 몇 가지 코드 수정이 조금 필요하다.

  • Context.dataStore의 선언 위치 변경
  • Repository 생성자 및 코드 일부 수정


구현

Context.dataStore의 선언 위치 변경

로컬 데이터 저장소에 해당하는 dataStore, 원래는 MainActivity.kt의 최상단에 선언해 주었었다. 그걸 그대로 Repository를 선언한 코드에 옮겨주면 된다.

나의 경우는 Proto를 쓸 때 디렉토리 구조를 보통 아래와 같이 하는데, 이 예시에서는 SampleRepository.kt로 옮겨주면 된다:

  • /data
    • /SampleRepository.kt (여기로)
    • /SampleSerializer.kt
  • /viewmodels
  • /views
  • /MainActivity.kt (여기에서)
package com.imeanttobe.compose.data

import ...

private val Context.dataStore: DataStore<Sample> by dataStore(
    fileName = "sample.pb",
    serializer = SampleSerializer
)

여기서 중요한 건 선언 위치는 무조건 코드 파일 최상단이어야 한다는 점이다. dataStore를 싱글톤으로 사용하기 위함이다. 처음에는 dataStore을 Repository 클래스 안에서만 쓰니까, 클래스 안에서 선언해줘도 되겠지, 하고 생각했다. 그러다가 "There are multiple DataStores active for the same file: (후략)"하는 예외 상황을 만나게 되었고 뒤늦게 싱글톤으로 쓰는 방법을 찾게 되었다.

이상의 이슈는 patrick-dev 님의 블로그를 참고해서 해결했다. 감사합니다 ~_~

Repository 생성자 및 코드 일부 수정

먼저 생성자다. Hilt가 의존 관계를 알 수 있도록 ViewModel에서의 경우와 같이 생성자에 @Inject constructor 어노테이션을 달아주어야 한다. 그리고 기존에 생성자로 넘겨주었던 dataStore는 이제 같은 파일의 최상단에 선언해 주었기 때문에 지워도 된다. 대신 dataStore를 사용하려면 android.content.Context가 필요하기 때문에 얘를 생성자에 넣어주어야 한다.

이상의 수정 사항을 반영한 생성자는 아래와 같다:

// Before
class SampleRepository (
	private val dataStore: DataStore<Sample>
)

// After
class SampleRepository @Inject constructor(
	@ApplicationContext private val context: Context
)

@ApplicationContext는 Hilt를 사용하면서 애플리케이션 또는 액티비티의 Context를 참고해야 할 경우에 사용하는 어노테이션이라고 한다. 이것만 뺀 상태로 앱을 돌려봤는데 실행이 안 되더라. 꼭 넣어주도록 하자.

다음으로는 Flow 타입을 통해 ViewModel에 데이터를 넘겨주는 val sampleData: Flow<Sample>을 조금 수정해야 한다. dataStore의 선언 위치가 바뀌었기 때문에 이에 따른 대응 차원의 수정이다.

// Before
class SampleRepository (
	private val dataStore: DataStore<Sample>
) {
	val flow: Flow<SampleData> = dataStore.data
    // 이하 생략
}

// After
class SampleRepository @Inject constructor(
	@ApplicationContext private val context: Context
) {
	val flow: Flow<SampleData> = context.dataStore.data
    // 이하 생략
}

여담으로 여기서 SampleSampleData, 두 자료형의 의미에 대해 궁금한 경우가 있을 수 있다. Sample은 .proto 파일에서 message에 정의해 준 이름이고, SampleDataSample을 가공해서 사용하기 위해 별도로 만든 클래스다. 날 것의 데이터를 가공한 후 담는데 쓰는 참치 캔... 정도로 이해하면 될 것 같다.


결론

에 뭐 휘황찬란하게 적을 건 딱히 없다. 잘 돌아간다.

조만간 SQLite를 쓰는 Room까지 적용해볼 것 같다. 이러다 완전히 안드로이드 개발자가 되어버리는 건 아닐지 모르겠다...

profile
i meant to be

0개의 댓글