본문 바로가기
Android Compose

Android Kotlin Compose Proto DataStore (kotlin dsl)

by 히예네 2024. 7. 7.
728x90
반응형

DataStore는 SharedPreference의 대체 class로 보안을 강화한 Jetpack class중 하나이다.

디바이스에 data를 처리해줄 수 있는 기능을 제공한다.

 

compose를 이용해서 proto datastore를 이용해 간단한 예제를 연습하려고한다. 

 

DataStore로 만들어 볼 예제

1. EditText에 text를 입력한다.

2. 버튼을 누른다.

3. 버튼을 누르면 디바이스에 해당 text가 저장된다. 

 

 

처음해보는 분들은 내 github를 참고하여 만들어봐도 좋을거같다! 

https://github.com/kof99athena/TPProtoData

 

GitHub - kof99athena/TPProtoData: 컴포즈를 활용하여 Proto DataStore 사용해보기

컴포즈를 활용하여 Proto DataStore 사용해보기. Contribute to kof99athena/TPProtoData development by creating an account on GitHub.

github.com

 

 

step1. gradle에 datastore 를 사용하기 위한 코드 추가

처음에 공식문서만 보고 하다가 적용이 안되서 스택오버플로우를 보니 protobuf에 관련된 플러그인도 추가해야한다는것을 알았다. 

난이도가 좀 있는 Jetpack을 사용하려면 빌드부터 꼼꼼하게 챙겨야하는거같다. 🤣

그리고 protobuf의 문법이 잘못되었는지, 앱빌드가 계속 안되서 스택오버플로우를 뒤져보아서 코드를 수정했다..

 

앱 수준의 gradle

import com.google.protobuf.gradle.*

plugins {
    alias(libs.plugins.android.application)
    alias(libs.plugins.jetbrains.kotlin.android)
    id("com.google.protobuf") version "0.9.4"
}

val protobufVersion = "3.21.7"


dependencies {
    implementation("com.google.protobuf:protobuf-javalite:$protobufVersion")
    implementation("androidx.datastore:datastore:1.0.0-alpha03")

    implementation("androidx.datastore:datastore:1.1.1")
    // optional - RxJava2 support
    implementation("androidx.datastore:datastore-rxjava2:1.1.1")
    // optional - RxJava3 support
    implementation("androidx.datastore:datastore-rxjava3:1.1.1")
    
    ....
    
  }
  
protobuf {
    protoc {
        artifact = "com.google.protobuf:protoc:$protobufVersion"
    }
    plugins {
        generateProtoTasks {
            all().forEach {
                it.builtins {
                    create("java") {
                        option("lite")
                    }
                }
            }
        }
    }
}

 

project 수준의 gradle

plugins {
    alias(libs.plugins.android.application) apply false
    alias(libs.plugins.jetbrains.kotlin.android) apply false
    id("com.google.protobuf") version "0.9.4" apply false
}

 

 

step2. 확장자가 .proto인 파일을 프로젝트에 만들기 

project 수준으로 변경한 후, app/main에 proto 패키지를 생성한다.

그 이후에 .proto 파일을 만들고, 공식문서에서 제공하는 스키마를 복붙하여 가져온다.

여기서 변경 해줘야할 부분이 있다!

 

option java_package: 나의 패키지명

message Settings: 이 Settings는 추후 내가 사용할 데이터와 관련된 자료형이된다! 내가 이름을 직접 지어줘야한다! 

그렇기때문에 Settings로 두지말고 이름을 바꿔보자. 나는 MyUserData라는 자료형을 만들어서 사용했다. 

브라켓안에는 내가 사용할 변수를 지정하는것이다. (숫자는 변수에 부여하는 고유한 숫자) 

syntax = "proto3";

option java_package = "내 패키지명"; 
option java_multiple_files = true;

message MyUserData {
  string name = 1;
}

이 과정까지한후 , 프로젝트가 내 MyUserData 자료형을 인식 할 수 있도록 리빌드한다

이제 MyUserData라는 class가 생긴다. 

 

step 3. 본격 Proto Datastore 만들기

우선 Serializer해줄 Object를 만든다. 

Context를 갖고있는 class 에서는 (예를들면 Activity), settingsUserData라는 변수로 해당 데이터를 불러올수있다! 

object UserDataSerializer : Serializer<MyUserData> {
    override val defaultValue: MyUserData = MyUserData.getDefaultInstance()

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

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

    val Context.settingsUserData: DataStore<MyUserData> by dataStore(
        fileName = "userdata.pb", //로컬에 저장될 protobuf 파일명
        serializer = UserDataSerializer
    )
}

 

step4. Main Activity

class MainActivity : ComponentActivity() {
    private val Context.settingsUserData: DataStore<MyUserData> by dataStore(
        fileName = "userdata.pb", //로컬에 저장될 protobuf 파일명
        serializer = UserDataSerializer
    )

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContent {
            TPProtoDataTheme {
                val dataStore = applicationContext.settingsUserData
                val savedUserName = dataStore.data.map { // MyUserData의 data인 name
                        settingsUserData ->
                    settingsUserData.name
                }.collectAsState(initial = "")

                Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
                    Greeting(
                        name = savedUserName.value,
                        modifier = Modifier.padding(innerPadding),
                        onSave = { userName ->
                            lifecycleScope.launch {
                                dataStore.updateData { currentNames ->
                                    currentNames.toBuilder()
                                        .setName(userName)
                                        .build()
                                }
                            }
                        }
                    )
                }
            }
        }
    }
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier, onSave: (String) -> Unit) {
    var text by remember {
        mutableStateOf(name)
    }
    Column(Modifier.padding(all = 32.dp)) {
        TextField(value = text, onValueChange = { text = it })
        Button(onClick = { onSave(text) }) {
            Text(text = "save")
        }
    }
}

@Preview(showBackground = true)
@Composable
fun GreetingPreview() {
    TPProtoDataTheme {
        Greeting("Android") {}
    }
}

 

 

step5. 이제!! 준비해둔 TextField에 userName을 입력해보자!! save button 클릭~

그리고 Device Explorer에 들어가서 내가 저장한 userName이 저장되었는지 확인해보자.

 

/data/data/내 패키지/files/datastore/userdata.pb

 

userdata.pb는 내가 지정한 fileName이다!  

 

입력한 userName "Android Cat"이 정상적으로 입력되었다 😆

 

초기 세팅이 어려울 뿐, 생각보다 더 간단해서 놀랐다!!

이렇게 Proto DataStore로 디바이스 하드웨어에 CURD 기능 처리가 가능하다.  뿌듯~ 😆

728x90
반응형