如何为含日期时间字段的Kotlin数据类创建无参构造函数
Firestore反序列化LocalDate/LocalTime崩溃的解决办法
问题背景
定义了如下Kotlin数据类用于映射Firestore文档:
data class TimesheetEntry( val id: Int = 0, val title: String = "", val category: String = "", var entryDate: LocalDate, var startTime: LocalTime, var endTime: LocalTime, val imageBitmap: Bitmap? = null )
从Firestore获取数据时应用崩溃,Logcat报错:
java.lang.RuntimeException: Could not deserialize object. Class java.time.LocalDate does not define a no-argument constructor. If you are using ProGuard, make sure these constructors are not stripped (found in field 'entryDate')
尝试为日期时间字段设置默认值(包括系统当前时间、硬编码固定时间),问题仍未解决,尝试的代码如下:
data class TimesheetEntry( val id: Int = 0, val title: String = "", val category: String = "", val entryDate: LocalDate = LocalDate.now(), val startTime: LocalTime= LocalTime.now(), val endTime: LocalTime= LocalTime.now(), val imageBitmap: Bitmap? = null )
data class TimesheetEntry( val id: Int = 0, val title: String = "", val category: String = "", val entryDate: LocalDate = LocalDate.of(2023, 6, 9), val startTime: LocalTime = LocalTime.of(9, 0), val endTime: LocalTime = LocalTime.of(17, 30), val imageBitmap: Bitmap? = null )
问题原因
Firestore默认不支持Java 8的LocalDate、LocalTime等时间类的反序列化。即使给数据类字段设置了默认值,Firestore在解析文档时会尝试直接实例化LocalDate/LocalTime对象,但这些类本身没有无参构造函数,导致反序列化失败。
解决方案
方案1:自定义Firestore类型转换器(推荐)
为LocalDate和LocalTime分别编写转换器,让Firestore知道如何在数据库的Timestamp/字符串与Java时间类之间转换。
1. 编写LocalDate转换器
import com.google.firebase.Timestamp import com.google.firebase.firestore.PropertyConverter import java.time.LocalDate import java.time.ZoneId import java.util.Date class LocalDateConverter : PropertyConverter<LocalDate> { override fun toFirestore(property: LocalDate): Any { // 将LocalDate转为Firestore支持的Timestamp val instant = property.atStartOfDay(ZoneId.systemDefault()).toInstant() return Timestamp(Date.from(instant)) } override fun fromFirestore(value: Any): LocalDate { // 将Firestore的Timestamp转为LocalDate val timestamp = value as Timestamp return timestamp.toDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate() } }
2. 编写LocalTime转换器
import com.google.firebase.Timestamp import com.google.firebase.firestore.PropertyConverter import java.time.LocalTime import java.time.ZoneId import java.util.Date class LocalTimeConverter : PropertyConverter<LocalTime> { override fun toFirestore(property: LocalTime): Any { // 将LocalTime转为Timestamp(基于当前日期) val instant = LocalDate.now().atTime(property).atZone(ZoneId.systemDefault()).toInstant() return Timestamp(Date.from(instant)) } override fun fromFirestore(value: Any): LocalTime { // 将Timestamp转为LocalTime val timestamp = value as Timestamp return timestamp.toDate().toInstant().atZone(ZoneId.systemDefault()).toLocalTime() } }
3. 注册转换器到Firestore
在初始化Firestore时,全局注册这些转换器:
import com.google.firebase.firestore.FirebaseFirestore import com.google.firebase.firestore.FirebaseFirestoreSettings val firestore = FirebaseFirestore.getInstance() val settings = FirebaseFirestoreSettings.Builder() .setConverter(LocalDateConverter()) .setConverter(LocalTimeConverter()) .build() firestore.firestoreSettings = settings
或者在数据类字段上单独指定转换器:
data class TimesheetEntry( val id: Int = 0, val title: String = "", val category: String = "", @PropertyConverter(LocalDateConverter::class) val entryDate: LocalDate = LocalDate.now(), @PropertyConverter(LocalTimeConverter::class) val startTime: LocalTime = LocalTime.now(), @PropertyConverter(LocalTimeConverter::class) val endTime: LocalTime = LocalTime.now(), val imageBitmap: Bitmap? = null )
方案2:改用Firestore原生支持的类型
直接使用Firestore默认支持的类型存储日期时间,使用时再转换为LocalDate/LocalTime:
import com.google.firebase.Timestamp import java.time.LocalDate import java.time.LocalTime import java.time.ZoneId data class TimesheetEntry( val id: Int = 0, val title: String = "", val category: String = "", // 用Timestamp存储日期 val entryDate: Timestamp = Timestamp.now(), // 用字符串存储时间 val startTime: String = LocalTime.now().toString(), val endTime: String = LocalTime.now().toString(), val imageBitmap: Bitmap? = null ) // 使用时转换为LocalDate/LocalTime fun TimesheetEntry.getLocalEntryDate(): LocalDate { return entryDate.toDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDate() } fun TimesheetEntry.getLocalStartTime(): LocalTime { return LocalTime.parse(startTime) } fun TimesheetEntry.getLocalEndTime(): LocalTime { return LocalTime.parse(endTime) }
内容的提问来源于stack exchange,提问作者Pineapple_pizza
相关产品推荐
相关产品推荐

