androidkotlinserializationsupabase

Serializer for class 'Any' is not found. Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied


I'm encountering the following error:

Serializer for class 'Any' is not found.  
Please ensure that class is marked as '@Serializable' and that the serialization compiler plugin is applied.

I tried the solution mentioned here, but it didn't work for me.

Here’s the code where the error occurs:

class UserAuthentication {

    private val supabase = createSupabaseClient(
        supabaseKey = "MySupabaseKey",
        supabaseUrl = "MySupabaseUrl"
    ) {
        install(io.github.jan.supabase.auth.Auth)
        install(Postgrest)
    }

    private lateinit var credentialManager: CredentialManager

    fun initializeCredentialManager(context: Context) {
        credentialManager = CredentialManager.create(context)

        val rawNonce = UUID.randomUUID().toString()
        val bytes = rawNonce.toByteArray()
        val messageDigest = MessageDigest.getInstance("SHA-256")
        val digest = messageDigest.digest(bytes)
        val hashedNonce = digest.fold("") { str, it -> str + "%02x".format(it) }

        val googleIdOption: GetGoogleIdOption =
            GetGoogleIdOption.Builder().setFilterByAuthorizedAccounts(false)
                .setServerClientId("MyServerClientID")
                .setNonce(hashedNonce).build()

        val credentialRequest: GetCredentialRequest =
            GetCredentialRequest.Builder().addCredentialOption(googleIdOption).build()

        GlobalScope.launch {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
                try {
                    val result = credentialManager.getCredential(
                        request = credentialRequest,
                        context = context,
                    )
                    val credential = result.credential
                    val googleIdTokenCredential =
                        GoogleIdTokenCredential.createFrom(credential.data)
                    val googleIdToken = googleIdTokenCredential.idToken

                    supabase.auth.signInWith(IDToken) {
                        idToken = googleIdToken
                        provider = Google
                        nonce = rawNonce
                    }
                    // Retrieve the user_id from Supabase
                    val session = supabase.auth.currentSessionOrNull()

                    // Extract user details from credential
                    val email = googleIdTokenCredential.id
                    val userName = googleIdTokenCredential.displayName
                    val userId = session?.user?.id // This is the Supabase UUID


                    // Check if the user exists in Supabase and add if not
                    handleUserAuthentication(context,userId, userName, email)

                } catch (e: Exception) {
                    withContext(Dispatchers.Main) {
                        Log.d("Error initializeCredentialManager", e.message.toString())
                        Toast.makeText(context, "Error:-"+e.message, Toast.LENGTH_SHORT).show()
                    }
                }
            }
        }
    }

    private suspend fun handleUserAuthentication(
        context: Context,
        userId: String?,
        userName: String?,
        email: String?
    ) {
        if (userId == null || userName == null) {
            withContext(Dispatchers.Main) {
                Toast.makeText(context, "User ID or Username is missing!", Toast.LENGTH_SHORT).show()
            }
            return
        }

        try {
            // Check if the user exists
            val userExists = withContext(Dispatchers.IO) {
                supabase.from("Table_User_Data")
                    .select { filter { eq("user_id", userId) } }
                    .decodeList<Map<String, Any>>()
                    .isNotEmpty()
            }

            // If user does not exist, insert a new record
            if (!userExists) {
                val newUser = mapOf(
                    "user_id" to userId,
                    "UserName" to userName,
                    "EmailID" to email,
                    "LatestScanDate" to null,
                    "LatestPurchaseTokensTimestamp" to null,
                    "LatestPurchaseToken" to null,
                    "SubscriptionActive" to false
                )

                val insertResult = withContext(Dispatchers.IO) {
                    supabase.from("Table_User_Data")
                        .insert(newUser) {
                            select() // This ensures that the inserted data is returned
                        }
                        .decodeSingle<Map<String, Any>>() // Decodes the result into the expected type to confirm success
                }


                // Notify user of successful insertion
                withContext(Dispatchers.Main) {
                    Toast.makeText(context, "New user added successfully!", Toast.LENGTH_SHORT).show()
                }
            } else {
                withContext(Dispatchers.Main) {
                    Toast.makeText(context, "User already exists!", Toast.LENGTH_SHORT).show()
                }
            }
        }  catch (e: Exception) {
            // Handle exceptions
            withContext(Dispatchers.Main) {
                Log.d("Error handleUserAuthentication", e.message.toString())
                Toast.makeText(context, "Error: ${e.message}", Toast.LENGTH_SHORT).show()
            }
        }
    }
}

Additional Context:

Any suggestions on how to resolve this issue?

EDIT:- When i debug, this line is returning the code to catch block and showing me the error

 .decodeList<Map<String, Any>>()

Solution

  • I resolved the issue by creating a data class for the user data and marking it with the @Serializable annotation from Kotlinx Serialization:

    import kotlinx.serialization.Serializable
    
    @Serializable
    data class UserData(
        val user_id: String,
        val UserName: String?,
        val EmailID: String?,
        val LatestScanDate: String?,
        val LatestPurchaseTokensTimestamp: String?,
        val LatestPurchaseToken: String?,
        val SubscriptionActive: Boolean
    )
    

    Then, I updated the code to use this UserData class in the decoding operation:

    // Check if the user exists
    val userExists = withContext(Dispatchers.IO) {
        supabase.from("Table_User_Data")
            .select { filter { eq("user_id", userId) } }
            .decodeList<UserData>() // Specify UserData as the type
            .isNotEmpty()
    }
    

    This ensures the decodeList function knows the type it's working with, resolving the error.