๋ชฉ์ฐจ
Proto Datastore ๊ฐ๋ ์ดํดํ๊ธฐ
Jetpack์ Datastore์ ํ๋กํ ์ฝ ๋ฒํผ๋ฅผ ์ฌ์ฉํ์ฌ ํค-๊ฐ ์ ๋๋ ์ ํ์ด ์ง์ ๋ ๊ฐ์ฒด๋ฅผ ์ ์ฅํ ์ ์๋ ๋ฐ์ดํฐ ์ ์ฅ์ ์๋ฃจ์ ์ด๋ค.
Datastore์ kotlin์ ์ฝ๋ฃจํด ๋ฐ Flow๋ฅผ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ์ ์ด๊ณ ์ผ๊ด๋ ํธ๋์ญ์ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค.
์ด ๋ 2๊ฐ์ง์ ์ ์ฅ์๋ฅผ ์ ๊ณตํ๋ค.
1๏ธโฃProto Datastore : ๋ง์ถค ๋ฐ์ดํฐ ์ ํ์ ์ธ์คํด์ค๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ค. ์ ํ ์์ ์ฑ์ ์ ๊ณตํ๋ฉฐ ํ๋กํ ์ฝ ๋ฒํผ๋ฅผ ์ฌ์ฉํ์ฌ ์คํค๋ง๋ฅผ ์ ์ํ๋ค.
2๏ธโฃPreferences Datastore : ํค๋ฅผ ์ฌ์ฉํ์ฌ ๋ฐ์ดํฐ์ ์ ์ฅ & ์ก์ธ์ค๋ฅผ ํ๋ค ์ ํ ์์ ์ฑ์ ์ ๊ณตํ์ง ์์ผ๋ฉฐ ์ฌ์ ์ ์ ์๋ ์คํค๋ง๊ฐ ํ์ํ์ง ์๋ค.
โ ์ ํ ์์ ์ฑ์ ์ ๊ณตํ๋ฉด ๋ฌด์์ด ๋ค๋ฅธ๊ฑธ๊น?
๐จ๏ธํ๋ก๊ทธ๋จ์์ ๋ฐ์ดํฐ ์ ํ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฌ์ฉํ๋๋ก ๋ณด์ฅํ๋ ๊ฐ๋ ์ด๋ค. ์ฆ ๋ณ์์ ์ ์ฅ๋ ๋ฐ์ดํฐ๊ฐ ํด๋น ๋ณ์์ ์ ํ๊ณผ ์ผ์นํ๋์ง๋ฅผ ํ์ธํ๋ค. ์ด๋ฅผ ํตํด ์ปดํ์ผ ์์ ์ ๋ฐ์ดํฐ ์ ํ ์ค๋ฅ๋ฅผ ๊ฐ์งํ์ฌ ์ฝ๋ ์ค๋ฅ๋ฅผ ์ค์ผ ์ ์๋ค.
โ ํ๋กํ ์ฝ ๋ฒํผ๋?
๐จ๏ธ ๊ตฌ์กฐํ๋ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ง๋ ฌํํ๋ ๋ฐฉ๋ฒ์ ์๋ฏธํ๋ค.
โ ์คํค๋ง๋?
๐จ๏ธ ์คํค๋ง๋ ๋ฉ์์ง์ ๊ตฌ์กฐ๋ฅผ ์ ์ํ๋ ๊ท์น์ ์๋ฏธํ๋ค.
protobuf ๊ฐ์ฒด ์ ์ ๋ฐ ์ฌ์ฉ
Proto Datastore ๊ตฌํ์ Datastore ๋ฐ ํ๋กํ ์ฝ ๋ฒํผ๋ฅผ ์ฌ์ฉํ์ฌ ์ ํ์ด ์ง์ ๋ ๊ฐ์ฒด๋ฅผ ๋์คํฌ์ ์ ์งํ๋ค.
1๏ธโฃ proto ํ์ผ ๋ง๋ค๊ธฐ - ์คํค๋ง ์ ์ํ๊ธฐ
proto ํ์ผ์์ ์คํค๋ง๋ฅผ ์ ์ํด์ค๋ค.
proto๋ฅผ ์ฌ์ฉํ๋ ค๋ฉฐ app/src/main/proto/ ๋๋ ํฐ๋ฆฌ์ proto ํ์ผ์ ์ฌ์ ์ ์๋ ์คํค๋ง๊ฐ ์์ด์ผํ๋ค.
์ฌ์ ์ ์๋ ์คํค๋ง๋ Proto์ ์ ์งํ๋ ๊ฐ์ฒด์ ์ ํ์ ์ ์ํ๋ค.
์ฐ๋ฆฌ๋ ๋ก๊ทธ์ธ ์์ฒญ ์ ์๋ต์ผ๋ก ์ค๋ access token๊ณผ refresh token ๋ ๊ฐ์ง์ ๊ฐ์ฒด๋ฅผ ํํํด์ค๋ค.
DataStore์ ์ ์ฅ๋ UserToken ํด๋์ค ์๋์ ์ด ๋ ํ๋๊ทธ๋ฅผ ํตํฉํด์ฃผ์๋ค.
์ด ๋ protobuf์ ๊ฐ ๊ตฌ์กฐ๋ message ํค์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ํ๋ค.
๊ตฌ์กฐ์ ๊ฐ ์์๋ ์ ํ๊ณผ ์ด๋ฆ์ ๋ฐ๋ผ ๋ฉ์์ง ๋ด์ ์ ์๋๋ฉฐ 1๋ถํฐ ์ฐจ๋ก๋๋ก ์์๊ฐ ํ ๋น๋๋ค.
syntax = "proto3";
option java_package = "com.teamfilmo.filmo.data.local.model";
option java_multiple_files = true;
message UserToken {
string access_token = 1;
string refresh_token = 2;
}
Proto ์คํค๋ง๋ฅผ ์ ์ํ๋ ๋ฐฉ๋ฒ
1๏ธโฃ์ฒซ๋ฒ์งธ ์ค์ proto3 systax์ ์ฌ์ฉํ๋ค๊ณ ์ ์ด์ค๋ค. ๋ง์ฝ ์ ์ด์ฃผ์ง ์์ผ๋ฉด ํ๋กํ ์ฝ ๋ฒํผ ์ปดํ์ผ๋ฌ๋ proto2๋ฅผ ์ฌ์ฉํ๋ค๊ณ ๊ฐ์ ํ๋ค.
2๏ธโฃUserToken message ์ ์๋ access_token๊ณผ refresh_token 2๊ฐ์ง์ ํ๋๋ฅผ ์ง์ ํ๋ค. ์ด๋ฌํ ๋ฉ์์ง์ ํฌํจํ๊ณ ์ถ์ ๋ฐ์ดํฐ ๊ฐ๊ฐ์ ํ๋๋ ์ด๋ฆ๊ณผ ํ์ ์ ๊ฐ์ง๋ค.
3๏ธโฃํ๋์๋ ์๋ฅผ ์ง์ ํด์ฃผ์ด์ผํ๋ค. ์ด ๋ ์๋ ๋ฐ๋์ ์ ์ผํด์ผํ๋ค(unique)
์ด๋ ๊ฒ proto ํ์ผ์ ์ ์ํ๊ณ ํ๋ก์ ํธ๋ฅผ ๋ค์ ๋น๋ํ๋ฉด UserToken message๋ ์ปดํ์ผ ์๊ฐ์ ๊ฐ์ฒด์ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
โฃ๏ธ ์ฒ์์ ์ฝ๋๋ฅผ ๋ฐ์์ ๋๋ ์ ์๋ UserToken ํด๋์ค๊ฐ ์์ด์ ์๋ฌ๊ฐ ๋ฐ์ํ์๋ค. ๊ทธ๋์ ๊ฐ๋ฐ์๋ถ๊ป ํ์ผ์ด ์๋ค๊ณ ์ง๋ฌธ์ ๋๋ ธ๊ณ , ๋น๋ ํ์ ์์ฑ๋๋ค๋ ๋ต๋ณ์ ๋ฐ์๋ค. ๊ทธ ์ด์ ๊ฐ ์์ ๊ฐ์๋ค!!
์ด์ ์ด๋ ๊ฒ ์ ํ์ด ์ง์ ๋ UserToken ๊ฐ์ฒด๋ฅผ ์ ์ฅํด์ค Proto Datastore์ ๋ง๋ค์ด์ฃผ์. ์ ์ฅํด์ฃผ๊ธฐ ์ํด์๋ 2๋จ๊ณ๋ฅผ ๊ฑฐ์ณ์ผํ๋ค.
- Serializer<T>๋ฅผ ๊ตฌํํ๋ ํด๋์ค ์ ์ํ๊ธฐ
- dataStore๋ก ๋ง๋ ์์ฑ ์์์ ์ฌ์ฉํ์ฌ DataStore<T >์ ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด์ค๋ค.
์ฌ๊ธฐ์ T๋ proto ํ์ผ์ ์ ์๋ ์ ํ์ ์๋ฏธํ๋ค.
์๋์ seirializer ๋ง๋ค๊ธฐ ๋ด์ฉ๋ถํฐ ์ดํด๋ณด์!
2๏ธโฃserializer ๋ง๋ค๊ธฐ
proto ํ์ผ์ ์ ์ํ ๋ฐ์ดํฐ ์ ํ ( access_token๊ณผ refresh_token์ ์ฝ๊ณ ์ฐ๋ ๋ฐฉ๋ฒ์ Datastore์ ์๋ฆฌ๋ ค๋ฉด serializer์ ๊ตฌํํด์ผํ๋ค. serializer ํด๋์ค๊ฐ ๋ฐ์ดํฐ ์ ํ์ ์ฝ๊ณ ์ฐ๋ ๋ฐฉ๋ฒ์ Datastore์ ์๋ ค์ฃผ๋ ์ญํ ์ ํ๋ค.
๋ํ serializer๋ ๋์คํฌ์ ๋ฐ์ดํฐ๊ฐ ์์ ๋(ํ์ผ์ด ์์ฑ๋์ง ์์ ๊ฒฝ์ฐ) ๋ฐํ๋ seriazlier๊ธฐ๋ณธ๊ฐ๋ ์ ์ํด์ฃผ์ด์ผํ๋ค.
object UserTokenSerializer : Serializer<UserToken> {
private val TAG = this::class.java.simpleName
override val defaultValue: UserToken
get() = UserToken.getDefaultInstance()
override suspend fun readFrom(input: InputStream): UserToken {
try {
return UserToken.parseFrom(input)
} catch (e: InvalidProtocolBufferException) {
throw CorruptionException("Cannot read proto.", e)
}
}
override suspend fun writeTo(
t: UserToken,
output: OutputStream,
) {
t.writeTo(output)
}
}
์ด์ ์์ ๋งํ๋ DataStore<T>์ ์ธ์คํด์ค๋ฅผ ๋ง๋ค์ด์ค ์ฐจ๋ก์ด๋ค. ์๋์ ๋ด์ฉ๋ ์ดํด๋ณด์
Proto Datastore์์ ๋ฐ์ดํฐ ์ ์ง
1๏ธโฃDatastore ๋ง๋ค๊ธฐ
access_token๊ณผ refresh_token์ Datastore ๊ฐ์ฒด์ AuthRepository์ ์ ์ฅ๋์ด์ผํ๋ค.
Datastore ์ธ์คํด์ค๋ฅผ ๋ง๋ค๊ธฐ ์ํด dataStore ์์์ ์ฌ์ฉํ์ฌ ์์ ๊ธฐ๋ก Context๋ฅผ ์ฌ์ฉํ๋ค.
์ด ์์์๋ ๋๊ฐ์ง ํ์ ๋งค๊ฐ๋ณ์๊ฐ ์๋ค.
- Datastore๊ฐ ์๋ํ ํ์ผ์ ์ด๋ฆ : user_token_pb
-> ์ด๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋๋ฐ ์ฌ์ฉํ ํ์ผ์ Datastore์ ์๋ ค์ค๋ค.
- Datastore์์ ์ฌ์ฉ๋๋ ์ ํ์ ์ํ serializer : UserTokenSerializer
-> ์ด๋ ์์ ์ ์ํด์ค serializer ํด๋์ค์ ์ด๋ฆ์ Datastore์ ์๋ ค์ค๋ค.
object DataStoreConfig {
const val USER_TOKEN_DATA_STORE_FILE_NAME = "user_token.pb"
}
val Context.userToken: DataStore<UserToken> by dataStore(
fileName = DataStoreConfig.USER_TOKEN_DATA_STORE_FILE_NAME,
serializer = UserTokenSerializer,
)
dataStore ์์์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ด ์ด๋ฆ์ ๊ฐ์ง Datastore ์ธ์คํด์ค๊ฐ ํ๋๋ง ์์์ ๋ณด์ฅํด์ค๋ค. ํ์ฌ AuthRepository๋ ์์ฑ์ ๋งค๊ฐ๋ณ์๋ก DataStore ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ์์ผํ๋ค.
์ฐ๋ฆฌ๋ di๋ฅผ ์ฌ์ฉํ๋ฏ๋ก ์๋์ ๊ฐ์ด ๋ชจ๋๋ก ์ ์ํด๋์๋ค.
@Module
@InstallIn(SingletonComponent::class)
object LocalModule {
@Provides
fun provideDataStoreUserToken(
@ApplicationContext context: Context,
): DataStore<UserToken> {
return context.userToken
}
}
2๏ธโฃ Proto Datastore์์ ๋ฐ์ดํฐ ์ฝ๊ธฐ
๊ตฌํํด์ค ํจ์๋ฅผ ์ธํฐํ์ด์ค๋ก ์ ์ํด์ค๋ค.
interface UserTokenSource {
fun getUserToken(): Flow<String>
suspend fun setUserToken(token: String)
fun getRefreshToken(): Flow<String>
suspend fun setRefreshToken(token: String)
suspend fun updateToken(
accessToken: String,
refreshToken: String,
)
suspend fun clearUserToken()
}
Proto Datastore์ Flow<UserToken>์ ์ ์ฅ๋ ๋ฐ์ดํฐ๋ฅผ ๋ ธ์ถํ๋ค.
DataStore.data๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฅ๋ ๊ฐ์ฒด์์ ์ ์ ํ ์์ฑ์ Flow๋ฅผ ๋ ธ์ถํ๋ค.
class UserTokenSourceImpl
@Inject
constructor(
private val dataStore: DataStore<UserToken>,
) : UserTokenSource {
override fun getUserToken(): Flow<String> {
return dataStore.data.map {
it.accessToken
}
}
override suspend fun setUserToken(token: String) {
dataStore.updateData {
it.copy {
accessToken = token
}
}
}
override fun getRefreshToken(): Flow<String> {
return dataStore.data.map {
it.refreshToken
}
}
override suspend fun setRefreshToken(token: String) {
dataStore.updateData {
it.copy {
refreshToken = token
}
}
}
override suspend fun updateToken(
accessToken: String,
refreshToken: String,
) {
dataStore.updateData {
it.copy {
this.accessToken = accessToken
this.refreshToken = refreshToken
}
}
}
override suspend fun clearUserToken() {
dataStore.updateData {
userToken {
clearAccessToken()
clearRefreshToken()
}
}
}
}
Proto Datastore์ ๋ฐ์ดํฐ ์ฐ๊ธฐ
๋ฐ์ดํฐ๋ฅผ ์ธ ์ ์๋๋ก suspend ์ ์ง ํจ์์ธ DataStore.updateData ํจ์๋ฅผ ์ ๊ณตํ๊ณ ์๋ค. ์ด ํจ์์์ ๋งค๊ฐ๋ณ์๋ก token์ ๊ฐ์ ธ์ฌ ์ ์๋ค. ์ด๋ฅผ ์ ๋ฐ์ดํธ ํด์ฃผ๋ ค๋ฉด UserToken.copy๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ ์๋ก์ด token์ผ๋ก accessToken์ ์ ๋ฐ์ดํธ ํด์ค๋ค.
updateData( )๋ ๋ฐ์ดํฐ์ ํ์ฌ ์ํ๋ฅผ ๋ฐ์ดํฐ ์ ํ์ ์ธ์คํด์ค๋ก ์ ๊ณตํ๊ณ ์์์ ์ฝ๊ธฐ-์ฐ๊ธฐ-์์ ์์ ์ ํตํด ํธ๋์ญ์ ๋ฐฉ์์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ค. ์ฝ๋ฃจํด์ ๋ฐ์ดํฐ๊ฐ ๋์คํฌ์ ์ ์ง๋๋ฉด ์๋ฃ๊ฐ ๋๋ค.
override suspend fun setUserToken(token: String) {
dataStore.updateData {
it.copy {
accessToken = token
}
}
}
โ๊ณต์๋ฌธ์๋ toBuilder( )๋ฅผ ํตํด ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํ๋ค. ์ฐ๋ฆฌ ์ฝ๋๋ .copy( )๋ฅผ ์ฌ์ฉํ๋๋ฐ ์ด ๋๊ฐ์ง ๋ฐฉ์์ ์ฐจ์ด๋ ๋ญ๊น?
๐จ๏ธ toBuilder()์ copy()๋ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ๋ณต์ฌํ๋๋ฐ ์ฌ์ฉ๋๋ ํจ์์ด๋ค. ์ฐจ์ด๋ฅผ ์์๋ณด์
๊ฐ์ฒด ๋ณ๊ฒฝ ๋ฐฉ์
- toBuilder( )๋ ๊ฐ์ฒด์ ๋น๋๋ฅผ ๋ฐํํ๋ค. ๋น๋๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ์ฒด์ ์์ฑ์ ๋ณ๊ฒฝํ ํ build() ๋ฉ์๋๋ฅผ ํธ์ถ๋๋ฉฐ ์ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ค.
- copy()๋ ๊ฐ์ฒด์ ์๋ก์ด ๋ณต์ฌ๋ณธ์ ๋ฐํํ๋ค. ์ ๋ณต์ฌ๋ณธ์๋ ์๋ณธ ๊ฐ์ฒด์ ๋ชจ๋ ์์ฑ๊ฐ์ด ํฌํจ๋๋ค. ์ด๋ ๋ณ๊ฒฝํ๋ ค๋ ์์ฑ๊ฐ์ ์ง์ ์ง์ ํ ์ ์๋ค.
์ฑ๋ฅ
- toBuilder()๋ ์ผ๋ฐ์ ์ผ๋ก ๊ฐ์ฒด์ ๋ชจ๋ ์์ฑ์ ๋ณต์ฌํด์ผํ๊ธฐ ๋๋ฌธ์ copy()๋ณด๋ค ์ฑ๋ฅ์ด ๋ฎ๋.
- copy()๋ฅผ ํตํด ๋ง๋ค์ด์ง ์ ๋ณต์ฌ๋ณธ์๋ ์๋ณธ ๊ฐ์ฒด์ ๋ชจ๋ ์์ฑ ๊ฐ์ด ํฌํจ๋์ง๋ง ๋ณ๊ฒฝํ๋ ค๋ ์์ฑ ๊ฐ๋ง ๋ณต์ฌํ๋ฉด ๋๋ค. ์ด๋ toBuilder()๋ณด๋ค ์ฑ๋ฅ์ด ๋์ ํธ์ด๋ค.
์ ์ฉํ ์ํฉ
- toBuilder()๋ ๋ณต์กํ ๋ณ๊ฒฝ์ ์ํํ ๋ ๋ ์ ํํ๋ค. ๋น๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ์์ฑ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ์กฐ๊ฑด๋ถ๋ก ์์ฑ์ ๋ณ๊ฒฝํ ์ ์๋ค.
- copy()๋ ๊ฐ๋จํ ๋ณ๊ฒฝ์ ์ํํ ๋ ๋ ์ ์ฉํ๋ค. ํ๋๊ฐ์ ์์ฑ์ ๋ณ๊ฒฝํด์ผํ๋ ๊ฒฝ์ฐ copy()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ๊ฐ๋จํ๋ค.
์ฐ๋ฆฌ ์ฝ๋์ ๋ ์ ์ ํ ๊ฒ์?
์ฐ๋ฆฌ๋ accessToken ์์ฑ๋ง ๋ณ๊ฒฝํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ copy()๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ๊ฐ๋จํ๊ณ ์ง๊ด์ ์ด๋ค.
'๐ค2024 ์๋๋ก์ด๋ > ๐ฟ ์ํ ํ๋ก์ ํธ ๊ฐ๋ฐ ์ผ์ง' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๐๏ธ[Android ์ฑ ์ํคํ ์ณ] : UI layer , Domain layer, Data layer (0) | 2024.03.15 |
---|---|
๐Android ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ di ์ ์ฉํด๋ณด๋ ๊ธฐ๋ก (0) | 2024.03.15 |
Android ๋คํธ์ํฌ ํต์ ๊ฐ๋ ์ตํ๊ธฐ [REST,URL, URI, Retrofit] (0) | 2024.03.05 |
[android ๋ก๊ทธ์ธ] jwt token์ผ๋ก ๊ตฌ๊ธ ๋ก๊ทธ์ธ ์์ฑํ๊ธฐ (0) | 2024.02.15 |
[hilt 2ํ] hilt ๋ฅผ ์ฌ์ฉํ ์ข ์ ํญ๋ชฉ ์ฝ์ (0) | 2024.02.10 |