Datastore   Android Jetpack의 구성요소.

Jetpack Datastore는 프로토콜 버퍼를 사용하여 키-값 쌍 또는 유형이 지정된 객체를 저장할 수 있는 데이터 저장소 솔루션입니다. Datastore는 Kotlin 코루틴 및 Flow를 사용하여 비동기적이고 일관된 트랜잭션 방식으로 데이터를 저장합니다.

현재 SharedPreferences를 사용하여 데이터를 저장하고 있다면 대신 Datastore로 이전하는 것이 좋습니다.

Preferences DataStore 및 Proto DataStore

DataStore는 Preferences DataStore와 Proto DataStore라는 두 가지 구현을 제공합니다.

  • Preferences DataStore는 키를 사용하여 데이터를 저장하고 데이터에 액세스합니다. 이 구현은 유형 안전성을 제공하지 않으며 사전 정의된 스키마가 필요하지 않습니다.
  • Proto Datastore는 맞춤 데이터 유형의 인스턴스로 데이터를 저장합니다. 이 구현은 유형 안전성을 제공하며 프로토콜 버퍼를 사용하여 스키마를 정의해야 합니다.

설정

앱에서 Jetpack Datastore를 사용하려면 사용할 구현에 따라 다음을 Gradle 파일에 추가하세요.

유형

// Typed DataStore (Typed API surface, such as Proto)
dependencies {
  implementation "androidx.datastore:datastore:1.0.0-beta01"

  // optional - RxJava2 support
  implementation "androidx.datastore:datastore-rxjava2:1.0.0-beta01"

  // optional - RxJava3 support
  implementation "androidx.datastore:datastore-rxjava3:1.0.0-beta01"
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
  implementation "androidx.datastore:datastore-core:1.0.0-beta01"
}

환경설정

// Preferences DataStore (SharedPreferences like APIs)
dependencies {
  implementation "androidx.datastore:datastore-preferences:1.0.0-beta01"

  // optional - RxJava2 support
  implementation "androidx.datastore:datastore-preferences-rxjava2:1.0.0-beta01"

  // optional - RxJava3 support
  implementation "androidx.datastore:datastore-preferences-rxjava3:1.0.0-beta01"
}
// Alternatively - use the following artifact without an Android dependency.
dependencies {
  implementation "androidx.datastore:datastore-preferences-core:1.0.0-beta01"
}

Preferences Datastore로 키-값 쌍 저장

Preferences Datastore 구현은 DataStore 클래스와 Preferences 클래스를 사용하여 간단한 키-값 쌍을 디스크에 유지합니다.

Preferences Datastore 만들기

preferencesDataStore로 만든 속성 위임을 사용하여 Datastore<Preferences>의 인스턴스를 만듭니다. kotlin 파일의 최상위 수준에서 인스턴트를 한 번 호출한 후 애플리케이션의 나머지 부분에서는 이 속성을 통해 인스턴트에 액세스합니다. 이렇게 하면 더 간편하게 DataStore를 싱글톤으로 유지할 수 있습니다. 또는 RxJava를 사용하는 경우 RxPreferenceDataStoreBuilder를 사용합니다. 필수 name 매개변수는 Preferences Datastore의 이름입니다.

Kotlin

// At the top level of your kotlin file:
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")

자바

RxDataStore<Preferences> dataStore =
  new RxPreferenceDataStoreBuilder(context, /*name=*/ "settings").build();

Preferences Datastore에서 읽기

Preferences Datastore는 사전 정의된 스키마를 사용하지 않으므로 DataStore<Preferences> 인스턴스에 저장해야 하는 각 값의 키를 정의하려면 상응하는 키 유형 함수를 사용해야 합니다. 예를 들어 int 값의 키를 정의하려면 intPreferencesKey()를 사용합니다. 그런 다음 DataStore.data 속성을 사용하여 Flow를 사용한 적절한 저장 값을 노출합니다.

Kotlin

