realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.75k forks source link

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to io.realm.RealmModel #7814

Closed sharawanan closed 6 months ago

sharawanan commented 1 year ago

How frequently does the bug occur?

Always

Description

Using Retrofit 2.9.0 and AGP 8.0.1

java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to io.realm.RealmModel at io.realm.DefaultRealmModuleMediator.insertOrUpdate(SourceFile:7121) at io.realm.Realm.insertOrUpdate(SourceFile:1367)

Stacktrace & log output

No response

Can you reproduce the bug?

Always

Reproduction Steps

No response

Version

10.15.1

What Atlas App Services are you using?

Local Database only

Are you using encryption?

No

Platform OS and version(s)

Android

Build environment

Android Studio version: ... Android Build Tools version: ... Gradle version: ...

clementetb commented 1 year ago

From the exception, it seems you are trying to insert an instance that is not a RealmModel but a LinkedTreeMap.

Could you check that you are deserializing to the correct object type?

sharawanan commented 1 year ago

The same code works on Retrofit 2.9.0 and AGP 7.4.1

rorbech commented 1 year ago

@sharawanan Can you elaborate a bit on how your model looks like?

sharawanan commented 1 year ago
open class UserRoleMenus : RealmObject() {
    @PrimaryKey
    @Index
    var id = 0
    var name: String? = null
    var parent: String? = null
    var actions: UserRoleMenuActions? = null
    var components: RealmList<UserRoleComponents>? = null

}

open class UserRoleMenuActions : RealmObject() {
    var show: Boolean = false
    var view: Boolean = false
    var add: Boolean = false
    var edit: Boolean = false
    var delete: Boolean = false

}

open class UserRoleComponents : RealmObject() {
    var name: String? = null
    var show: Boolean = false

}
 private var userRoleMenusList: List<UserRoleMenus>? = emptyList()

 val asyncTracker: SafeAsyncTask<Boolean> = object : SafeAsyncTask<Boolean>() {
            @Throws(java.lang.Exception::class)
            override fun call(): Boolean {
                val response: Response<UserRoleWrapper?> =
                    serviceProvider.getService(getActivity).getUserRole(userId)!!.execute()
                return if (response.isSuccessful && response.body() != null) {
                    if (response.body()!!.success != null && response.body()?.userRoleMenus?.size!! > 0) {
                        userRoleMenusList = response.body()!!.userRoleMenus
                        saveuserRoleMenu()
                        true
                    } else {
                        if (response.body() != null && response.body()!!.error != null) {
                            Toaster.showShort(getActivity, "" + response.body()!!.error)
                        }
                        false
                    }
                } else false
            }

            @Throws(RuntimeException::class)
            override fun onException(e: java.lang.Exception) {
                e.printStackTrace()
                if (e !is IOException) {
                    val cause = if (e.cause != null) e.cause else e
                    Toaster.showLong(getActivity, cause!!.message)
                }
                apiView.onException()
            }

            @Throws(Exception::class)
            override fun onSuccess(resSuccess: Boolean?) {
                if (resSuccess!!) {
                    apiView.onSuccess()
                } else {
                    apiView.onException()
                }
            }

            override fun onFinally() {

            }
        }
        asyncTracker.execute()
 fun saveuserRoleMenu()
 {

            Realm.getDefaultInstance().use { realmT ->
            val results = realmT.where<UserRoleMenus>().findAll()
            val results1 = realmT.where<UserRoleMenuActions>().findAll()
            val results2 = realmT.where<UserRoleComponents>().findAll()

            realmT.executeTransaction { results.deleteAllFromRealm() }
            realmT.executeTransaction { results1.deleteAllFromRealm() }
            realmT.executeTransaction { results2.deleteAllFromRealm() }
            realmT.executeTransaction { realm: Realm ->
                realm.insertOrUpdate(
                    userRoleMenusList
                )
            }
        }
 }
rorbech commented 1 year ago

@sharawanan There is nothing in the model definition that catches the eye. From the stacktrace it seems like you are invoking the insertOrUpdate(Collection<? extends RealmModel>)-variant of insertOrUpdate so I assume your userRoleMenusList is just a list of UserRoleMenus. In that case it looks like our plugin might not have been applied.

