Можно ли использовать дженерики с объектами Android room?

#android #kotlin #polymorphism #android-room

#Android #kotlin #полиморфизм #android-room

Вопрос:

Рассмотрим следующее Entity :

 @Entity(tableName = "media")
data class Media(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    // Stored as a JSON blob in SQLite using some TypeAdapter magic
    val content: Content,
) {
    sealed class Content {
        data class Image(val width: Int, val height: Int): Content()
        data class Video(val framerate: Int): Content()
    }
}
 

Чтобы получить доступ Media.Content.Image.width , я должен был бы сделать

 val media: Media = // { ... } - returns image media
(media.content as Media.Content.Image).width
 

Это довольно быстро устаревает и кажется подверженным ошибкам.

С дженериками я мог бы сделать что-то вроде следующего:

 @Entity(tableName = "media")
data class Media<T: Media.Content>(
    @PrimaryKey(autoGenerate = true) val id: Long = 0,
    val content: T,
) {
    sealed class Content {
        data class Image(val width: Int, val height: Int): Content()
        data class Video(val framerate: Int): Content()
    }
}
 
 val media: Media<Media.Content.Image> = // { ... } - returns image media
media.content.width
 

Однако этот стиль кажется проблематичным:

 error: Cannot use unbound fields in entities.
    private final T content = null;
error: Cannot figure out how to save this field into database. You can consider adding a type converter for it.
    private final T content = null;
 

Я не уверен T , как будет выглядеть TypeConverter для — есть ли способ получить место для обработки этого типа дженериков или он просто не поддерживается?

Ответ №1:

Почему бы не использовать овеществленный встроенный метод, который обрабатывает приведение типов? Поскольку у вас уже нет никаких гарантий безопасности типов от компилятора.

Еще лучше было бы также сериализовать тип и использовать оператор switch для обработки всех возможных возвращаемых типов.

 inline fun <reified T> Media.contentAccess(): T {
  return this.content as T
}

val media: Media = Media(id = 1, content = Media.Content.Image(width = 1, height = 1))
media.contentAccess<Media.Content.Image>().width
 

Комментарии:

1. Это вариант, который я рассмотрел. Однако у меня есть некоторые проблемы с этим. 1. вы не можете создать, например, a List<Media<Media.Content.Image>> , 2. у вас есть два свойства для одних и тех же данных, 3. contentT<Media.Content.Image>() такой же длины (или длиннее), как content as Media.Content.Image (почему бы просто не использовать as в первую очередь?), 4. Теперь вам нужно использовать общий метод для каждого доступа . Используя as напрямую, вы можете использовать интеллектуальные приведения для последовательного .content использования.

2. Я слышу все ваши опасения, и они полностью обоснованы. Проблема, с которой вы сталкиваетесь, заключается в том, что объявление объекта по сути представляет собой схему хранения, аналогичную сценарию SQL DDL, а не объекту транспортного уровня. Это означает, что тип T: Media . Содержимое — это характеристика способа запроса к БД. Если вы хотите создать более эргономичный api, то ваш DAO, вероятно, должен предлагать mapper, который преобразует мультимедиа в MediaWrapper<T>, когда вы можете использовать контекст вашего запроса для предоставления правильной семантики.