Developing Myself Everyday
article thumbnail

사진: UnsplashFikri Rasyid

 

 

이번 게시글에서는 Datastore에 대해 공부한 다음 이전에 만들어놨던 멀티 모듈 로그인 앱에 사용해서 자동 로그인 기능을 구현해보려고 합니다.

 

시작하기에 앞서 이 게시글에서는 Datastore에 대한 이론적인 내용을 다루고 있지는 않습니다. 아래의 게시글에서 이를 공부한 내용이 있으니, Datastore에 대해 잘 알지 못하신다면 참고하고 오시면 좋을 것 같습니다.

 

[AAC] DataStore with SharedPreferences

이 게시글은 아래의 공식 문서를 보고 작성했습니다. 앱 아키텍처: 데이터 영역 - Datastore - Android 개발자 | Android Developers 데이터 영역 라이브러리에 관한 이 앱 아키텍처 가이드를 통해 Preferences D

everyday-develop-myself.tistory.com


 

 

 

멀티 모듈에서의 Datastore


먼저 제가 자동 로그인 기능을 추가하고자하는 멀티 모듈 프로젝트의 그래프를 보겠습니다.

 

위의 그래프에서 알 수 있는 것은 데이터 모듈에서 데이터베이스에 대한 데이터를 가져오고 있다는 것입니다.

 

결국 Datastore 또한 데이터입니다. 그렇기 때문에 전체적인 모듈들의 구조에서 Datastore의 위치는 이전의 database와 크게 다르지 않습니다.

 

그렇기에 저는 Datastore 모듈을 만들고, data 모듈에 이를 주입해주도록 하겠습니다.

 

 

 

 

Datastore과 Kotlin Serialization


Datastore는 프로토콜 버퍼를 이용해서 데이터를 직렬화합니다. 일반적으로는 원시 타입의 데이터를 저장하게 됩니다.

 

이 뿐만 아니라, Kotlin Serialization과 커스텀 데이터 클래스(data class)를 활용하여 DataStore를 사용할 수도 있습니다. 이렇게 되면 데이터를 위한 스키마를 제공함과 동시에, 프로토콜 버퍼 라이브러리를 사용 혹은 학습하지 않아도 보일러플레이트 코드를 줄일 수 있습니다.

 

 

Data class

가장 먼저 정의할 것은 우리가 직렬화할 데이터 클래스입니다. 일반적으로 데이터 클래스를 정의하고 `@Serializable` 애노테이션을 붙여줍니다.

import kotlinx.serialization.Serializable

@Serializable
data class UserPreferences(
    val email: String = "",
    val password: String = ""
)

 

 

Serializer

다음으로는 Serializer를 구현해야 합니다. Serializer는 인터페이스로 기본값과 데이터를 가져오거나 변경하는 메서드를 가집니다.

public interface Serializer<T> {

    public val defaultValue: T

    public suspend fun readFrom(input: InputStream): T

    public suspend fun writeTo(t: T, output: OutputStream)
}

 

 

데이터를 Json으로 쓰고 읽기 위해서 위의 인터페이스를 구현한 클래스에 직렬화하는 방법을 정의해야 합니다.

class UserPreferencesSerializer @Inject constructor() : Serializer<UserPreferences> {

    override val defaultValue: UserPreferences = UserPreferences()

    override suspend fun readFrom(input: InputStream): UserPreferences {
        try {
            return Json.decodeFromString(
                UserPreferences.serializer(), input.readBytes().decodeToString())
        } catch (serialization: SerializationException) {
            throw CorruptionException("Unable to read UserPrefs", serialization)
        }
    }

    override suspend fun writeTo(t: UserPreferences, output: OutputStream) {
        withContext(Dispatchers.IO) {
            output.write(Json.encodeToString(UserPreferences.serializer(), t).encodeToByteArray())
        }
    }
}

 

 

Module

다음으로는 데이터 모듈에 이 Datastore를 주입해주기 위해, 모듈을 정의해야 합니다.

@Module
@InstallIn(SingletonComponent::class)
object DataStoreModule {

    @Provides
    @Singleton
    fun provideUserPreferences(
        @ApplicationContext context: Context,
        userPreferencesSerializer: UserPreferencesSerializer
    ): DataStore<UserPreferences> =
        DataStoreFactory.create(userPreferencesSerializer) {
            context.dataStoreFile("user_preferences.json")
        }
}

 

위의 코드에서  context.dataStoreFile("user_preferences.json") 코드는 Datastore가 사용할 파일의 경로를 지정하는 데 사용됩니다.

 

 

Repository

이 Datastore를 주입받은 Repository를 정의합니다. 추상화를 위해 인터페이스를 만들었습니다.

interface UserPrefsRepository {

    fun getUserPrefs(): Flow<User>

    suspend fun setUserPrefs(user: User)
}
class DefaultUserPrefsRepository @Inject constructor(
    private val datastore: DataStore<UserPreferences>
): UserPrefsRepository {

    override fun getUserPrefs(): Flow<User> {
        return datastore.data.map { it.toUser() }
    }

    override suspend fun setUserPrefs(user: User) {
        datastore.updateData { user.toUserPrefs() }
    }
}

 

 

위의 있는 코드에서 toUser, toUserPrefs는 각각의 모듈의 모델을 개별적으로 두기 위해 아래와 같이 Mapper를 정의한 것입니다.

fun User.toUserPrefs() = UserPreferences(
    email, password
)

fun UserPreferences.toUser() = User(
    email, password
)

 

 

마지막으로 Hilt를 통해 Repository를 주입해주면 Datastore를 쉽게 가져오고, 설정할 수 있습니다.

@HiltViewModel
class LoginViewModel @Inject constructor(
    private val loginUseCase: LoginUsecase,
    private val userPrefRepository: UserPrefsRepository
) : ViewModel() {

 

 

 

마무리


이렇게 해서 Datastore를 이용해서 자동 로그인 구현을 진행해 봤습니다. 게시글에서는 Datastore의 기본적인 사용, Datastore에 직렬화를 사용해서 매우 손쉽게 Data class를 저장하고 불러올 수 있다는 것, Datastore의 데이터 가져오고 수정하기를 진행했습니다.

 

앞으로 Datastore를 사용할 일이 있다면, 매우 손쉽게 적용할 수 있겠다는 생각이 듭니다.

 

자동 로그인 기능을 구현한 코드는 아래에 있습니다.

 

GitHub - Iwillbeagood/Login-Multi-Module-Architecture: CleanArchitecture Multi Module Login App

CleanArchitecture Multi Module Login App. Contribute to Iwillbeagood/Login-Multi-Module-Architecture development by creating an account on GitHub.

github.com

 

 

 

Reference

 

Kotlin Serialization과 함께 DataStore 사용하기

작성자: Rohit Sathyanarayana (Software Engineer)  이 글의 원문은 여기서 확인할 수 있으며 블로그 리뷰에는 김태호(Google)님이 참여해주셨습니다. 지금까지 DataStore를 프로토콜 버퍼(Protocol Bu...

developers-kr.googleblog.com

 

profile

Developing Myself Everyday

@배준형

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!