JSONArray into Kotling Array/List and Setting value in spinner - android

I am performing a HTTP request from an API that gives me a JSON Array, I have been able to parse it but I am not able to convert it into a list or array in order to set it within the spinner. Here is what I have tried:
HTTP CALL:
private fun initiate() {
var product = 1
val httpAsync = "https://link_here"
.httpGet()
.responseString { request, response, result ->
when (result) {
is Result.Failure -> {
val ex = result.getException()
println(ex)
}
is Result.Success -> {
val data = result.get()
val json = JSONArray(data)
println(json)
nebulae = listOf(json)
}
}
}
httpAsync.join()
}
the response looks like this:
[{"_nebulae_id":"2","_ss_id":"1","_product_id":"2","product_type":"Dust"},{"_nebulae_id":"2","_ss_id":"3","_product_id":"1","product_type":"Star"}]
Here is the spinner code:
val spinnerProducts: Spinner = findViewById(R.id.spinnerProducts)
var adapter= ArrayAdapter(this,android.R.layout.simple_list_item_1,nebulae)
ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
nebulae // insert list
).also { adapter ->
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
spinnerProducts.adapter = adapter
}
P.S I am new to Kotlin and it's been very long time since I used Java.

Firstly, nebulae = listOf(json) will not parse the list inside the json, will only put the whole json as an element of a list.
Create your data class for holding response:
data class Nebulae(
#SerializedName("_nebulae_id") val nebulae_id: String,
#SerializedName("_ss_id") val ss_id: String,
#SerializedName("_product_id") val product_id: String
#SerializedName("product_type") val product_type: String
)
If you want to use Gson, then convert your response like that:
val type = object : TypeToken<List<Nebulae>>() {}.type
val decodedResponse = Gson().fromJson<List<Nebulae>>(json, type)
If you want to use kotlinx.serialization, then convert your response as follows:
val decodedResponse = Json.decodeFromString<List<Nebulae>>(your_json_string)

Related

Formatting the Nested JSON response from retrofit API in MVVM Architecture - Kotlin

