2

I am struggling to understand how to convert JSON data with Moshi. I am learning Android and Kotlin and my app is supposed to load and display COVID data. The input JSON format is like this:

[
  {
    "infected": 109782,
    "tested": "NA",
    "recovered": 75243,
    "deceased": 2926,
    "country": "Algeria",
    "moreData": "https://api.apify.com/v2/key-value-stores/pp4Wo2slUJ78ZnaAi/records/LATEST?disableRedirect=true",
    "historyData": "https://api.apify.com/v2/datasets/hi0DJXpcyzDwtg2Fm/items?format=json&clean=1",
    "sourceUrl": "https://www.worldometers.info/coronavirus/",
    "lastUpdatedApify": "2021-02-11T12:15:00.000Z"
  },
  {
    "infected": 425561,
    "tested": 11205451,
    "recovered": 407155,
    "deceased": 8138,
    "country": "Austria",
    "moreData": "https://api.apify.com/v2/key-value-stores/RJtyHLXtCepb4aYxB/records/LATEST?disableRedirect=true",
    "historyData": "https://api.apify.com/v2/datasets/EFWZ2Q5JAtC6QDSwV/items?format=json&clean=1",
    "sourceUrl": "https://www.sozialministerium.at/Informationen-zum-Coronavirus/Neuartiges-Coronavirus-(2019-nCov).html",
    "lastUpdatedApify": "2021-02-11T12:15:00.000Z"
  },
...
]

As you can see, numbers can also be represented as strings (as in 'tested'), also some URLs can be missing for some countries. So I followed Moshi documentation and created 2 data classes and a custom adapter.

//desired structure
data class CountryData(
    val infected: Int,
    val tested: Int,
    val recovered: Int,
    val deceased: Int,
    val country: String,
    val moreData: String,
    val historyData: String,
    val sourceUrl: String,
    val lastUpdatedApify: String
)

//actual JSON
data class CountryDataJson(
    val infected: String,
    val tested: String,
    val recovered: String,
    val deceased: String,
    val country: String,
    val moreData: String?,
    val historyData: String?,
    val sourceUrl: String?,
    val lastUpdatedApify: String
)

Custom adapter:

import android.util.Log
import com.example.coronastats.network.CountryData
import com.example.coronastats.network.CountryDataJson
import com.squareup.moshi.FromJson

class CountryJsonAdapter {
    @FromJson fun fromJson(countryDataJson: CountryDataJson): CountryData {
        val countryData = CountryData(
            if (countryDataJson.infected != "NA") countryDataJson.infected.toInt() else -1,
            if (countryDataJson.tested != "NA") countryDataJson.tested.toInt() else -1,
            if (countryDataJson.recovered != "NA") countryDataJson.recovered.toInt() else -1,
            if (countryDataJson.deceased != "NA") countryDataJson.deceased.toInt() else -1,
            countryDataJson.country,
            countryDataJson.moreData ?: "NA",
            countryDataJson.historyData ?: "NA",
            countryDataJson.sourceUrl ?: "NA",
            countryDataJson.lastUpdatedApify
        )
        Log.d("adapterLOG", "fromJson triggered")
        return countryData
    }
}

And my service API:


import com.example.coronastats.CountryJsonAdapter
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET

private const val BASE_URL = "https://api.apify.com/"

private val moshi = Moshi.Builder()
    .add(CountryJsonAdapter())
    .build()


private val retrofit = Retrofit.Builder()
    .addConverterFactory(MoshiConverterFactory.create(moshi))
    .baseUrl(BASE_URL)
    .build()

interface CoronaApiService {
    @GET("v2/key-value-stores/tVaYRsPHLjNdNBu7S/records/LATEST?disableRedirect=true")
    suspend fun getStatistics() : List<CountryData>
}

object CoronaApi {
    val retrofitService: CoronaApiService by lazy {
        retrofit.create(CoronaApiService::class.java)
    }
}

And I'm getting an empty screen as a result. The Log in the adapter is never triggered, so I assume something is wrong and my adapter is never called.

NB: Without all this converter stuff, the app runs ok with the standard KotlinJsonAdapterFactory() and CountryData class as all strings, but I'd like to know how to get the structure that I have here.

Anton V
  • 21
  • 2

1 Answers1

0

I believe that CountryDataJson is missing default values. Reading the docs I've noticed the following:

In Kotlin, these fields must have a default value if they are in the primary constructor.