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.
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
)
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).