val EXAMPLE_COUNTER = intPreferencesKey("example_counter")
val exampleCounterFlow: Flow<Int> = context.dataStore.data
  .map { preferences ->
    // No type safety.
    preferences[EXAMPLE_COUNTER] ?: 0
}

자바

Preferences.Key<Integer> EXAMPLE_COUNTER = PreferencesKeys.int("example_counter");

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(prefs -> prefs.get(EXAMPLE_COUNTER));

Preferences Datastore에 쓰기

Preferences Datastore는 DataStore의 데이터를 트랜잭션 방식으로 업데이트하는 edit() 함수를 제공합니다. 함수의 transform 매개변수는 필요에 따라 값을 업데이트할 수 있는 코드 블록을 허용합니다. 변환 블록의 모든 코드는 단일 트랜잭션으로 취급됩니다.

Kotlin

suspend fun incrementCounter() {
  context.dataStore.edit { settings ->
    val currentCounterValue = settings[EXAMPLE_COUNTER] ?: 0
    settings[EXAMPLE_COUNTER] = currentCounterValue + 1
  }
}

자바

Single<Preferences> updateResult =  dataStore.updateDataAsync(prefsIn -> {
  MutablePreferences mutablePreferences = prefsIn.toMutablePreferences();
  Integer currentInt = prefsIn.get(INTEGER_KEY);
  mutablePreferences.set(INTEGER_KEY, currentInt != null ? currentInt + 1 : 1);
  return Single.just(mutablePreferences);
});
// The update is completed once updateResult is completed.

유형이 지정된 객체를 Proto Datastore로 저장

Proto Datastore 구현은 Datastore 및 프로토콜 버퍼를 사용하여 유형이 지정된 객체를 디스크에 유지합니다.

스키마 정의

Proto Datastore를 사용하려면 app/src/main/proto/ 디렉터리의 proto 파일에 사전 정의된 스키마가 있어야 합니다. 사전 정의된 스키마는 Proto Datastore에 유지하는 객체의 유형을 정의합니다. proto 스키마를 정의하는 방법에 관한 자세한 내용은 protobuf 언어 가이드를 참고하세요.

syntax = "proto3";

option java_package = "com.example.application";
option java_multiple_files = true;

message Settings {
  int32 example_counter = 1;
}

Proto Datastore 만들기

유형이 지정된 객체를 저장할 Proto DataStore를 만드는 작업은 두 단계로 이루어집니다.

  1. Serializer<T>를 구현하는 클래스를 정의합니다. 여기서 T는 proto 파일에 정의된 유형입니다. serializer 클래스는 데이터 유형을 읽고 쓰는 방법을 Datastore에 알립니다. 아직 파일이 생성되지 않은 경우 사용할 serializer의 기본값을 포함해야 합니다.
  2. dataStore로 만든 속성 위임을 사용하여 DataStore<T>의 인스턴스를 만듭니다. 여기서 T는 proto 파일에 정의된 유형입니다. kotlin 파일의 최상위 수준에서 인스턴트를 한 번 호출한 후 애플리케이션의 나머지 부분에서는 이 속성을 통해 인스턴트에 액세스합니다. filename 매개변수는 데이터를 저장하는 데 사용할 파일을 Datastore에 알리고 serializer 매개변수는 1단계에서 정의한 serializer 클래스 이름을 Datastore에 알립니다.

Kotlin

object SettingsSerializer : Serializer<Settings> {
  override val defaultValue: Settings = Settings.getDefaultInstance()

  override suspend fun readFrom(input: InputStream): Settings {
    try {
      return Settings.parseFrom(input)
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException("Cannot read proto.", exception)
    }
  }

  override suspend fun writeTo(
    t: Settings,
    output: OutputStream) = t.writeTo(output)
}

val Context.settingsDataStore: DataStore<Settings> by dataStore(
  fileName = "settings.pb",
  serializer = SettingsSerializer
)

자바

private static class SettingsSerializer implements Serializer<Settings> {
  @Override
  public Settings getDefaultValue() {
    Settings.getDefaultInstance();
  }