Is this code in your main source set or is it test/integration test code or are there some specials about your configuration? Also just to be completely explicit and help troubleshooting, what is the exact type of userRoleMenusList?

sharawanan commented 1 year ago

@rorbech I've updated full code

rorbech commented 1 year ago

It is a bit difficult to hunt without the ability to debug, but there are a couple of concerns:

Could you debug and inspect that the userRoleMenusList is in fact a non-null UserRoleMenus-collection when you see the error?

DearDhruv commented 1 year ago

It is not a Realm issue.

It is related to gson due to Type Erasure.

I have the same issue with my simple custom class, I am keeping my androidGradlePlugin 7.4.2 for now.

rorbech commented 11 months ago

@sharawanan Have you been able to trace down what the root of this was? If not please share more details or a minimal project that shows the issue for us to try to debug it.

github-actions[bot] commented 10 months ago

This issue has been automatically closed because there has been no response to our request for more information from the original author. With only the information that is currently in the issue, we don't have enough information to take action. Please reach out if you have or find the answers we need so that we can investigate further.

JacobCube commented 9 months ago

Same issue here with AGP 8.0.2. The issue gets resolved when downgrading to 7.4.2 with no code change.

In my case, I'm using a Room database with the item in List having it's own table. It's also a variable in a parent object, which deserializes correctly outside of this variable, which throws an error only on using it (not when converting it - therefore runtime only).

Here's my logic for parsing:

@TypeConverter fun toQuestionAnswerIOList(value: String): List<QuestionAnswerIO> { return gson.fromJson( value, TypeToken.getParameterized(List::class.java, QuestionAnswerIO::class.java).type ) }

(gson is injected by Hilt)

and here's a definition for CollectionIO object:

@Entity(tableName = AppRoomDatabase.ROOM_COLLECTION_TABLE) data class CollectionIO(

val questions: MutableList<QuestionIO> = mutableListOf(),

@SerializedName("default_preference")
val defaultPreference: UserPromptPreferences = UserPromptPreferences(),

var name: String = "",

var description: String = "",

val icon: LargePathAsset? = null,

@SerializedName("date_created")
@ColumnInfo(name = "date_created")
var dateCreated: Date? = null,

@SerializedName("date_modified")
@ColumnInfo(name = "date_modified")
var dateModified: Date? = DateUtils.now.time,

@PrimaryKey
val uid: String = UUID.randomUUID().toString()

): Serializable {

/** Checks whether object contains any non-default data */
val isNotEmpty: Boolean
    get() = dateCreated != null
        || name.isNotEmpty()
        || description.isNotEmpty()
        || questions.isNotEmpty()

override fun toString(): String {
    return "{" +
        "questions: $questions" +
        "defaultPreference: $defaultPreference" +
        "name: $name" +
        "description: $description" +
        "icon: $icon" +
        "dateCreated: $dateCreated" +
        "dateModified: $dateModified," +
        "uid: $uid," +
        "task: $tasks" +
        "}"
}

}`

sharawanan commented 9 months ago

Add this line in proguard file

JsonSerializer, JsonDeserializer instances (so they can be used in @JsonAdapter)

-keep class * implements com.google.gson.TypeAdapterFactory
 -keep class * implements com.google.gson.JsonSerializer
 -keep class * implements com.google.gson.JsonDeserializer

 -keepattributes Signature
 -keep class com.google.gson.reflect.TypeToken
 -keep class * extends com.google.gson.reflect.TypeToken
 -keep public class * implements java.lang.reflect.Type
 -keepclassmembers,allowobfuscation class * {
  @com.google.gson.annotations.SerializedName <fields>;
 }

gradle.properties

android.enableJetifier=true
android.useAndroidX=true
android.debug.obsoleteApi=true
org.gradle.configureondemand=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx6656M
org.gradle.warning.mode=all
kotlin.mpp.stability.nowarn=true
kotlin.mpp.androidSourceSetLayoutVersion=2
android.defaults.buildfeatures.buildconfig=true
android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.enableR8.fullMode=false
JacobCube commented 9 months ago

This seems to solve the issue. Thank you.

github-actions[bot] commented 6 months ago

This issue has been automatically closed because there has been no response to our request for more information from the original author. With only the information that is currently in the issue, we don't have enough information to take action. Please reach out if you have or find the answers we need so that we can investigate further.