개발일지

Android in A..Z - DataStore 본문

Android (안드로이드)

Android in A..Z - DataStore

강태종 2021. 3. 30. 14:58

DataStore

Jetpack DataStore는 SharedPreferences를 대체하기 위해 나왔다.

DataStore는 Key-Value뿐만 아니라, Protocol Buffers를 이용하여 Object를 저장할 수 있고, 동기/비동기를 지원하며 IO에 대한 Exception도 제공하기 때문에 유연하게 코드를 작성할 수 있다.

* Android Developer에서 복잡한 구조의 데이터는 Room을 사용해서 저장하는 것을 권장한다.


SharedPreferences 단점

Key-Value 형태로 XML파일에 값을 저장하고 String 또는 Primitives 값만 저장할 수 있다.

=> 이를 해결하고자 JSON이나 XML형식의 String으로 저장하지만 Type Safety를 보장하지 않기 때문에 파싱 과정에서 Exception이 발생할 수 있다.

 

get() 함수로 동기식으로 데이터를 읽지만 UI Thread를 막기 때문에 ANR을 발생 시킬 수 있다.

 

apply() 함수로 비동기식으로 데이터를 저장하지만 성공/실패 여부를 확인할 수 없다.

 

commit() 함수로 동기식으로 데이터를 저장하지만 UI Thread를 막기 때문에 ANR을 발생 시킬 수 있다.


Dependencies

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

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

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

Preferences DataStore

SharedPreferences처럼 Key-Value구조로 Data를 저장하는 방법이다.

 

DataStore 생성

최상위 파일에서 선언을 하여 간편하게 접근할 수 있고, 싱글톤 패턴을 유지할수 있습니다.

val Context.loginStore: DataStore<Preferences> by preferencesDataStore(name = "login")

 

Preferences DataStore 값 읽기/쓰기

stringPreferencesKey, intPreferencesKey 등으로 Key를 만든다.

dataStore.data로 값을 읽는다. (Flow형으로 반환된다.)

dataStore.edit으로 값을 저장한다. (edit이 suspend함수이기 때문에 Coroutine에서 실행해야 한다.)

@Singleton
class LoginRepository @Inject constructor() {
    private val idKey by lazy { stringPreferencesKey("id") }
    private val passwordKey by lazy { stringPreferencesKey("password") }

    @Inject
    @LoginDataStore
    lateinit var dataStore: DataStore<Preferences>

    var id: String
        get() {
            return runBlocking(Dispatchers.IO) {
                dataStore.data.map {
                    it[idKey] ?: ""
                }.first()
            }
        }
        set(value) {
            runBlocking(Dispatchers.IO) {
                dataStore.edit {
                    it[idKey] = value
                }
            }
        }

    var password: String
        get() {
            return runBlocking(Dispatchers.IO) {
                dataStore.data.map {
                    it[passwordKey] ?: ""
                }.first()
            }
        }
        set(value) {
            runBlocking(Dispatchers.IO) {
                dataStore.edit {
                    it[passwordKey] = value
                }
            }
        }
}

Proto DataStore

Protocol Buffers를 이용하여 Object를 저장할 수 있다.

(Protocol Buffers : Data를 직렬화하는 구글의 메커니즘이다. 언어, 플랫폼에 독립적이고 확장이 쉬운 장점이있다.)

 

Dependency

plugins {
    id 'com.google.protobuf' version "0.8.12"
}

dependencies {
    implementation  "com.google.protobuf:protobuf-javalite:3.11.0"
}

protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:3.11.0"
    }

    generateProtoTasks {
        all().each { task ->
            task.builtins {
                java {
                    option 'lite'
                }
            }
        }
    }
}

 

Protocol Buffers 정의 (정의방법)

프로젝트 app/src/main/proto 에 *.proto파일로 정의해서 저장하고 Rebuild한다.

syntax = "proto3";

option java_package = "com.taetae98.datastore";
option java_multiple_files = true;

message Account {
  string department = 1;
  string studentId = 2;
  string name = 3;
  string phone = 4;
}

 

Serializer 정의

Protocol Buffers를 정의하면 자동으로 객체와 함수들이 생긴다.

object AccountSerializer : Serializer<Account> {
    override val defaultValue: Account
        get() = Account.getDefaultInstance()

    override suspend fun readFrom(input: InputStream): Account {
        return try {
            Account.parseFrom(input)
        } catch (e: Exception) {
            throw CorruptionException("Cannot read proto.", e)
        }
    }

    override suspend fun writeTo(t: Account, output: OutputStream) {
        t.writeTo(output)
    }
}

 

DataStore 생성

val Context.accountStore: DataStore<Account> by dataStore(
    fileName = "user_prefs.pb",
    serializer = AccountSerializer
)

 

DataStore 값 읽기/쓰기

Preferences DataStore처럼 DataStore.data를 통해 값을 읽을 수 있고, Flow형으로 반환된다.

updateData로 값을 쓸 수 있고, suspend 함수이기 때문에 Coroutine을 활용해야 한다.

@Singleton
class AccountRepository @Inject constructor() {
    @Inject
    lateinit var accountDataStore: DataStore<Account>

    val studentId: Flow<String>
        get() {
            return accountDataStore.data.map { it.studentId }
        }

    suspend fun set(information: Information) {
        accountDataStore.updateData {
            it.toBuilder()
                .setDepartment(information.department)
                .setName(information.name)
                .setPhone(information.phone)
                .setStudentId(information.studentId)
                .build()
        }
    }
}

Git (예제코드)

github.com/KangTaeJong98/Example/tree/main/Android/DataStore

 

KangTaeJong98/Example

My Example Code. Contribute to KangTaeJong98/Example development by creating an account on GitHub.

github.com

 

'Android (안드로이드)' 카테고리의 다른 글

Android in A..Z - OnTouchListener, GestureEvent  (0) 2021.07.18
Android in A..Z - ActivityResultContract  (0) 2021.05.23
Android in A..Z - Dialog  (0) 2021.03.22
Android in A..Z - QR Code  (0) 2021.03.16
Android in A..Z - Location  (0) 2021.02.06
Comments