  @Override
  public Settings readFrom(@NotNull InputStream input) {
    try {
      return Settings.parseFrom(input);
    } catch (exception: InvalidProtocolBufferException) {
      throw CorruptionException(“Cannot read proto.”, exception);
    }
  }

  @Override
  public void writeTo(Settings t, @NotNull OutputStream output) {
    t.writeTo(output);
  }
}

RxDataStore<Byte> dataStore =
    new RxDataStoreBuilder<Byte>(context, /* fileName= */ "settings.pb", new SettingsSerializer()).build();

Proto Datastore에서 읽기

DataStore.data를 사용하여 저장된 객체에서 적절한 속성의 Flow를 노출합니다.

Kotlin

val exampleCounterFlow: Flow<Int> = context.settingsDataStore.data
  .map { settings ->
    // The exampleCounter property is generated from the proto schema.
    settings.exampleCounter
  }

자바

Flowable<Integer> exampleCounterFlow =
  dataStore.data().map(settings -> settings.getExampleCounter());

Proto Datastore에 쓰기

Proto Datastore는 저장된 객체를 트랜잭션 방식으로 업데이트하는 updateData() 함수를 제공합니다. updateData()는 데이터의 현재 상태를 데이터 유형의 인스턴스로 제공하고 원자적 읽기-쓰기-수정 작업을 통해 트랜잭션 방식으로 데이터를 업데이트합니다.

Kotlin

suspend fun incrementCounter() {
  context.settingsDataStore.updateData { currentSettings ->
    currentSettings.toBuilder()
      .setExampleCounter(currentSettings.exampleCounter + 1)
      .build()
    }
}

자바

Single<Settings> updateResult =
  dataStore.updateDataAsync(currentSettings ->
    Single.just(
      currentSettings.toBuilder()
        .setExampleCounter(currentSettings.getExampleCounter() + 1)
        .build()));

동기 코드에서 Datastore 사용

DataStore의 주요 이점 중 하나는 비동기 API이지만 주변 코드를 비동기로 변경하는 것이 항상 가능하지는 않을 수도 있습니다. 동기 디스크 I/O를 사용하는 기존 코드베이스로 작업하거나 비동기 API를 제공하지 않는 종속 항목이 있다면 이러한 상황이 발생할 수 있습니다.

Kotlin 코루틴은 runBlocking() 코루틴 빌더를 제공하여 동기 코드와 비동기 코드 간의 격차를 해소합니다. runBlocking()을 사용하여 Datastore에서 데이터를 동기식으로 읽을 수 있습니다. RxJava는 Flowable에서 차단 메서드를 제공합니다. 다음 코드는 Datastore가 데이터를 반환할 때까지 호출 스레드를 차단합니다.

Kotlin

val exampleData = runBlocking { context.dataStore.data.first() }

자바

Settings settings = dataStore.data().blockingFirst();

UI 스레드에서 동기 I/O 작업을 실행하면 ANR 또는 UI 버벅거림이 발생할 수 있습니다. Datastore에서 데이터를 비동기식으로 미리 로드하여 이 문제를 완화하세요.

Kotlin

override fun onCreate(savedInstanceState: Bundle?) {
    lifecycleScope.launch {
        context.dataStore.data.first()
        // You should also handle IOExceptions here.
    }
}

자바

dataStore.data().first().subscribe();

이렇게 하면 Datastore가 비동기식으로 데이터를 읽고 메모리에 캐시합니다. 초기 읽기가 완료되면 이후 runBlocking()을 사용한 동기 읽기가 더 빠를 수도 있고 디스크 I/O 작업을 완전히 방지할 수도 있습니다.

의견 보내기

다음 리소스를 통해 의견을 보내고 아이디어를 공유해 주세요.

Issue Tracker
버그를 수정할 수 있도록 문제를 신고해 주세요.

추가 리소스

Jetpack DataStore에 관한 자세한 내용은 다음 추가 리소스를 참고하세요.

블로그

Codelab