2

I am trying to use Ktor and Kotlinx Serialization to pull some dummy post data form jsonplaceholder.typicode.com (here) and deserialize the array. However, I get the following error:

Error:Expected class kotlinx.serialization.json.JsonObject (Kotlin reflection is not available) as the serialized body of kotlinx.serialization.Polymorphic<List>, but had class kotlinx.serialization.json.JsonArray (Kotlin reflection is not available)

Where in my code am I specifying that I am expecting the data as JsonObjects and not JsonArrays? This is likely the error, but I don't see where in the code I specify this.

Thanks in advance for the help.

Relevant code is posted here:

LoginRegisterFragment.kt

package com.example.groupupandroid

import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope
import androidx.navigation.Navigation
import androidx.navigation.fragment.findNavController
import com.example.groupupandroid.databinding.ActivityMainBinding
import com.example.groupupandroid.databinding.FragmentLoginRegisterBinding
import data.remote.PostResponse
import data.remote.PostsService
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.launch

class LoginRegisterFragment : Fragment(){
    // Getting xml objects
    private var binding: FragmentLoginRegisterBinding? = null
    // Creating service for networking
    private lateinit var service: PostsService

    private var posts: List<PostResponse> = emptyList()

    //@Composable
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentLoginRegisterBinding.inflate(layoutInflater)

        lifecycleScope.launch {getPosts()}

        // Inflate the layout for this fragment
        return binding?.root
    }

    private suspend fun getPosts() {
        service = PostsService.create()
        posts = service.getPosts()
        print(posts)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

        // If register button is tapped make register visible
        binding?.registerToggleButton?.setOnClickListener {
            binding?.registerFields?.visibility = View.VISIBLE
            binding?.loginFields?.visibility = View.GONE
        }

        // If login button is tapped make login visible
        binding?.loginToggleButton?.setOnClickListener {
            binding?.registerFields?.visibility = View.GONE
            binding?.loginFields?.visibility = View.VISIBLE
        }

        // If login button is pushed swap to maps
        binding?.loginButton?.setOnClickListener {
            findNavController().navigate(R.id.loginToHomeScreen)
        }

        // If register button is pushed swap to maps
        binding?.registerButton?.setOnClickListener {
            findNavController().navigate(R.id.loginToHomeScreen)
        }

        binding?.materialButtonToggleGroup?.check(binding?.loginToggleButton!!.id)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        binding = null
    }
}

PostResponse.kt

package data.remote

import kotlinx.serialization.Serializable

@Serializable
data class PostResponse (
    val body: String,
    val title: String,
    val id: Int,
    val userId: Int
)

PostsServiceImplementation.kt

package data.remote

import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import io.ktor.util.*
import java.lang.Exception

class PostsServiceImplementation(private val client: HttpClient): PostsService
 {
    override suspend fun getPosts(): List<PostResponse> {
        return try {
            client.get {url(HttpRoutes.POSTS)}.body()
        } catch(e: Exception){
            println("Error:${e.message}")
            emptyList()
        }
    }

    override suspend fun createPosts(postRequest: PostRequest): PostResponse? {
        return try {
            client.post {
                url(HttpRoutes.POSTS)
                contentType(ContentType.Application.Json)
                setBody(postRequest)
            }.body()
        } catch(e: Exception) {
            println("Error:${e.message}")
            null
        }
    }
}

Edit: More relevant code.

HttpRoutes.kt

package data.remote

object HttpRoutes {

    private const val BASE_URL = "https://jsonplaceholder.typicode.com"

    const val POSTS = "$BASE_URL/posts"
}

PostsService.kt

package data.remote

import io.ktor.client.*
import io.ktor.client.engine.android.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.plugins.kotlinx.serializer.*
import io.ktor.client.plugins.logging.*
import io.ktor.client.statement.*
import io.ktor.serialization.kotlinx.json.*

interface PostsService {

    suspend fun getPosts(): List<PostResponse>

    suspend fun createPosts(postRequest: PostRequest): PostResponse?

    companion object {
        fun create():PostsService {
            return PostsServiceImplementation (
                client = HttpClient(Android) {
                    install(Logging) {
                        level = LogLevel.ALL
                    }
                    install(ContentNegotiation) {
                        json()
                    }
                }
            )
        }
    }
}

PostRequest.kt

package data.remote

import kotlinx.serialization.Serializable

@Serializable
data class PostRequest (
    val body: String,
    val title: String,
    val userId: Int
)
5
  • 1
    How are you setting up the HttpClient? When I try what you have here (guessing at the missing parts like the value of HttpRoutes.POSTS and the HttpClient setup) it works fine Commented Jul 30, 2022 at 1:19
  • 1
    I cannot reproduce it too. Commented Aug 1, 2022 at 11:47
  • Thanks for the responses, that is very odd. I have added more relevant code to my post above, please take a look at that. Could it be something in PostRequest.kt? I am modeling the PostRequest as a data class. When the error says expected JsonObject, is this why it is expecting a JsonObject? How do I make it expect a JsonArray? Commented Aug 5, 2022 at 23:34
  • Given that I am trying to pull a JsonArray from the URL, I thought using List<PostResponse> would work to let kotlinx serialization know to expect a JsonArray, however it doesn't. I have tried using an ArrayList<PostResponse>, however this also doesn't work, with an error of Error: Serializer for class 'PostModel' is not found. This despite putting @Serializable in the class definition. Commented Aug 6, 2022 at 1:29
  • If you would like to try and clone my project, to make sure I am not crazy, here is the GitHub: github.com/EdIzaguirre/GroupUpAndroid/tree/networking Commented Aug 6, 2022 at 2:00

1 Answer 1

2

I am going to close the question. Figured out that the following message was being displayed on my data class: "kotlinx.serialization compiler plugin is not applied to the module, so this annotation would not be processed. Make sure that you've setup your buildscript correctly and re-import project." The issue was that I did not include the following line in my build.gradle.kts (app)

    kotlin("plugin.serialization") version "1.7.10"

I also was using ProGuard, but did not include the relevant changes to the ProGuard rules (see here).

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.