You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为含日期时间字段的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 17:44:54