我已经读了很多关于堆栈溢出的文章和其他博客文章,他们尝试了不同的解决方案来解决类似(* 但不相同 *)的问题。
我将按以下方式安排这一职位:
1.我的问题
1.我的代码(我认为相关的部分)
1.我想补救的是
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$14.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$1.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.read(ReflectiveTypeAdapterFactory.java:131)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$Adapter.read(ReflectiveTypeAdapterFactory.java:222)
at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory$1.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)。这些设置包括一些简单的内容,如球队赢得的最终分数、名称等。选择并保存设置后,应用程序将创建一个应用了这些设置的Game类对象,我把它们保存到一个房间数据库中,我的数据库表中的一个实体基本上包含一个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
)
之前,Game类不包含LiveData/MutableLiveData,但这导致必须将属性转换为ViewModel类中的LiveData/MutableLiveData,这导致了大量错误。*但它成功了!*我重构了Game类,因此它主要包含LiveData/MutableLiveData属性(我需要的是LiveData),因为在CustomGameFragment和它的ViewModel中,这将允许我简单地直接观察游戏的属性。但是在我重构了类之后,Gson无法再加载它。
我不确定这仅仅是因为我使用LiveData,他们不知何故需要上下文或viewLifeCylceOwner,他们在ViewModel或其他东西中隐式获得。
2.我的代码
a)房间数据库(包含存储库、实体、Dao、数据库)
实体:
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)游戏类
现在一个非常短的版本的游戏,因为大多数的方法并不真正相关的我的问题,我认为:
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类需要知道的参数来放置它(例如上下文),这是必要的。我没有,我想(?)。
我试了一下,当然没有成功。我也试了几种使用TypeToken的方法,我在开始的时候也展示过。
另一件事我经常读,是使用最新版本的包Gson,房间和LiveData或使用kapt instad的实现关键字在grandle.build在项目级别.我尝试了两个-〉相同的异常
你有什么想法吗?
还是我做错了什么蠢事?
提前感谢您的宝贵时间!
PS:我不是以英语为母语的人,所以很抱歉语法和拼写不好。
1条答案
按热度按时间sirbozc51#
以下显示了如何反序列化
LiveData
,但是在您的用例中,将Game
数据共享为ViewModel
可能更合适?请参阅Android开发人员页面。当没有自定义或内置类型适配器匹配时,Gson使用基于反射的适配器。问题是您要求Gson将JSON反序列化为
LiveData
。如果您查看LiveData
的source code,您会发现它有多个私有字段,并且对于其中一个的类型,Gson无法创建示例。一般来说,不鼓励对任何第三方类(这里是
LiveData
)使用Gson的基于反射的序列化或反序列化,因为这样会依赖于它们的内部实现细节,而这些细节随时都可能更改。这可以通过创建自定义
TypeAdapterFactory
来解决。我不熟悉Kotlin,但希望下面的Java代码作为示例对您有用:
(Note这并不保留
LiveData
的观察者)然后可以使用
GsonBuilder
创建Gson
示例并注册工厂:还原序列化
Game
时,不需要使用TypeToken
,直接使用类别也可以。TypeToken
是针对泛型型别。理想情况下,您还可以为
MutableList
创建一个TypeAdapterFactory
,以便不依赖于其内部实现。