Десериализация вложенных классов Firebase Firestore на AndroidAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Десериализация вложенных классов Firebase Firestore на Android

Сообщение Anonymous »

Описание проблемы
Я пытаюсь десериализовать простой документ Firebase Firestore для приложения Android.

Проблема связана с десериализацией вложенных классов Kotlin. И это должно быть довольно просто, но почему-то кажется, что он не работает.
У меня есть класс, который хотел бы хранить в качестве класса данных Kotlin содержимое карты на удаленный документ.

Даже несмотря на то, что имена полей, предоставленные @PropertyName, совпадают с именами свойств связанного класса и невложенного класса (называемого Bucket) правильно принимает все «примитивные» данные, его Вложенные классы просто не заполняются удаленными данными, возвращаясь к значениям пустого конструктора, которые вы должны предоставить, создавая таким образом мусорный объект.


Детали реализации
Структура данных документа Firebase Bucket следующая:
Изображение

Я создал следующий класс для представления всего документа:

Код: Выделить всё

@Parcelize
data class Bucket(
@PropertyName("id")
val id: String,

@PropertyName("name")
val name: String,
@PropertyName("description")
val description: String,

@PropertyName("bucket_style")
val style: BucketStyle,

@PropertyName("bucket_dates")
val dates: BucketDates
) : Parcelable {
constructor() : this("mock.id", "mock.name", "mock.desc", BucketStyle(), BucketDates(), listOf())
}
В качестве примера с классом BucketStyle:

Код: Выделить всё

@Parcelize
data class BucketStyle(
@PropertyName("icon_family")
val iconFamily: Int,

@PropertyName("icon_id")
val iconId: Int,

@PropertyName("color_id")
val colorId: Int
) : Parcelable {
constructor() : this(0, 0, 1)
}
И симметрично:

Код: Выделить всё

@Parcelize
data class BucketDates(
@PropertyName("creation_date_timestamp")
val creationDate: Timestamp,

@PropertyName("last_edit_timestamp")
val lastEdit: Timestamp
) : Parcelable {
constructor() : this(Timestamp.now(), Timestamp.now())
}
И я десериализую его (на самом деле все документы в коллекции с использованием прослушивателя в реальном времени), используя метод, предоставляемый Firebase по умолчанию ( версия firebase-bom)

Код: Выделить всё

buckets.addSnapshotListener { data, error ->
val update: List = data.toObjects(Bucket::class.java)
}
А внутренние классы не заполняются, как показано в этом отладчике, так как значение сервера отличается, и они используют свое значение по умолчанию, а идентификаторы, имена и все остальное "примитивное" работает корректно .

Изображение

Я не получаю ошибок, только предупреждение logcat во время выполнения:

Код: Выделить всё

W/Firestore: (23.0.4) [CustomClassMapper]: No setter/field for bucket_style found on class com.[redacted].model.bucket.Bucket
W/Firestore: (23.0.4) [CustomClassMapper]: No setter/field for bucket_dates found on class com.[redacted].model.bucket.Bucket
Дополнительный анализ
Эти журналы озадачили меня, потому что я действительно создал методы получения и установки, поскольку Bucket — это класс данных. Или нет?
Я декомпилировал файл kotlin в Java и понял, что для нашего объекта не существует метода получения с именем getbucket_style():

Код: Выделить всё

@PropertyName("bucket_style")
@NotNull
private final BucketStyle style;

@NotNull
public final BucketStyle getStyle() {
return this.style;
}
У нас есть только getStyle().
Мы можем попытаться проделать трюк, переименовав свойство kotlin style в Bucket_style (чтобы сопоставить имя поля документа Firestore) таким образом в Bucket:

Код: Выделить всё

@PropertyName("bucket_style")
val bucket_style: BucketStyle,
Декомпиляция Java будет:

Код: Выделить всё

@PropertyName("bucket_style")
@NotNull
private final BucketStyle bucket_style;

