Related
I tried to use Spoonacular API in Android using Jetpack Compose and Kotlin
but I got an error
BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2 path $
and I can't figure out how to solve this problem
Here is my JSON Format
{
"results": [
{
"vegetarian": true,
"vegan": true,
"glutenFree": true,
"dairyFree": true,
"veryHealthy": false,
"cheap": false,
"veryPopular": false,
"sustainable": false,
"lowFodmap": false,
"weightWatcherSmartPoints": 0,
"gaps": "GAPS_FULL",
"preparationMinutes": -1,
"cookingMinutes": -1,
"aggregateLikes": 1,
"healthScore": 51,
"creditsText": "Foodista.com – The Cooking Encyclopedia Everyone Can Edit",
"license": "CC BY 3.0",
"sourceName": "Foodista",
"pricePerServing": 81.7,
"extendedIngredients": [
{
"id": 11080,
"aisle": "Produce",
"image": "beets.jpg",
"consistency": "SOLID",
"name": "beet",
"nameClean": "red beet",
"original": "1 Beet, peeled",
"originalName": "Beet, peeled",
"amount": 1.0,
"unit": "",
"meta": [
"peeled"
],
"measures": {
"us": {
"amount": 1.0,
"unitShort": "",
"unitLong": ""
},
"metric": {
"amount": 1.0,
"unitShort": "",
"unitLong": ""
}
}
},
{
"id": 11124,
"aisle": "Produce",
"image": "sliced-carrot.png",
"consistency": "SOLID",
"name": "carrots",
"nameClean": "carrot",
"original": "1 pound Carrots, peeled",
"originalName": "Carrots, peeled",
"amount": 1.0,
"unit": "pound",
"meta": [
"peeled"
],
"measures": {
"us": {
"amount": 1.0,
"unitShort": "lb",
"unitLong": "pound"
},
"metric": {
"amount": 453.592,
"unitShort": "g",
"unitLong": "grams"
}
}
},
{
"id": 9200,
"aisle": "Produce",
"image": "orange.png",
"consistency": "SOLID",
"name": "oranges",
"nameClean": "orange",
"original": "2 pounds Oranges, peeled",
"originalName": "Oranges, peeled",
"amount": 2.0,
"unit": "pounds",
"meta": [
"peeled"
],
"measures": {
"us": {
"amount": 2.0,
"unitShort": "lb",
"unitLong": "pounds"
},
"metric": {
"amount": 907.185,
"unitShort": "g",
"unitLong": "grams"
}
}
}
],
"id": 1095729,
"title": "Immunity Booster Beet, Carrot & Orange Juice",
"readyInMinutes": 10,
"servings": 4,
"sourceUrl": "https://www.foodista.com/recipe/HHM34WCD/immune-booster-beet-carrot-orange-juice",
"image": "https://spoonacular.com/recipeImages/1095729-312x231.jpg",
"imageType": "jpg",
"summary": "Need a <b>gluten free, dairy free, paleolithic, and lacto ovo vegetarian beverage</b>? Immunity Booster Beet, Carrot & Orange Juice could be an outstanding recipe to try. This recipe makes 4 servings with <b>162 calories</b>, <b>4g of protein</b>, and <b>1g of fat</b> each. For <b>82 cents per serving</b>, this recipe <b>covers 18%</b> of your daily requirements of vitamins and minerals. This recipe from Foodista has 1 fans. If you have beet, carrots, oranges, and a few other ingredients on hand, you can make it. From preparation to the plate, this recipe takes roughly <b>10 minutes</b>. Overall, this recipe earns an <b>amazing spoonacular score of 89%</b>. If you like this recipe, you might also like recipes such as Goji-Orange-Turmeric Smoothie (“The Immunity Booster”), Rejuvenating Root Juice | Carrot, Beet, Blood Orange, Ginger, Turmeric Juice, and Rejuvenating Root Juice | Carrot, Beet, Blood Orange, Ginger, Turmeric Juice.",
"cuisines": [
],
"dishTypes": [
"beverage",
"drink"
],
"diets": [
"gluten free",
"dairy free",
"paleolithic",
"lacto ovo vegetarian",
"primal",
"whole 30",
"vegan"
],
"occasions": [
],
"analyzedInstructions": [
{
"name": "",
"steps": [
{
"number": 1,
"step": "Roughly chop the beet, carrots and oranges. When peeling and chopping the beet, use gloves and apron to avoid staining your hands and clothes.Throw them in a juicer and in about 2 minutes you will end up with this delicious healthy juice.",
"ingredients": [
{
"id": 11124,
"name": "carrot",
"localizedName": "carrot",
"image": "sliced-carrot.png"
},
{
"id": 9200,
"name": "orange",
"localizedName": "orange",
"image": "orange.png"
},
{
"id": 1019016,
"name": "juice",
"localizedName": "juice",
"image": "apple-juice.jpg"
},
{
"id": 11080,
"name": "beet",
"localizedName": "beet",
"image": "beets.jpg"
}
],
"equipment": [
{
"id": 404683,
"name": "juicer",
"localizedName": "juicer",
"image": "juicer.jpg"
}
],
"length": {
"number": 2,
"unit": "minutes"
}
}
]
}
],
"spoonacularSourceUrl": "https://spoonacular.com/immunity-booster-beet-carrot-orange-juice-1095729",
"usedIngredientCount": 0,
"missedIngredientCount": 3,
"missedIngredients": [
{
"id": 11080,
"amount": 1.0,
"unit": "",
"unitLong": "",
"unitShort": "",
"aisle": "Produce",
"name": "beet",
"original": "1 Beet, peeled",
"originalName": "Beet, peeled",
"meta": [
"peeled"
],
"image": "https://spoonacular.com/cdn/ingredients_100x100/beets.jpg"
},
{
"id": 11124,
"amount": 1.0,
"unit": "pound",
"unitLong": "pound",
"unitShort": "lb",
"aisle": "Produce",
"name": "carrots",
"original": "1 pound Carrots, peeled",
"originalName": "Carrots, peeled",
"meta": [
"peeled"
],
"image": "https://spoonacular.com/cdn/ingredients_100x100/sliced-carrot.png"
},
{
"id": 9200,
"amount": 2.0,
"unit": "pounds",
"unitLong": "pounds",
"unitShort": "lb",
"aisle": "Produce",
"name": "oranges",
"original": "2 pounds Oranges, peeled",
"originalName": "Oranges, peeled",
"meta": [
"peeled"
],
"image": "https://spoonacular.com/cdn/ingredients_100x100/orange.png"
}
],
"likes": 0,
"usedIngredients": [
],
"unusedIngredients": [
]
}
],
"offset": 0,
"number": 1,
"totalResults": 40
}
Model
data class ResultX(
#SerializedName("aggregateLikes")
val aggregateLikes: Int,
#SerializedName("cheap")
val cheap: Boolean,
#SerializedName("dairyFree")
val dairyFree: Boolean,
#SerializedName("extendedIngredients")
val extendedIngredients: List<ExtendedIngredient>,
#SerializedName("glutenFree")
val glutenFree: Boolean,
#SerializedName("id")
val id: Int,
#SerializedName("image")
val image: String,
#SerializedName("likes")
val likes: Int,
#SerializedName("readyInMinutes")
val readyInMinutes: Int,
#SerializedName("servings")
val servings: Int,
#SerializedName("sourceName")
val sourceName: String,
#SerializedName("sourceUrl")
val sourceUrl: String,
#SerializedName("summary")
val summary: String,
#SerializedName("title")
val title: String,
#SerializedName("vegan")
val vegan: Boolean,
#SerializedName("vegetarian")
val vegetarian: Boolean,
#SerializedName("veryHealthy")
val veryHealthy: Boolean
)
data class ExtendedIngredient(
#SerializedName("amount")
val amount: Double,
#SerializedName("consistency")
val consistency: String,
#SerializedName("image")
val image: String,
#SerializedName("name")
val name: String,
#SerializedName("original")
val original: String,
#SerializedName("unit")
val unit: String
)
API INTERFACE
#GET("/recipes/complexSearch")
suspend fun getRecipes(
#QueryMap queries: Map<String, String>
): List<ResultX>
RESOURCE CLASS AND API REPOSITORY IMPLEMENTATION
sealed class Resource<T>(val data: T? = null, val message: String? = null){
class Success<T>(data: T): Resource<T>(data)
class Error<T>(message: String?, data: T? = null): Resource<T>(data, message)
class Loading<T>(data: T? = null): Resource<T>(data)
}
interface FoodApiRepository {
fun getRecipes(queries: Map<String, String>): Flow<Resource<List<ResultX>>>
}
class FoodApiRepositoryImpl #Inject constructor(
private val foodApi: FoodApi
): FoodApiRepository {
override fun getRecipes(queries: Map<String, String>): Flow<Resource<List<ResultX>>> {
return flow {
try {
emit(Resource.Loading())
val result = foodApi.getRecipes(queries = queries)
emit(
Resource.Success(
data = result
)
)
}catch (e: HttpException){
emit(Resource.Error(e.localizedMessage ?: "An expected error occurred"))
}catch (e: IOException){
emit(Resource.Error("Couldn't reach server. Check your internet connection"))
}
}
}
}
FOOD LIST STATE CLASS AND HOME VIEW MODEL
data class FoodListState(
val isLoading: Boolean = false,
val recipes: List<ResultX> = emptyList(),
val error: String = ""
)
#HiltViewModel
class HomeViewModel #Inject constructor(
private val foodApiRepository: FoodApiRepository
): ViewModel(){
private val _state = mutableStateOf(FoodListState())
val state: State<FoodListState> = _state
init {
getRecipes(applyQueries())
}
private fun getRecipes(queries: Map<String, String>) {
foodApiRepository.getRecipes(queries = queries).onEach { result ->
when(result) {
is Resource.Success -> {
_state.value = FoodListState(recipes = result.data ?: emptyList())
}
is Resource.Error -> {
_state.value =
FoodListState(
error = result.message ?: "An un expected error occurred"
)
Log.d("TAG", "getFoodRecipes: ERROR")
}
is Resource.Loading -> {
_state.value = FoodListState(isLoading = true)
}
}
}.launchIn(viewModelScope)
}
private fun applyQueries(): HashMap<String, String> {
val queries: HashMap<String, String> = HashMap()
queries["number"] = "50"
queries["apiKey"] = API_KEY
queries["type"] = "snack"
queries["diet"] = "vegan"
queries["addRecipeInformation"] = "true"
queries["fillIngredients"] = "true"
Log.d("TAG", "applyQueries: $queries")
return queries
}
}
HOME SCREEN
#Composable
fun HomeScreen(
navController: NavController,
homeViewModel: HomeViewModel = hiltViewModel()
) {
val state = homeViewModel.state.value
Box(modifier = Modifier.fillMaxSize()){
LazyColumn(modifier = Modifier.fillMaxSize()) {
items(state.recipes) { recipe ->
Text(
text = recipe.title,
style = MaterialTheme.typography.subtitle1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier
.fillMaxWidth()
.padding(20.dp)
)
}
}
if (state.error.isNotBlank()){
Text(
text = state.error,
color = MaterialTheme.colors.error,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp)
.align(
Alignment.Center
)
)
}
if (state.isLoading){
CircularProgressIndicator(modifier = Modifier.align(Alignment.Center))
}
}
}
THANK YOU
You're parsing it wrong. Your Retrofit function says it returns a List. That's not what the JSON being returned is. It's returning a JSON object, not a JSON Array. You need another class ResultWrapper which has a a list of ResultX in it named result, and have the Retrofit API return that.
This question already has answers here:
Expected BEGIN_ARRAY but was BEGIN_OBJECT at line 1 column 2
(2 answers)
Closed 12 months ago.
I cant generate a true code for showing all the element in recyclerView...or i can only show 1 element when i use this code on RecyclerAdapter - holder.tvValueName.text = currentItem.AMD.Name
but when i want add all elements whit this code - holder.tvValueName.text = currentItem.currencyModel.Name - it didnt work...
I already tried change the types of some classes like a val valute: Valute -to- val valute:List<Valute> or val currencyModel: CurrencyModel -to- val currencyModel: List<CurrencyModel>
and then get values with method holder.tvValueName.text = currentItem.currencyModel.get(position).Name - but i get error with ARRAY...
If anyone has any ideas please help, thanks in advance
interface ApiInterface {
#GET("daily_json.js")
fun getValuteData() : Call<Main>
companion object {
var BASE_URL = "https://www.cbr-xml-daily.ru/"
fun create() : ApiInterface {
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.build()
return retrofit.create(ApiInterface::class.java)
}
}
}
class RecyclerAdapter(val context: Context) : RecyclerView.Adapter<RecyclerAdapter.MyViewHolder>() {
var valuteItemList : MutableList<Valute> = mutableListOf()
private var changedData: MutableList<Valute> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerview_adapter,parent,false)
return MyViewHolder(view)
}
override fun getItemCount(): Int {
return changedData.size
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val currentItem = changedData[position]
holder.tvValueName.text = currentItem.AMD.Name
holder.tvValueName.text = currentItem.currencyModel.Name
}
#SuppressLint("NotifyDataSetChanged")
fun setValuteListItems(valuteList: MutableList<Valute>){
valuteItemList.clear()
valuteItemList.addAll(valuteList)
changedData.addAll(valuteItemList)
notifyDataSetChanged()
}
class MyViewHolder(itemView: View?) : RecyclerView.ViewHolder(itemView!!) {
val tvValueName: TextView = itemView!!.findViewById(R.id.title)
val tvValuteValue: TextView = itemView!!.findViewById(R.id.valute_value)
val tvValuteCharCode: TextView = itemView!!.findViewById(R.id.chare_code)
val image: ImageView = itemView!!.findViewById(R.id.image)
}
}
class MainActivity : AppCompatActivity() {
lateinit var recyclerView: RecyclerView
lateinit var recyclerAdapter: RecyclerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recyclerView = findViewById(R.id.recyclerview)
recyclerAdapter = RecyclerAdapter(this)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = recyclerAdapter
val apiInterface = ApiInterface.create().getValuteData()
apiInterface.enqueue( object : Callback<Main> {
override fun onResponse(call: Call<Main>?, response: Response<Main>?) {
Log.d("MyTag","Success ")
if(response!!.body() != null)
//recyclerAdapter.setValuteListItems(response.body()!!.Valute as MutableList<Valute>)
recyclerAdapter.setValuteListItems(mutableListOf(response.body()!!.Valute))
Log.d("MyTag","Success "+response.body().toString())
}
override fun onFailure(call: Call<Main>?, t: Throwable?) {
Log.d("MyTag", "Failure "+t.toString())
}
})
}
}
Whole JSON Model
data class Main(
val Date: String,
val PreviousDate: String,
val PreviousURL: String,
val Timestamp: String,
val Valute: Valute
)
data class CurrencyModel(
val CharCode: String,
val ID: String,
val Name: String,
val Nominal: Int,
val NumCode: String,
val Previous: Double,
val Value: Double
)
data class Valute(
val currencyModel: CurrencyModel,
val AMD: CurrencyModel,
val AUD: CurrencyModel,
val AZN: CurrencyModel,
val BGN: CurrencyModel,
val BRL: CurrencyModel,
val BYN: CurrencyModel,
val CAD: CurrencyModel,
val CHF: CurrencyModel,
val CNY: CurrencyModel,
val CZK: CurrencyModel,
val DKK: CurrencyModel,
val EUR: CurrencyModel,
val GBP: CurrencyModel,
val HKD: CurrencyModel,
val HUF: CurrencyModel,
val INR: CurrencyModel,
val JPY: CurrencyModel,
val KGS: CurrencyModel,
val KRW: CurrencyModel,
val KZT: CurrencyModel,
val MDL: CurrencyModel,
val NOK: CurrencyModel,
val PLN: CurrencyModel,
val RON: CurrencyModel,
val SEK: CurrencyModel,
val SGD: CurrencyModel,
val TJS: CurrencyModel,
val TMT: CurrencyModel,
val TRY: CurrencyModel,
val UAH: CurrencyModel,
val USD: CurrencyModel,
val UZS: CurrencyModel,
val XDR: CurrencyModel,
val ZAR: CurrencyModel
)
JSON
{
"Date": "2022-03-02T11:30:00+03:00",
"PreviousDate": "2022-03-01T11:30:00+03:00",
"PreviousURL": "\/\/www.cbr-xml-daily.ru\/archive\/2022\/03\/01\/daily_json.js",
"Timestamp": "2022-03-01T18:00:00+03:00",
"Valute": {
"AUD": {
"ID": "R01010",
"NumCode": "036",
"CharCode": "AUD",
"Nominal": 1,
"Name": "Австралийский доллар",
"Value": 66.7817,
"Previous": 67.3624
},
"AZN": {
"ID": "R01020A",
"NumCode": "944",
"CharCode": "AZN",
"Nominal": 1,
"Name": "Азербайджанский манат",
"Value": 53.9998,
"Previous": 55.067
},
"GBP": {
"ID": "R01035",
"NumCode": "826",
"CharCode": "GBP",
"Nominal": 1,
"Name": "Фунт стерлингов Соединенного королевства",
"Value": 123.1411,
"Previous": 125.0415
},
"AMD": {
"ID": "R01060",
"NumCode": "051",
"CharCode": "AMD",
"Nominal": 100,
"Name": "Армянских драмов",
"Value": 18.8196,
"Previous": 19.524
},
"BYN": {
"ID": "R01090B",
"NumCode": "933",
"CharCode": "BYN",
"Nominal": 1,
"Name": "Белорусский рубль",
"Value": 29.4765,
"Previous": 33.9264
},
"BGN": {
"ID": "R01100",
"NumCode": "975",
"CharCode": "BGN",
"Nominal": 1,
"Name": "Болгарский лев",
"Value": 52.5944,
"Previous": 53.4592
},
"BRL": {
"ID": "R01115",
"NumCode": "986",
"CharCode": "BRL",
"Nominal": 1,
"Name": "Бразильский реал",
"Value": 17.7795,
"Previous": 18.1182
},
"HUF": {
"ID": "R01135",
"NumCode": "348",
"CharCode": "HUF",
"Nominal": 100,
"Name": "Венгерских форинтов",
"Value": 27.7437,
"Previous": 28.0932
},
"HKD": {
"ID": "R01200",
"NumCode": "344",
"CharCode": "HKD",
"Nominal": 1,
"Name": "Гонконгский доллар",
"Value": 11.7413,
"Previous": 11.9737
},
"DKK": {
"ID": "R01215",
"NumCode": "208",
"CharCode": "DKK",
"Nominal": 1,
"Name": "Датская крона",
"Value": 13.8309,
"Previous": 14.0504
},
"USD": {
"ID": "R01235",
"NumCode": "840",
"CharCode": "USD",
"Nominal": 1,
"Name": "Доллар США",
"Value": 91.7457,
"Previous": 93.5589
},
"EUR": {
"ID": "R01239",
"NumCode": "978",
"CharCode": "EUR",
"Nominal": 1,
"Name": "Евро",
"Value": 102.9112,
"Previous": 104.4772
},
"INR": {
"ID": "R01270",
"NumCode": "356",
"CharCode": "INR",
"Nominal": 10,
"Name": "Индийских рупий",
"Value": 12.1817,
"Previous": 12.3927
},
"KZT": {
"ID": "R01335",
"NumCode": "398",
"CharCode": "KZT",
"Nominal": 100,
"Name": "Казахстанских тенге",
"Value": 18.6621,
"Previous": 18.8393
},
"CAD": {
"ID": "R01350",
"NumCode": "124",
"CharCode": "CAD",
"Nominal": 1,
"Name": "Канадский доллар",
"Value": 72.4346,
"Previous": 73.2646
},
"KGS": {
"ID": "R01370",
"NumCode": "417",
"CharCode": "KGS",
"Nominal": 10,
"Name": "Киргизских сомов",
"Value": 10.7936,
"Previous": 11.0069
},
"CNY": {
"ID": "R01375",
"NumCode": "156",
"CharCode": "CNY",
"Nominal": 1,
"Name": "Китайский юань",
"Value": 14.534,
"Previous": 14.8243
},
"MDL": {
"ID": "R01500",
"NumCode": "498",
"CharCode": "MDL",
"Nominal": 10,
"Name": "Молдавских леев",
"Value": 49.8618,
"Previous": 50.8472
},
"NOK": {
"ID": "R01535",
"NumCode": "578",
"CharCode": "NOK",
"Nominal": 1,
"Name": "Норвежская крона",
"Value": 10.4292,
"Previous": 10.4927
},
"PLN": {
"ID": "R01565",
"NumCode": "985",
"CharCode": "PLN",
"Nominal": 1,
"Name": "Польский злотый",
"Value": 21.9288,
"Previous": 22.2151
},
"RON": {
"ID": "R01585F",
"NumCode": "946",
"CharCode": "RON",
"Nominal": 1,
"Name": "Румынский лей",
"Value": 20.7899,
"Previous": 21.1342
},
"XDR": {
"ID": "R01589",
"NumCode": "960",
"CharCode": "XDR",
"Nominal": 1,
"Name": "СДР (специальные права заимствования)",
"Value": 127.9724,
"Previous": 130.4436
},
"SGD": {
"ID": "R01625",
"NumCode": "702",
"CharCode": "SGD",
"Nominal": 1,
"Name": "Сингапурский доллар",
"Value": 67.764,
"Previous": 68.8743
},
"TJS": {
"ID": "R01670",
"NumCode": "972",
"CharCode": "TJS",
"Nominal": 10,
"Name": "Таджикских сомони",
"Value": 81.2988,
"Previous": 82.8688
},
"TRY": {
"ID": "R01700J",
"NumCode": "949",
"CharCode": "TRY",
"Nominal": 10,
"Name": "Турецких лир",
"Value": 66.1464,
"Previous": 67.7806
},
"TMT": {
"ID": "R01710A",
"NumCode": "934",
"CharCode": "TMT",
"Nominal": 1,
"Name": "Новый туркменский манат",
"Value": 26.2506,
"Previous": 26.7694
},
"UZS": {
"ID": "R01717",
"NumCode": "860",
"CharCode": "UZS",
"Nominal": 10000,
"Name": "Узбекских сумов",
"Value": 84.384,
"Previous": 86.3067
},
"UAH": {
"ID": "R01720",
"NumCode": "980",
"CharCode": "UAH",
"Nominal": 10,
"Name": "Украинских гривен",
"Value": 30.4044,
"Previous": 31.0053
},
"CZK": {
"ID": "R01760",
"NumCode": "203",
"CharCode": "CZK",
"Nominal": 10,
"Name": "Чешских крон",
"Value": 41.0266,
"Previous": 41.8215
},
"SEK": {
"ID": "R01770",
"NumCode": "752",
"CharCode": "SEK",
"Nominal": 10,
"Name": "Шведских крон",
"Value": 96.7925,
"Previous": 98.1669
},
"CHF": {
"ID": "R01775",
"NumCode": "756",
"CharCode": "CHF",
"Nominal": 1,
"Name": "Швейцарский франк",
"Value": 100.2028,
"Previous": 101.0683
},
"ZAR": {
"ID": "R01810",
"NumCode": "710",
"CharCode": "ZAR",
"Nominal": 10,
"Name": "Южноафриканских рэндов",
"Value": 59.9571,
"Previous": 60.8375
},
"KRW": {
"ID": "R01815",
"NumCode": "410",
"CharCode": "KRW",
"Nominal": 1000,
"Name": "Вон Республики Корея",
"Value": 76.5619,
"Previous": 77.7626
},
"JPY": {
"ID": "R01820",
"NumCode": "392",
"CharCode": "JPY",
"Nominal": 100,
"Name": "Японских иен",
"Value": 79.7962,
"Previous": 80.9718
}
}
}
In your model change
val Valute: Valute
to
val Valute = ArrayList()
and change your response json from
"Valute": {
"AUD": {
to
"Valute": [
"AUD": {
By changing this you can get the Valute as Arraylist and then you can load the data in adapter
Can anybody say where I am doing wrong. I have json like that
[
{
"id": "1",
"name": "ff",
"surname": "ggg",
"cap": "10000"
},
{
"id": "1",
"name": "aaa",
"surname": "hhh",
"cap": "22222"
},
{
"id": "1",
"name": "rrr",
"surname": "hhhhhdr",
"cap": "33333"
},
{
"id": "1",
"name": "hhh",
"surname": "qqqqq",
"cap": "44444"
}
]
And I parse to this class.
data class ResponseList(
val capList: List<Response>?
) {
data class Response(
#JsonProperty("id")
val id: String,
#JsonProperty("name")
val name: String,
#JsonProperty("surname")
val surname: String,
#JsonProperty("cap")
val cap: String
)
}
When I try to parse it the list is always null and if I try to test it I have this error:
Cannot deserialize value of type com.myapp.ResponseList from Array value (token JsonToken.START_ARRAY)
just class Response is needed, like following:
fun test(){
val jsonStr = "your json str"
val mapper = ObjectMapper()
val lendReco: List<Response> =
mapper.readValue(jsonStr, object : TypeReference<List<Response?>?>() {})
}
data class Response(
#JsonProperty("id")
val id: String,
#JsonProperty("name")
val name: String,
#JsonProperty("surname")
val surname: String,
#JsonProperty("cap")
val cap: String
)
I faced with this problem. I've searched in the internet but answers was for Java, not Kotlin. How do i solve this ? If you need anything else please let me know. I'm trying to get closest restaurants for user location. Currently I'm using static variables because I couldn't get Zomato APi yet. How can i manage to do this ? It's been 4-5 days since I start learning kotlin
My restaurant data class
data class Restaurant(
#SerializedName("name")
val restName: String?,
#SerializedName("address")
val restAddress: String?
)
My API interface
interface ZomatoAPI {
#Headers(
"Accept: application/json",
"user-key: $API_KEY"
)
#GET("/search")
fun getRestaurants(#Query("count") count: Int
,#Query("lat") latitude: Double
,#Query("lon") longitude: Double
,#Query("sort") sort : String
,#Query("order") order : String
): Single<List<Restaurant>>
}
My Service Class
class RestaurantService{
private val baseUrl = "https://developers.zomato.com/api/v2.1/"
private val api: ZomatoAPI
private val latitude = 41.2
private val longitude = 28.2
init {
api = Retrofit.Builder()
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build()
.create(ZomatoAPI::class.java)
}
fun getRestaurants(): Single<List<Restaurant>>{
return api.getRestaurants(5,latitude,longitude,"rating","desc")
}
}
My ListViewModel
class ListViewModel: ViewModel(){
private val restaurantService = RestaurantService()
private val disposable = CompositeDisposable()
val restaurants = MutableLiveData<List<Restaurant>>()
val restaurantLoadError = MutableLiveData<Boolean>()
val loading = MutableLiveData<Boolean>()
fun refresh() {
fetchRestaurants()
}
private fun fetchRestaurants() {
loading.value = true
restaurantService.getRestaurants()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object: DisposableSingleObserver<List<Restaurant>>(){
override fun onSuccess(value: List<Restaurant>?) {
restaurants.value = value
restaurantLoadError.value = false
loading.value = false
}
override fun onError(e: Throwable?) {
restaurantLoadError.value = true
loading.value = false
}
})
}
override fun onCleared() {
super.onCleared()
disposable.clear()
}
}
Response Body of Zomato API
{
"results_found": 1499579,
"results_start": 0,
"results_shown": 2,
"restaurants": [
{
"restaurant": {
"R": {
"has_menu_status": {
"delivery": -1,
"takeaway": -1
},
"res_id": 18047914,
"is_grocery_store": false
},
"apikey": "61528551ffc800703d600cb2c25e6900",
"id": "18047914",
"name": "The Mossy Cafe",
"url": "https://www.zomato.com/mossy-point-nsw/the-mossy-cafe-mossy-point?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"location": {
"address": "31 Pacific Street, Mossy Point",
"locality": "Mossy Point",
"city": "Mossy Point",
"city_id": 2181,
"latitude": "-35.8369730000",
"longitude": "150.1796610000",
"zipcode": "",
"country_id": 14,
"locality_verbose": "Mossy Point, Mossy Point"
},
"switch_to_order_menu": 0,
"cuisines": "Cafe Food, Coffee and Tea",
"timings": "7:30am – 3pm (Mon-Sun)",
"average_cost_for_two": 50,
"price_range": 4,
"currency": "$",
"highlights": [
"Cash",
"Debit Card",
"No Alcohol Available",
"Credit Card",
"Breakfast",
"Lunch",
"Kid Friendly",
"Vegan Options",
"Pet Friendly",
"Indoor Seating",
"Gluten Free Options"
],
"offers": [],
"opentable_support": 0,
"is_zomato_book_res": 0,
"mezzo_provider": "OTHER",
"is_book_form_web_view": 0,
"book_form_web_view_url": "",
"book_again_url": "",
"thumb": "",
"user_rating": {
"aggregate_rating": "3.9",
"rating_text": "Good",
"rating_color": "9ACD32",
"rating_obj": {
"title": {
"text": "3.9"
},
"bg_color": {
"type": "lime",
"tint": "600"
}
},
"votes": 7
},
"all_reviews_count": 0,
"photos_url": "https://www.zomato.com/mossy-point-nsw/the-mossy-cafe-mossy-point/photos?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1#tabtop",
"photo_count": 42,
"menu_url": "https://www.zomato.com/mossy-point-nsw/the-mossy-cafe-mossy-point/menu?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1&openSwipeBox=menu&showMinimal=1#tabtop",
"featured_image": "",
"has_online_delivery": 0,
"is_delivering_now": 0,
"store_type": "",
"include_bogo_offers": true,
"deeplink": "zomato://restaurant/18047914",
"is_table_reservation_supported": 0,
"has_table_booking": 0,
"events_url": "https://www.zomato.com/mossy-point-nsw/the-mossy-cafe-mossy-point/events#tabtop?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"phone_numbers": "02 4471 8599",
"all_reviews": {
"reviews": []
},
"establishment": [
"Café"
],
"establishment_types": []
}
},
{
"restaurant": {
"R": {
"has_menu_status": {
"delivery": -1,
"takeaway": -1
},
"res_id": 16611721,
"is_grocery_store": false
},
"apikey": "61528551ffc800703d600cb2c25e6900",
"id": "16611721",
"name": "Blue Swimmer at Seahaven",
"url": "https://www.zomato.com/gerroa-nsw/blue-swimmer-at-seahaven-gerroa?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"location": {
"address": "19 Riverleigh Ave, Gerroa, Gerroa",
"locality": "Gerroa",
"city": "Gerroa",
"city_id": 2154,
"latitude": "-34.7697390000",
"longitude": "150.8117400000",
"zipcode": "2534",
"country_id": 14,
"locality_verbose": "Gerroa, Gerroa"
},
"switch_to_order_menu": 0,
"cuisines": "Modern Australian, Australian",
"timings": "7 AM to 3 PM (Mon, Tue, Wed, Thu, Sun), 7 AM to 3 PM, 5:30 PM to 11 PM (Fri-Sat)",
"average_cost_for_two": 20,
"price_range": 2,
"currency": "$",
"highlights": [
"Cash",
"Breakfast",
"Lunch",
"No Alcohol Available",
"Dinner",
"Table booking recommended",
"Outdoor Seating"
],
"offers": [],
"opentable_support": 0,
"is_zomato_book_res": 0,
"mezzo_provider": "OTHER",
"is_book_form_web_view": 0,
"book_form_web_view_url": "",
"book_again_url": "",
"thumb": "",
"user_rating": {
"aggregate_rating": "3.9",
"rating_text": "Good",
"rating_color": "9ACD32",
"rating_obj": {
"title": {
"text": "3.9"
},
"bg_color": {
"type": "lime",
"tint": "600"
}
},
"votes": 21
},
"all_reviews_count": 11,
"photos_url": "https://www.zomato.com/gerroa-nsw/blue-swimmer-at-seahaven-gerroa/photos?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1#tabtop",
"photo_count": 15,
"menu_url": "https://www.zomato.com/gerroa-nsw/blue-swimmer-at-seahaven-gerroa/menu?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1&openSwipeBox=menu&showMinimal=1#tabtop",
"featured_image": "",
"has_online_delivery": 0,
"is_delivering_now": 0,
"store_type": "",
"include_bogo_offers": true,
"deeplink": "zomato://restaurant/16611721",
"is_table_reservation_supported": 0,
"has_table_booking": 0,
"events_url": "https://www.zomato.com/gerroa-nsw/blue-swimmer-at-seahaven-gerroa/events#tabtop?utm_source=api_basic_user&utm_medium=api&utm_campaign=v2.1",
"phone_numbers": "(02) 4234 3796",
"all_reviews": {
"reviews": [
{
"review": []
},
{
"review": []
},
{
"review": []
},
{
"review": []
},
{
"review": []
}
]
},
"establishment": [
"Casual Dining"
],
"establishment_types": []
}
}
]
}
Your getRestaurants method should be like this:
interface ZomatoAPI {
#Headers(
"Accept: application/json",
"user-key: $API_KEY"
)
#GET("/search")
fun getRestaurants(#Query("count") count: Int
,#Query("lat") latitude: Double
,#Query("lon") longitude: Double
,#Query("sort") sort : String
,#Query("order") order : String
): Single<RestaurantResponse>
}
and your model class should be like this,
data class RestaurantResponse(
#SerializedName("restaurants")
val restaurants: List<Restaurant>?,
#SerializedName("results_found")
val results_found: Int?
#SerializedName("results_start")
val results_start: Int?
#SerializedName("results_shown")
val results_shown: Int?
)
i got this error but i think my json are right
this is my json
{
"data": [
{
"id_movie": "7",
"id_review": "4",
"rating": "4",
"review": "menginspirasi",
"created_at": "2019-01-11 18:50:11",
"id_user": "1",
"title": "Bohemian Rhapsody",
"release_date": "2018-12-03",
"cover": "https://i.ytimg.com/vi/lcwmDAYt22k/maxresdefault.jpg",
"trailer": "https://www.youtube.com/watch?v=lcwmDAYt22k",
"id": "3",
"genre": "Science Fiction",
"poster": "https://m.media-amazon.com/images/M/MV5BOTM3NzE5OTE1Nl5BMl5BanBnXkFtZTgwNzQwNjM3NjM#._V1_UX182_CR0,0,182,268_AL_.jpg"
}
]
}
and i got this error and it referring to this code
fun getReviewList(genre: String?) {
view.showLoading()
GlobalScope.launch(context.main) {
val data = gson.fromJson(apiRepository
.doRequest(localhost.getReview(genre)).await(),
ReviewResponse::class.java)
println(data)
view.showReviewList(data.data)
view.hideLoading()
}
}
this is my ReviewResponse class
data class ReviewResponse(
val data: MutableList<Review>)
this is my Review class
data class Review(
#SerializedName("id_review")
var reviewId: String? = null,
#SerializedName("id_movie")
var movieId: String? = null,
#SerializedName("poster")
var poster: String? = null,
#SerializedName("rating")
var rating: String? = null,
#SerializedName("review")
var review: String? = null,
#SerializedName("created_at")
var date: String? = null
)
please help what is wrong with it