I m new to kotlin and MVVM, I have been working around this issue for a week now, couldn't get any idea even after searching for some code on the internet.
I'm trying to edit or modify the retrofit response (to observe a specific type; say "sf") according to my need and neglecting other data which is not needed. I'm using mutable livedata to fetch and update the JSON data from the retrofit response to the recylerview.
Here is the link for the JSON data: http://www.nactem.ac.uk/software/acromine/dictionary.py?sf=HMM
3 Data classes based on JSON response:
data class sf(
#SerializedName("sf")
#Expose
val sf : String? = null,
#SerializedName("lfs")
#Expose
val lfs : List<lfs>? = null,
)
data class lfs(
#SerializedName("lf")
#Expose
var lf : String? = null,
#SerializedName("freq")
#Expose
var freq : Int? = null,
#SerializedName("since")
#Expose
var since : Int? = null,
#SerializedName("vars")
#Expose
var vars : List<vars>? = null,
) : Serializable
class vars (
#SerializedName("lf")
#Expose
var lf : String? = null,
#SerializedName("freq")
#Expose
var freq : Int? = null,
#SerializedName("since")
#Expose
var since : Int?
): Serializable
Code in Activity:
listUsers = mutableListOf()
adapter = WordAdapters(this, listUsers )
recyclerView.adapter = adapter
wordViewModel = ViewModelProviders.of(this,
WordViewModelFactory(this)).get(WordsViewModel::class.java)
wordViewModel!!.getData().observe(this, { t: ArrayList<sf>? ->
listUsers.clear()
t?.let { listUsers.addAll(it)
}
adapter.notifyDataSetChanged()
})
ViewModel:
class WordsViewModel ( context: Context) : ViewModel() {
private var listData = MutableLiveData<ArrayList<sf>>()
init {
val wordRepository: WordsRepository by lazy {
WordsRepository
}
//if (context.isInternetAvailable()) {
listData = wordRepository.getMutableLiveData(context)
// }
}
fun getData(): MutableLiveData<ArrayList<sf>> {
return listData
} }
Repository:
object WordsRepository {
var call: Call<MutableList<sf>>? = null
fun getMutableLiveData(context: Context) : MutableLiveData<ArrayList<sf>> {
val mutableLiveData = MutableLiveData<ArrayList<sf>>()
//context.showProgressBar()
call = NetworkApiClient.apiService.getWordsMatching("HMM")
call!!.enqueue(object : Callback<MutableList<sf>> {
override fun onFailure(call: Call<MutableList<sf>>, t: Throwable) {
//hideProgressBar()
Log.e("error", t.localizedMessage.toString())
}
override fun onResponse(call: Call<MutableList<sf>>, response:
Response<MutableList<sf>>)
{
//hideProgressBar()
if (!response.isSuccessful){
Log.e("Code " , response.code().toString());
return
}
val raw: okhttp3.Response = response.raw()
val usersResponse : MutableList<sf>? = response.body()
/* if (usersResponse != null) {
for( movie in usersResponse[0].lfs!!){
Log.v("MainActivity", movie.vars.toString())
}
}*/
Log.e("Output : ", usersResponse.toString())
usersResponse?.let { mutableLiveData.value = it as ArrayList<sf> }
}
})
return mutableLiveData
}
}
this is the base structure of JSON: here "sf" is a string, lfs is the array, according to this JSON response link provided I get 8 lfs arrays, but currently after parsing the recyclecount is 1 which is the same in the adapter itemcount method, so I get one row displayed in recylerview and rest are ignored.
JSON response:
[
{
"sf":"HMM",
"lfs":[
{
"lf":"heavy meromyosin",
"freq":267,
"since":1971,
"vars":[
{
"lf":"heavy meromyosin",
"freq":244,
"since":1971
},
{
"lf":"Heavy meromyosin",
"freq":12,
"since":1975
},
{
"lf":"H-meromyosin",
"freq":5,
"since":1975
},
{
"lf":"heavy-meromyosin",
"freq":4,
"since":1977
},
{
"lf":"heavy meromyosin",
"freq":1,
"since":1976
},
{
"lf":"H-Meromyosin",
"freq":1,
"since":1976
}
]
},
I want to ignore "sf" string after response and parse the ArrayList which is present under the "sf" which is "lfs", so based on "lfs" I need to display the data.
Mutable live data is not accepting any other type other than sf, since I placed the observer on it.
On the json you posted, there is only one parent item ( one sf ), but you are actually trying to pass the 8 lfs children. You have to perform such transformation somewhere, it could be on the network call directly, like this:
usersResponse?.let { mutableLiveData.value = it[0].lfs as ArrayList }
Take into account two things:
It could be better to check if "it" is not empty before going for the first item.
This only works if you will always have only one item on the parent array ( this sounds strange since if this is the case then the service should be returning an object, not a list, as the root of the json. If you will receive more than one object you will have to map the response into a single list of lfs. Something like (pseudo code since I'm from my phone):
It.map( item -> item.lfs)

Json same name different data type Moshi Android

This is my model class and i have these types of json to convert in to this model. How i can do that with Moshi (Using Retrofit)
data class(var Id:Int, var image:List<String>)
{"Id":188, "image":"\/posts\/5fd9aa6961c6dd54129f51d1.jpeg"}
{"Id":188, "image":["\/posts\/5fd9aa6961c6dd54129f51d1.jpeg","\/posts\/5fd9aa6961c6dd54129f51d1.jpeg"]}
Your case is a bit unorthodox, in general I would avoid designing JSONs with matching field names but different signature. Anyway, the solution:
Define your model as follow:
data class MyModel(
#Json(name = "Id") val Id: Long,
#Json(name = "image") val images: List<String>
)
Then, you will have to create a custom adapter for it:
class MyModelAdapter {
#ToJson
fun toJson(model: MyModel): String {
// MyModel is data class so .toString() should convert it to correct Json format with
// image property as list of image path strings
return model.toString()
}
#FromJson
fun fromJson(reader: JsonReader): MyModel = with(reader) {
// We need to manually parse the json
var id: Long? = null
var singleImage: String? = null
val imageList = mutableListOf<String>()
beginObject()
while (hasNext()) {
// iterate through the JSON fields
when (nextName()) {
"Id" -> id = nextLong() // map the id field
"image" -> { // map the image field
when (peek()) {
JsonReader.Token.BEGIN_ARRAY -> {
// the case where image field is an array
beginArray()
while(hasNext()) {
val imageFromList = nextString()
imageList.add(imageFromList)
}
endArray()
}
JsonReader.Token.STRING -> {
// the case where image field is single string
singleImage = nextString()
}
else -> skipValue()
}
}
else -> skipValue()
}
}
endObject()
id ?: throw IllegalArgumentException("Id should not be null")
val images = if (singleImage != null) {
listOf(singleImage)
} else {
imageList
}
MyModel(Id = id, images = images)
}
}
Then, add the adapter to your moshi builder:
Moshi.Builder()
.add(MyModelAdapter())
.build()
That should do it. For complete code base, you can check my demo I just created that mirrors your case:
https://github.com/phamtdat/MoshiMultipleJsonDemo

Is it possible to same key with differ class and data type in data class kotlin android?

I have one issue about code data class kotlin android.
How to implement server response? sometimes I get String value or sometime get Object class.
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
val data: String as Data
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
When I implement only Data class it works, like this val data: Data or val data: String. But I need together Data and String with key only data.
Is it possible?
When having multiple type for same variable, we can use Any type which is equivalent to Object type in java. So solution is like below :
class CMSRespTemp {
data class CMSRespApi(
val status: Boolean = false,
val message: String = "",
var data: Any? = null // changed it to var from val, so that we can change it's type runtime if required
)
data class Data(
val cms_id: String = "",
val cms_content: String = ""
)
}
And when accessing that variable, one can simply cast like below :
val apiResponse : CMSRespApi //= some API response here from network call
when (apiResponse.data) {
is String -> {
// apiResponse.data will be smart-casted to String here
}
else -> {
val responseData = Gson().fromJson<CMSRespApi.Data>(
Gson().toJsonTree(apiResponse.data),
CMSRespApi.Data::class.java
)
}
}
After 12 Hrs spend and got the solution my self,
val getResultCon = getSerCont.result // response Any
val gson = Gson()
val jsonElement = gson.toJsonTree(getResultCon)
val resultData = gson.fromJson(jsonElement, SearchContactApi.Result::class.java)
Convert your data string to toJsonTree and fromJson with model class then got result.

How To Filter Retrofit2 Json Response To Keep Only Certain Elements In ArrayList

I'm new to kotlin so this maybe a very easy issue to resolve.
What I'm trying to do is filter the json response that I receive using Retrofit2 before I display the images in a grid with a RecyclerView.
instagram.com/explore/tags/{hashtag}/?__a=1&max_id= Using Retrofit2 I'm able to get the data response fine and also display the given url images in a RecyclerView.
I have not been successful in using the filter, map, loops and conditions to remove elements from the Arraylist. I do not understand these to the fullest extent but I have searched looking for solutions and those are what I came apon.
Interface
interface InstagramDataFetcher
{
#GET("tags/{tag}/?__a=1&max_id=")
fun getInstagramData(#Path("tag") hashtag: String) : Call <InstagramResponse>
}
Where I get my response from and also get StringIndexOutOfBoundsException
class InstagramFeedFragment : Fragment()
{
private fun onResponse()
{
val service = RestAPI.retrofitInstance?.create(InstagramDataFetcher::class.java)
val call = service?.getInstagramData("hashtag")
call?.enqueue(object : Callback<InstagramResponse>
{
override fun onFailure(call: Call<InstagramResponse>, t: Throwable)
{
Log.d("FEED", " $t")
}
override fun onResponse(
call: Call<InstagramResponse>, response: Response<InstagramResponse>
)
{
//for ((index, value) in data.withIndex())
if (response.isSuccessful)
{
var data: ArrayList<InstagramResponse.InstagramEdgesResponse>? = null
val body = response.body()
data = body!!.graphql.hashtag.edge_hashtag_to_media.edges
for ((index, value) in data.withIndex())
{
if(value.node.accessibility_caption[index].toString().contains("text") ||
value.node.accessibility_caption[index].toString().contains("person"))
{
data.drop(index)
}
}
recyclerView.adapter = InstagramGridAdapter(data, parentFragment!!.context!!)
}
}
})
}
}
This is my model class
data class InstagramResponse(val graphql: InstagramGraphqlResponse)
{
data class InstagramGraphqlResponse(val hashtag: InstagramHashtagResponse)
data class InstagramHashtagResponse(val edge_hashtag_to_media: InstagramHashtagToMediaResponse)
data class InstagramHashtagToMediaResponse(
val page_info: InstagramPageInfo,
val edges: ArrayList<InstagramEdgesResponse>
)
data class InstagramPageInfo(
val has_next_page: Boolean,
val end_cursor: String
)
data class InstagramEdgesResponse(val node: InstagramNodeResponse)
data class InstagramNodeResponse(
val __typename: String,
val shortcode: String,
val display_url: String,
val thumbnail_src: String,
val thumbnail_resources: ArrayList<InstagramThumbnailResourceResponse>,
val is_video: Boolean,
val accessibility_caption: String
)
data class InstagramThumbnailResourceResponse(
val src: String,
val config_width: Int,
val config_height: Int
)
}
Simply again, I want to just remove elements from the arraylist that match certain things what I don't want. For instance. the "is_video" value that comes from the json. I want to go through the arraylist and remove all elements that have "is_video" as true.
Thanks
If you asking how to filter the list then below is the demo.
You just need to use filter on your data which is an ArrayList. I've tried keeping the same structure for the models so that you can get a better understanding.
fun main() {
val first = InstagramNodeResponse(
title = "first",
is_video = true
)
val second = InstagramNodeResponse(
title = "second",
is_video = false
)
val list: ArrayList<InstagramEdgesResponse> = arrayListOf(
InstagramEdgesResponse(node = first),
InstagramEdgesResponse(node = second)
)
val itemsWithVideo = list.filter { it.node.is_video == true }
val itemsWithoutVideo = list.filter { it.node.is_video == false }
println(itemsWithVideo.map { it.node.title }) // [first]
println(itemsWithoutVideo.map { it.node.title }) // [second]
}
// Models
data class InstagramEdgesResponse(val node: InstagramNodeResponse)
data class InstagramNodeResponse(
val title: String,
val is_video: Boolean
)

How to get Json Response in Arraylist from List<Item> using RxJava and Kotlin

Facing Problem on getting Response in ArrayList.
I have following Respose on String value
var res_message: String = ""
res_message = "${result.vehicletypes} "
Getting below Value on this String
[VehicleType(_id=1, vehicleType=Hatchback, __v=0),
VehicleType(_id=2, vehicleType=Maruti, __v=0),
VehicleType(_id=3, vehicleType=Honda, __v=0),
VehicleType(_id=4, vehicleType=Bike, __v=0)]
Retrofit Result is
vehicletypes = {ArrayList#6055} size = 4
0 = {Model$VehicleType#6058} "VehicleType(_id=1,
vehicleType=Hatchback, __v=0)"
1 = {Model$VehicleType#6059} "VehicleType(_id=2,
vehicleType=Maruti, __v=0)"
2 = {Model$VehicleType#6060} "VehicleType(_id=3,
vehicleType=Honda, __v=0)"
3 = {Model$VehicleType#6061} "VehicleType(_id=4,
vehicleType=Bike, __v=0)"
Below Code snippest sending request to API.
disposable = apiServices.getVehicle(token)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result ->
res_message = "${result.vehicletypes} "
Log.d("Type==", res_message)
},
{ error ->
res_message = "${error.message}"
// validateToken()
}
)
Model Class
data class Vehicles(val success: Boolean, val vehicletypes: List<VehicleType>, val message: String)
data class VehicleType(val _id: String, val vehicleType: String, val __v: String)
I want to get this value on Arralist VehicleType List on below vehicleListArray
private var vehicleListArray: ArrayList<Model.VehicleType>? = null
How we can achieve this.
Thanks in advance.
Assuming that what you are trying to parse is a response from a service that is able to send you propper format for lists (eg Json) than
Retrofit can handle parsing lists with ease.
In your apiService definition:
fun getPeople(token: Whatever): Observable<List<VehicleType>>
And if you don't have it already:
Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create(gson))
I got solution I have to handle Respose as below code snippet.
private fun getVehicleType() {
disposable?.add(apiServices.getVehicle(token)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(Schedulers.io())
.subscribe(this::handleResponse, this::handleError))
}
private fun handleResponse(vehicles: Model.Vehicles) {
VehiclesArrayList = ArrayList(vehicles.vehicletypes)
Log.d("type==","n--"+VehiclesArrayList )
mAdapter = DataAdapter(VehiclesArrayList !!, this)
v_android_list.adapter = mAdapter
}
private fun handleError(error: Throwable) {
Log.d("type", error.localizedMessage)
Toast.makeText(context, "Error ${error.localizedMessage}", Toast.LENGTH_SHORT).show()
}

Categories

Resources