Gson 无法在我的 Android & Kotlin 项目中为观察者 (LiveData) 调用无参数构造器
Gson unbable to invoke no-args construktor for observer ( LiveData) in my Android & Kotlin project
我已经阅读了很多 stackoverflow 文章和其他博客 posts 关于尝试不同解决方案的类似(但不是同样) 他们在那里遇到的问题。
我将 post 结构如下:
- 我的问题
- 我的代码(我认为相关的部分)
- 我尝试解决的问题
1。我的问题
我收到以下错误消息:
Process: com.myapp, PID: 23553
java.lang.RuntimeException: Unable to invoke no-args constructor for androidx.arch.core.internal.SafeIterableMap$SupportRemove<androidx.lifecycle.Observer<? super java.lang.Integer>, androidx.lifecycle.LiveData$ObserverWrapper>. Registering an InstanceCreator with Gson for this type may fix this problem.
at com.google.gson.internal.ConstructorConstructor.construct(ConstructorConstructor.java:228)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:186)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.Gson.fromJson(Gson.java:932)
at com.google.gson.Gson.fromJson(Gson.java:897)
at com.google.gson.Gson.fromJson(Gson.java:846)
at com.myapp.ui.customGame.CustomGameViewModel.initialize(CustomGameViewModel.kt:46)
我要实现的目标的描述:
我正在编写一个应用程序作为学习 Kotlin 编程和制作应用程序的项目,我对制作应用程序和 Kotlin 比较陌生,如果您看到我犯了愚蠢的错误,请记住这一点; ).
在我的应用程序中,我有一个 activity,其中包含一个片段,可让您选择排球游戏的设置(CustomGameSetupFragment)。设置包括一些简单的事情,例如团队获胜的最终分数、名称等。选择并保存设置后,应用程序会创建一个应用了设置的游戏 class 对象,并将它们保存到 Room 数据库中.我的数据库 table 中的实体基本上包含一个 ID、一些其他信息和游戏对象的 JSON 字符串(通过 Google 的 Gson 包创建)。然后 activity 将该片段替换为允许您计算游戏分数并查看名称和内容的片段(CustomGameFragment)。新片段创建一个 ViewModel 对象,然后再次从数据库中读取游戏,选择最后保存的实体,然后尝试从保存的 JSON 字符串重新创建游戏对象。
这是通过执行:
val gameType = object: TypeToken<Game>() {}.type
this.game = Gson().fromJson<Game>(
gameRepo.ongoingGameData[0].gameJson,
gameType
//Game::class.java // this was an other method I tried. Also didnt work
)
之前,游戏 class 不包含 LiveData/MutableLiveData,但这导致必须在 ViewModel class 和 [=62= 中将属性转换为 LiveData/MutableLiveData ]that 导致了很多错误。 但它奏效了!。我重构了游戏 class,所以它主要是 LiveData/MutableLiveData 属性(我需要的属性是 LiveData),因为在 CustomGameFragment 及其 ViewModel 中,我可以直接简单地观察游戏的属性。但是在我重构了class之后,Gson无法再加载它了。
我不确定这仅仅是因为我使用了 LiveData,而且他们不知何故需要他们在 ViewModel 或其他东西中隐式获得的上下文或 viewLifeCylceOwner。
2。我的代码
a) Room 数据库(包含 Repository、Entity、Dao、Database)
实体:
package com.myapp.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "gameData_table")
data class GameData(
@PrimaryKey(autoGenerate = true) val id: Int?,
@ColumnInfo(name = "gid") val gid: String?,
@ColumnInfo(name = "is_over") val isOver : Boolean?,
@ColumnInfo(name = "team1_name") val team1Name: String?,
@ColumnInfo(name = "team2_name") val team2Name: String?,
@ColumnInfo(name = "team1_sets") val team1Sets: Int?,
@ColumnInfo(name = "team2_sets") val team2Sets: Int?,
@ColumnInfo(name = "total_sets") val totalSets: Int?,
@ColumnInfo(name = "game_json") val gameJson : String?
)
道:
package com.myapp.data
import androidx.room.*
@Dao
interface GameDao {
@Query("SELECT * FROM gameData_table")
suspend fun getAll(): List<GameData>
@Query("SELECT * FROM gameData_table WHERE id = (:id)")
fun loadAllByIds(id: Array<Int>): List<GameData>
@Query("SELECT * FROM gameData_table WHERE is_over = 0")
suspend fun getOngoing() : List<GameData>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(vararg game: GameData)
@Delete
suspend fun delete(game: GameData)
@Query("DELETE FROM gameData_table WHERE is_over = 0")
suspend fun deleteOngoing()
@Query("UPDATE gameData_table SET game_json = (:json) WHERE gid = (:gameId)")
suspend fun updateJSON(json: String, gameId : String)
}
数据库:
package com.myapp.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [GameData::class], version = 1, exportSchema = false)
abstract class GameDatabase : RoomDatabase() {
abstract fun gameDao() : GameDao
companion object {
//Singleton pattern to prevent multiple instances of the database
@Volatile
private var INSTANCE: GameDatabase? = null
fun getDatabase(context: Context) : GameDatabase {
return INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
GameDatabase::class.java,
"game_database"
).build()
INSTANCE = instance
return instance
}
}
}
存储库:
package com.myapp.data
import kotlinx.coroutines.runBlocking
class GameRepository (private val gameDao: GameDao){
val allGameData: List<GameData> = runBlocking { gameDao.getAll()}
val ongoingGameData : List<GameData> = runBlocking { gameDao.getOngoing() }
suspend fun insert(gameData : GameData){
gameDao.insertAll(gameData)
}
suspend fun deleteOngoing() {
gameDao.deleteOngoing()
}
suspend fun updateGame(gameData : GameData){
gameDao.updateJSON(gameData.gameJson!!, gameData.gid!!)
}
}
b) 游戏 class
现在是一个非常简短的游戏版本,因为大多数方法与我的问题并不相关我认为:
package com.myapp.game
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.myapp.data.GameData
import com.myapp.values.Values
import com.google.gson.Gson
class Game {
/*
No live data needed or possible?
*/
private var sets : MutableList<Set>
private val pointGap : Int = Values.DEFAULT_POINT_GAP
private val gid : String = this.toString()
/*
Live data needed or possible
*/
// private MutableLiveData
private var _team1Name : MutableLiveData<String> = MutableLiveData(Values.DEFAULT_TEAM1_NAME)
(more strings ...)
private var _setWinScore : MutableLiveData<Int> = MutableLiveData(Values.DEFAULT_WIN_SCORE)
(...)
// public LiveData
val team1Name : LiveData<String>
(more strings ...)
val setWinScore : LiveData<Int>
(...)
init{
team1Name = _team1Name
(more strings ...)
setWinScore = _setWinScore
(...)
}
constructor(gameSettings: GameSettings = GameSettings()){
this._team1Name.value = gameSettings.team1Name
(more strings...)
this._setWinScore.value = gameSettings.setWinScore
(...)
}
}
3。修复方法#
我尝试使用 InstanceCreator。但是在我读了一些关于它的东西之后,我发现如果你想重新创建的对象有一个 Gson class 需要知道的东西的参数(例如上下文),这是必要的。我没有那个,我想 (?).
无论如何我都试过了,这当然没有用。
我还尝试了几种使用 TypeToken 的变体,我也在开头展示了这些变体。
我经常阅读的另一件事是使用最新版本的 Gson、Room 和 LiveData 包,或者在项目级别的 grandle.build 中使用 kapt instad 实现关键字。
我都试过了 -> 相同的异常
那么,你有什么想法吗?
还是我做错了什么蠢事?
在此先感谢您牺牲您的时间!
PS:我不是英语母语人士,对于语法和拼写错误感到抱歉。
下面显示了如何反序列化 LiveData
,但是在您的用例中,将 Game
数据共享为 ViewModel
可能更合适?参见 Android Developers page。
当没有自定义或内置类型适配器匹配时,Gson 使用基于反射的适配器。问题是您要求 Gson 将 JSON 反序列化为 LiveData
。如果您查看 LiveData
的 source code,您会发现它有多个私有字段,并且对于其中之一的类型,Gson 无法创建实例。
一般来说,不鼓励对任何第三方 classes(此处 LiveData
)使用 Gson 基于反射的序列化或反序列化,因为您随后依赖于它们的内部实现细节,这些细节可能会在任何一点。
这可以通过创建自定义 TypeAdapterFactory
.
来解决
我不熟悉 Kotlin,但希望以下 Java 代码对您有用,例如:
class LiveDataTypeAdapterFactory implements TypeAdapterFactory {
public static final LiveDataTypeAdapterFactory INSTANCE = new LiveDataTypeAdapterFactory();
private LiveDataTypeAdapterFactory() { }
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<?> rawType = type.getRawType();
// Only handle LiveData and MutableLiveData
if (rawType != LiveData.class && rawType != MutableLiveData.class) {
return null;
}
// Assumes that LiveData is never used as raw type but is always parameterized
Type valueType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
// Get the adapter for the LiveData value type `T`
// Cast TypeAdapter to simplify adapter code below
@SuppressWarnings("unchecked")
TypeAdapter<Object> valueAdapter = (TypeAdapter<Object>) gson.getAdapter(TypeToken.get(valueType));
// Is safe due to `type` check at start of method
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<LiveData<?>>() {
@Override
public void write(JsonWriter out, LiveData<?> liveData) throws IOException {
// Directly write out LiveData value
valueAdapter.write(out, liveData.getValue());
}
@Override
public LiveData<?> read(JsonReader in) throws IOException {
Object value = valueAdapter.read(in);
return new MutableLiveData<>(value);
}
};
return adapter;
}
}
(注意这不保留LiveData
的观察者)
然后您可以使用 GsonBuilder
创建 Gson
实例并注册工厂:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(LiveDataTypeAdapterFactory.INSTANCE)
.create();
反序列化Game
时不需要使用TypeToken
,直接使用class也可以。 TypeToken
适用于通用类型。
理想情况下,您还可以为 MutableList
创建一个 TypeAdapterFactory
,而不依赖其内部实现。
我已经阅读了很多 stackoverflow 文章和其他博客 posts 关于尝试不同解决方案的类似(但不是同样) 他们在那里遇到的问题。
我将 post 结构如下:
- 我的问题
- 我的代码(我认为相关的部分)
- 我尝试解决的问题
1。我的问题
我收到以下错误消息:
Process: com.myapp, PID: 23553
java.lang.RuntimeException: Unable to invoke no-args constructor for androidx.arch.core.internal.SafeIterableMap$SupportRemove<androidx.lifecycle.Observer<? super java.lang.Integer>, androidx.lifecycle.LiveData$ObserverWrapper>. Registering an InstanceCreator with Gson for this type may fix this problem.
at com.google.gson.internal.ConstructorConstructor.construct(ConstructorConstructor.java:228)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:212)
at com.google.gson.internal.bind.TypeAdapterRuntimeTypeWrapper.read(TypeAdapterRuntimeTypeWrapper.java:41)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:186)
at com.google.gson.internal.bind.MapTypeAdapterFactory$Adapter.read(MapTypeAdapterFactory.java:145)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.Gson.fromJson(Gson.java:932)
at com.google.gson.Gson.fromJson(Gson.java:897)
at com.google.gson.Gson.fromJson(Gson.java:846)
at com.myapp.ui.customGame.CustomGameViewModel.initialize(CustomGameViewModel.kt:46)
我要实现的目标的描述:
我正在编写一个应用程序作为学习 Kotlin 编程和制作应用程序的项目,我对制作应用程序和 Kotlin 比较陌生,如果您看到我犯了愚蠢的错误,请记住这一点; ). 在我的应用程序中,我有一个 activity,其中包含一个片段,可让您选择排球游戏的设置(CustomGameSetupFragment)。设置包括一些简单的事情,例如团队获胜的最终分数、名称等。选择并保存设置后,应用程序会创建一个应用了设置的游戏 class 对象,并将它们保存到 Room 数据库中.我的数据库 table 中的实体基本上包含一个 ID、一些其他信息和游戏对象的 JSON 字符串(通过 Google 的 Gson 包创建)。然后 activity 将该片段替换为允许您计算游戏分数并查看名称和内容的片段(CustomGameFragment)。新片段创建一个 ViewModel 对象,然后再次从数据库中读取游戏,选择最后保存的实体,然后尝试从保存的 JSON 字符串重新创建游戏对象。 这是通过执行:
val gameType = object: TypeToken<Game>() {}.type
this.game = Gson().fromJson<Game>(
gameRepo.ongoingGameData[0].gameJson,
gameType
//Game::class.java // this was an other method I tried. Also didnt work
)
之前,游戏 class 不包含 LiveData/MutableLiveData,但这导致必须在 ViewModel class 和 [=62= 中将属性转换为 LiveData/MutableLiveData ]that 导致了很多错误。 但它奏效了!。我重构了游戏 class,所以它主要是 LiveData/MutableLiveData 属性(我需要的属性是 LiveData),因为在 CustomGameFragment 及其 ViewModel 中,我可以直接简单地观察游戏的属性。但是在我重构了class之后,Gson无法再加载它了。
我不确定这仅仅是因为我使用了 LiveData,而且他们不知何故需要他们在 ViewModel 或其他东西中隐式获得的上下文或 viewLifeCylceOwner。
2。我的代码
a) Room 数据库(包含 Repository、Entity、Dao、Database)
实体:
package com.myapp.data
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "gameData_table")
data class GameData(
@PrimaryKey(autoGenerate = true) val id: Int?,
@ColumnInfo(name = "gid") val gid: String?,
@ColumnInfo(name = "is_over") val isOver : Boolean?,
@ColumnInfo(name = "team1_name") val team1Name: String?,
@ColumnInfo(name = "team2_name") val team2Name: String?,
@ColumnInfo(name = "team1_sets") val team1Sets: Int?,
@ColumnInfo(name = "team2_sets") val team2Sets: Int?,
@ColumnInfo(name = "total_sets") val totalSets: Int?,
@ColumnInfo(name = "game_json") val gameJson : String?
)
道:
package com.myapp.data
import androidx.room.*
@Dao
interface GameDao {
@Query("SELECT * FROM gameData_table")
suspend fun getAll(): List<GameData>
@Query("SELECT * FROM gameData_table WHERE id = (:id)")
fun loadAllByIds(id: Array<Int>): List<GameData>
@Query("SELECT * FROM gameData_table WHERE is_over = 0")
suspend fun getOngoing() : List<GameData>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(vararg game: GameData)
@Delete
suspend fun delete(game: GameData)
@Query("DELETE FROM gameData_table WHERE is_over = 0")
suspend fun deleteOngoing()
@Query("UPDATE gameData_table SET game_json = (:json) WHERE gid = (:gameId)")
suspend fun updateJSON(json: String, gameId : String)
}
数据库:
package com.myapp.data
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
@Database(entities = [GameData::class], version = 1, exportSchema = false)
abstract class GameDatabase : RoomDatabase() {
abstract fun gameDao() : GameDao
companion object {
//Singleton pattern to prevent multiple instances of the database
@Volatile
private var INSTANCE: GameDatabase? = null
fun getDatabase(context: Context) : GameDatabase {
return INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(
context.applicationContext,
GameDatabase::class.java,
"game_database"
).build()
INSTANCE = instance
return instance
}
}
}
存储库:
package com.myapp.data
import kotlinx.coroutines.runBlocking
class GameRepository (private val gameDao: GameDao){
val allGameData: List<GameData> = runBlocking { gameDao.getAll()}
val ongoingGameData : List<GameData> = runBlocking { gameDao.getOngoing() }
suspend fun insert(gameData : GameData){
gameDao.insertAll(gameData)
}
suspend fun deleteOngoing() {
gameDao.deleteOngoing()
}
suspend fun updateGame(gameData : GameData){
gameDao.updateJSON(gameData.gameJson!!, gameData.gid!!)
}
}
b) 游戏 class
现在是一个非常简短的游戏版本,因为大多数方法与我的问题并不相关我认为:
package com.myapp.game
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.myapp.data.GameData
import com.myapp.values.Values
import com.google.gson.Gson
class Game {
/*
No live data needed or possible?
*/
private var sets : MutableList<Set>
private val pointGap : Int = Values.DEFAULT_POINT_GAP
private val gid : String = this.toString()
/*
Live data needed or possible
*/
// private MutableLiveData
private var _team1Name : MutableLiveData<String> = MutableLiveData(Values.DEFAULT_TEAM1_NAME)
(more strings ...)
private var _setWinScore : MutableLiveData<Int> = MutableLiveData(Values.DEFAULT_WIN_SCORE)
(...)
// public LiveData
val team1Name : LiveData<String>
(more strings ...)
val setWinScore : LiveData<Int>
(...)
init{
team1Name = _team1Name
(more strings ...)
setWinScore = _setWinScore
(...)
}
constructor(gameSettings: GameSettings = GameSettings()){
this._team1Name.value = gameSettings.team1Name
(more strings...)
this._setWinScore.value = gameSettings.setWinScore
(...)
}
}
3。修复方法#
我尝试使用 InstanceCreator。但是在我读了一些关于它的东西之后,我发现如果你想重新创建的对象有一个 Gson class 需要知道的东西的参数(例如上下文),这是必要的。我没有那个,我想 (?).
无论如何我都试过了,这当然没有用。
我还尝试了几种使用 TypeToken 的变体,我也在开头展示了这些变体。
我经常阅读的另一件事是使用最新版本的 Gson、Room 和 LiveData 包,或者在项目级别的 grandle.build 中使用 kapt instad 实现关键字。 我都试过了 -> 相同的异常
那么,你有什么想法吗?
还是我做错了什么蠢事?
在此先感谢您牺牲您的时间!
PS:我不是英语母语人士,对于语法和拼写错误感到抱歉。
下面显示了如何反序列化 LiveData
,但是在您的用例中,将 Game
数据共享为 ViewModel
可能更合适?参见 Android Developers page。
当没有自定义或内置类型适配器匹配时,Gson 使用基于反射的适配器。问题是您要求 Gson 将 JSON 反序列化为 LiveData
。如果您查看 LiveData
的 source code,您会发现它有多个私有字段,并且对于其中之一的类型,Gson 无法创建实例。
一般来说,不鼓励对任何第三方 classes(此处 LiveData
)使用 Gson 基于反射的序列化或反序列化,因为您随后依赖于它们的内部实现细节,这些细节可能会在任何一点。
这可以通过创建自定义 TypeAdapterFactory
.
来解决
我不熟悉 Kotlin,但希望以下 Java 代码对您有用,例如:
class LiveDataTypeAdapterFactory implements TypeAdapterFactory {
public static final LiveDataTypeAdapterFactory INSTANCE = new LiveDataTypeAdapterFactory();
private LiveDataTypeAdapterFactory() { }
@Override
public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
Class<?> rawType = type.getRawType();
// Only handle LiveData and MutableLiveData
if (rawType != LiveData.class && rawType != MutableLiveData.class) {
return null;
}
// Assumes that LiveData is never used as raw type but is always parameterized
Type valueType = ((ParameterizedType) type.getType()).getActualTypeArguments()[0];
// Get the adapter for the LiveData value type `T`
// Cast TypeAdapter to simplify adapter code below
@SuppressWarnings("unchecked")
TypeAdapter<Object> valueAdapter = (TypeAdapter<Object>) gson.getAdapter(TypeToken.get(valueType));
// Is safe due to `type` check at start of method
@SuppressWarnings("unchecked")
TypeAdapter<T> adapter = (TypeAdapter<T>) new TypeAdapter<LiveData<?>>() {
@Override
public void write(JsonWriter out, LiveData<?> liveData) throws IOException {
// Directly write out LiveData value
valueAdapter.write(out, liveData.getValue());
}
@Override
public LiveData<?> read(JsonReader in) throws IOException {
Object value = valueAdapter.read(in);
return new MutableLiveData<>(value);
}
};
return adapter;
}
}
(注意这不保留LiveData
的观察者)
然后您可以使用 GsonBuilder
创建 Gson
实例并注册工厂:
Gson gson = new GsonBuilder()
.registerTypeAdapterFactory(LiveDataTypeAdapterFactory.INSTANCE)
.create();
反序列化Game
时不需要使用TypeToken
,直接使用class也可以。 TypeToken
适用于通用类型。
理想情况下,您还可以为 MutableList
创建一个 TypeAdapterFactory
,而不依赖其内部实现。