[Android] Gson을 이용한 Room에 다양한 타입의 데이터 저장하기
Android

[Android] Gson을 이용한 Room에 다양한 타입의 데이터 저장하기

728x90

 

 

 Room은 Android Jetpack에서 제공하는 라이브러리로, SQLite에 대한 추상화 레이어를 제공하여 로컬 데이터베이스를 보다 간단하게 사용할 수 있게 해줍니다.

 많은 양의 데이터를 처리하는 앱은 데이터를 로컬로 유지해서 많은 이점을 얻을 수 있습니다. 가장 대표적인 예시로는 데이터를 로컬 디비에 캐싱하는 것입니다. 앱이 네트워크에 접근할 수 없는 오프라인 상태인 경우에도 데이터가 로컬 디비에 저장되어 있다면, 이를 활용하여 콘텐츠를 탐색할 수 있습니다.

 Room에 데이터를 저장할 때는 기본적으로 primitive type과 그 wrapping 타입만 지원합니다. 그럼 그 외의 사용자가 정의한 커스텀 클래스나 List와 같은 경우는 어떻게 저장해야 할까요?

 이번 글에서는 Room에서 다양한 타입의 데이터를 어떻게 저장하고 가져올 수 있는지에 대해 설명해보도록 하겠습니다.

 

 

 

 

TypeConverter

Room에 primitive type이 아닌 다른 타입의 데이터를 저장하기 위해선 Room에서 제공하는 TypeConverter 어노테이션을 사용하면 됩니다.

class Converters {
    @TypeConverter
    fun fromTimestamp(value: Long?): Date? {
        return value?.let { Date(it) }
    }
    
    @TypeConverter
    fun dateToTimestamp(date: Date?): Long? {
        return date?.time?.toLong()
    }
}

 위의 예제에서는 두 개의 함수를 정의합니다. 하나는 Date 객체를 Long 객체로 변환하고, 다른 하나는 Long에서 Date로의 역변환을 실행합니다. Room은 Long 객체를 저장하는 방법을 이미 알고 있기 때문에 위의 Converter를 사용하여 Date 타입의 값을 저장할 수 있습니다.

 

 위의 예제처럼 Date와 primitive type의 변환이 간단하다면 위의 방식을 사용하면 됩니다. 하지만 List나 사용자가 직접 정의한 커스텀 클래스들은 primitive type으로의 변환이 쉽지 않습니다.

 이럴 때는 데이터를 json 형태의 문자열로 바꿔주는 직렬화 라이브러리를 사용하면 간단합니다. Room에 데이터를 저장할 때는 json 형태의 문자열로 직렬화하여 저장한 뒤, 꺼낼 때는 다시 역직렬화하여 원하는 형태의 클래스로 변환하면 됩니다. 직렬화 라이브러리에는 gson, moshi, kotlinx.serialization 등의 다양한 라이브러리가 있지만, 이번 글에서는 gson을 사용하도록 하겠습니다.

 

 

 

 

예제 보기

@Entity
data class UserEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0L,
    val name: String,
    val phones: List<String>,
    val address: Address
)

data class Address(
    val value: String,
    val zipcode: Int
)

 위의 UserEntity 클래스는 User 정보를 지니고 있는 하나의 테이블입니다. UserEntity의 필드로는 name, phones, address 등의 다양한 필드가 정의되어 있습니다. 그 중 phones는 List<String> 형태를, address는 커스텀 클래스인 Address로 정의되어 있습니다.

 위처럼 Entity를 정의한 후 빌드를 하게 되면 아래와 같은 에러 메시지가 나오게 됩니다.

error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
private final java.util.List<java.lang.String> phones = null;

 이는 List<String> 타입을 Room에 저장하는 방법을 모르기 때문에 TypeConverter를 사용해 정의해주라는 의미입니다.

 

@TypeConverter

 UserEntity를 데이터베이스의 저장하기 위해선 List<String>과 Address 타입을 저장하는 방법을 정의해야 합니다. 이를 위해 List<String>을 String으로, Address를 String으로 변환해줄 TypeConverter가 2개 필요합니다.

@ProvidedTypeConverter
class StringListTypeConverter(private val gson: Gson) {

    @TypeConverter
    fun listToJson(value: List<String>): String? {
        return gson.toJson(value)
    }

    @TypeConverter
    fun jsonToList(value: String): List<String> {
        return gson.fromJson(value, Array<String>::class.java).toList()
    }
}
@ProvidedTypeConverter
class AddressTypeConverter(private val gson: Gson) {

    @TypeConverter
    fun listToJson(value: Address): String? {
        return gson.toJson(value)
    }

    @TypeConverter
    fun jsonToList(value: String): Address {
        return gson.fromJson(value, Address::class.java)
    }
}

 @ProvidedTypeConverter를 사용하여 타입 컨버터를 정의한 뒤, @TypeConverter를 통해 어떤식으로 변환해줄지 정의해줍니다. Gson 라이브러리를 사용하면 간단하게 직렬화/역직렬화를 할 수 있습니다.

 

TypeConverter를 정의했다면, 이제 아래와 같이 RoomDatabase 객체에 등록해주면 됩니다.

@Database(entities = [UserEntity::class], version = 1, exportSchema = false)
@TypeConverters(
    value = [
        StringListTypeConverter::class,
        AddressTypeConverter::class
    ]
)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}
@Module
@InstallIn(SingletonComponent::class)
object DatabaseModule {

    private const val DB_NAME = "sample.db"

    @Singleton
    @Provides
    fun provideGson(): Gson {
        return Gson()
    }

    @Singleton
    @Provides
    fun provideDatabase(@ApplicationContext context: Context, gson: Gson): AppDatabase {
        return Room
            .databaseBuilder(context, AppDatabase::class.java, DB_NAME)
            .addTypeConverter(StringListTypeConverter(gson)) // 'List<String>' converter
            .addTypeConverter(AddressTypeConverter(gson)) // 'Address' converter
            .build()
    }

    @Singleton
    @Provides
    fun provideUserDao(database: AppDatabase): UserDao {
        return database.userDao()
    }
}

 

 위와 같이 설정한 뒤 User 정보를 하나 저장해주면 아래와 같이 저장되는 것을 확인하실 수 있습니다.

 

 

 

 

예제 소스

https://github.com/tkdgusl94/blog-source/tree/master/Roomconverter

 

GitHub - tkdgusl94/blog-source: https://leveloper.tistory.com/ 에서 제공하는 예제 source

https://leveloper.tistory.com/ 에서 제공하는 예제 source. Contribute to tkdgusl94/blog-source development by creating an account on GitHub.

github.com

 

참고

https://readystory.tistory.com/209

 

[Android] Room 을 통해 List 등 다양한 타입 데이터 저장하기(feat. Moshi)

Room 은 SQLite 에 대한 추상화 레이어를 제공하여 개발자가 보다 편리하게 로컬 데이터베이스에 접근할 수 있게 해주는 Jetpack 라이브러리입니다. 많은 데이터를 처리하는 앱은 데이터를 로컬로 유

readystory.tistory.com

https://developer.android.com/training/data-storage/room/referencing-data

 

Room을 사용하여 복잡한 데이터 참조  |  Android 개발자  |  Android Developers

Room을 사용하여 복잡한 데이터 참조 Room은 기본 유형과 박싱된 유형 간 변환을 위한 기능을 제공하지만 항목 간 개체 참조는 허용하지 않습니다. 본 문서에서는 유형 변환기를 사용하는 방법 및

developer.android.com

 

728x90