@NotNull
public final BucketStyle getBucket_style() {
return this.bucket_style;
}
Но предупреждение остается постоянным. Сгенерированный геттер имеет заглавную букву "B" (это синтаксическое соглашение для создания геттеров в классах данных Kotlin).

Однако, если я создам собственный геттер с именем getbucket_style() в исходном сегменте, например:

Код: Выделить всё

@Parcelize
data class Bucket(
@PropertyName("id")
val id: String,

@PropertyName("name")
val name: String,
@PropertyName("description")
val description: String,

@PropertyName("bucket_style")
val style: BucketStyle,

@PropertyName("bucket_dates")
val dates: BucketDates
) : Parcelable {

fun getbucket_style() = style

constructor() : this("mocker.id", "mocker.name", "mocker.desc", BucketStyle(), BucketDates(), listOf())
}
Как выглядит декомпиляция Java:

Код: Выделить всё

@PropertyName("bucket_style")
@NotNull
private final BucketStyle style;

@NotNull
public final BucketStyle getbucket_style() {
return this.style;
}

@NotNull
public final BucketStyle getStyle() {
return this.style;
}
Тогда предупреждение изменится!

Код: Выделить всё

W/Firestore: (23.0.4) [CustomClassMapper]: No setter/field for style found on class com.[redacted].model.bucket.Bucket (fields/setters are case sensitive!)
Понятия не имею, почему. Также обратите внимание, что в первом предупреждении свойство упоминается как Bucket_style, а здесь как Style.
Если я проделаю тот же трюк, что и раньше, переименование style в Bucket_style, получим следующее:

Код: Выделить всё

@Parcelize
data class Bucket(
@PropertyName("id")
val id: String,

@PropertyName("name")
val name: String,
@PropertyName("description")
val description: String,

@PropertyName("bucket_style")
val bucket_style: BucketStyle,

@PropertyName("bucket_dates")
val dates: BucketDates
) : Parcelable {

fun getbucket_style() = bucket_style

constructor() : this("mock.id", "mock.name", "mock.desc", BucketStyle(), BucketDates(), listOf())
}
Как выглядит декомпиляция Java

Код: Выделить всё

@NotNull
private final String description;

@PropertyName("bucket_style")
@NotNull
public final BucketStyle getbucket_style() {
return this.bucket_style;
}

@NotNull
public final BucketStyle getBucket_style() {
return this.bucket_style;
}
При этом происходит сбой без предупреждений, поскольку (я полагаю) существует две функции с одинаковым именем без учета регистра:

Код: Выделить всё

E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.[redacted], PID: 18996
java.lang.RuntimeException: Found conflicting getters for name getbucket_style on class com.[redacted].model.bucket.Bucket
Выводы
Альтернативой было бы использование карт Kotlin для моделирования вложенных карт Firestore, но работать с классами гораздо приятнее. с.

Я также пытался разблокировать все классы, но проблема не устранена.

Есть ли чистое решение / что-то не так с реализация?
ИЗМЕНИТЬ
Обходной путь — изменить имена свойств класса, чтобы они соответствовали именам полей документа Firestore.
Моя проблема — это частный случай более общей ошибки. Неправда, что десериализация не работает для подклассов. Это не работает для любого поля класса, имя которого не соответствует Firebase (в моем случае).
Действительно, все, что мне нужно было сделать, это изменить имя карты с «bucket_style» на «style», а внутренние поля (теперь на карте «style») использовать верблюжий регистр (чтобы соответствовать объявлениям моего приложения) и вуаля, данные теперь идут туда, куда и должны. Теперь я использую одни и те же имена на бэкенде и в своей модели, чтобы десериализатор не запутался.
Тем не менее, я не знаю, как это не работает . Я считаю, что это то, что я запускаю (я отказываюсь верить, что до сих пор никто не сталкивался с этой проблемой и не сообщал о ней), хотя мое приложение все еще маленькое, следуйте рекомендациям де-факто и используйте Firebase исключительно для пожарный магазин.

Подробнее здесь: https://stackoverflow.com/questions/697 ... on-android
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»