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)
I am making generic classes for hitting otp api.anybody can use otp section just have to pass request ,Response class and url and all will be done by this otp section.
Please note : this response class can be of different type (for eg: MobileOtpResponse,EmailOtpResponse)
below is the generic OtpClient which takes any request type and returns particular passed ResponseType (for example : Request class passed is OtpRequest ,ResponseType class passed is OtpResponse)
interface OtpClient {
#POST
suspend fun <Request : Any, ResponseType> sendOtp(#Url url: String,
#Body request:#JvmSuppressWildcards Any): #JvmSuppressWildcards ResponseType
}
OtpRequest
data class OtpRequest(#SerializedName("mobile_number") val mobileNumber: String,#SerializedName("app_version") val appVersion: String)
OtpResponse
data class OtpResponse(#SerializedName("status") val status: String = "",
#SerializedName("response") val response: OtpData? = null)
data class OtpData(
#SerializedName("otp_status") val otpStatus: Boolean = false,
#SerializedName("message") val message: String = "",
#SerializedName("error") val error: Int? = null,
#SerializedName("otp_length") val otpLength: Int? = null,
#SerializedName("retry_left") val retryLeft: Int? = null,)
Now i create Repo to call this api this simply use flow and when the data fetch it emits the data
class OtpRepoImpl<out Client : OtpClient>(val client: Client) :OtpRepo {
override fun <Request:Any, ResponseType> sentOtpApi(url: String, request: Request): Flow<ResponseType> {
return flow<ResponseType> {
// exectute API call and map to UI object
val otpResponse = client.sendOtp<Request, ResponseType>(url,request)
emit(otpResponse)
}.flowOn(Dispatchers.IO) // Use the IO thread for this Flow
}
}
this repo is used in viewmodel class
#ExperimentalCoroutinesApi
fun <A : Class<ResponseType>, Request : Any, ResponseType : Any> sendOtp(a: Class<ResponseType>, request: Request, response: ResponseType, url: String) {
viewModelScope.launch {
repo.sentOtpApi<Request, ResponseType>(url, request = request)
.onStart { _uiState.value = OtpState.Loading(true) }
.catch { cause ->
_uiState.value = OtpState.Loading(false)
getResponseFromError<Class<ResponseType>,ResponseType>(cause, response) {
// emit(it)
}
}
.collect {
_uiState.value = OtpState.Loading(false)
_uiState.value = OtpState.Success(it)
}
}
}
as you can see above this sendOtp method is called from the view class and inside this method we use repo.sentOtpApi and pass generic request response type.I get data in catch block coz api is send error otp data in 400 HttpException so i created another method getResponseFromError to get error response it should parse the errorBody response and call this lambda block.
private suspend fun <A : Class<*>, ResponseType : Any> getResponseFromError( cause: Throwable, rp: ResponseType, block: suspend (ResponseType) -> Unit) {
if (cause is HttpException) {
val response = cause.response()
if (response?.code() == 400) {
println("fetching error Response")
val errorResponse = response.errorBody()?.charStream()
val turnsType = object : TypeToken<ResponseType>() {}.type
val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
block(finalErrorResponse)
} else {
println("someOther exception")
}
} else
_uiState.value = OtpState.Error(cause)
}
so here i am facing the problem inside above method
val turnsType = object : TypeToken<ResponseType>() {}.type
val finalErrorResponse = Gson().fromJson<ResponseType>(errorResponse, turnsType)
block(finalErrorResponse)
This finalErrorResponse is returning LinkedTreeMap instead of ResponseType (in this case its OtpResponse)
i have also tried using Class<*> type like this
val turnsType = object : TypeToken<A>() {}.type
val finalErrorResponse = Gson().fromJson<A>(errorResponse, turnsType)
but its not working.
calling of this sentOtp viewmodel func is like
var classType = OtpResponse::class.java
otpViewModel.sendOtp(a = classType, request = otpRequest, response = OtpResponse() , url =
"http://preprod-api.nykaa.com/user/otp/v2/send-wallet-otp")
[![value in finalErroResponse][1]][1]
[1]: https://i.stack.imgur.com/Holui.png
required: finalErroResponse should be of OtpResponse type because that was passed in sentOtp func
Please help :)
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
)
I try to write a network request function,Its role is to analyze the general format and provide data content to callbacks.
This is my defined class:
data class Response<T>(
val code: Int = 0,
#JvmSuppressWildcards
var data: ArrayList<T>,
val message: String = "")
in this class 'code' and 'message' is fixed,'data' has different types
Select one of data:
data class TestData(
#SerializedName("create_by")
val createBy: String = "",
#SerializedName("create_time")
val createTime: String = "",
#SerializedName("name")
val name: String = "",
#SerializedName("id")
val id: String = "")
There is my network request function:
fun <T> post(callBack: (ArrayList<T>) -> Unit) {
...
override fun onSuccess(response:Response<String>) {
val result = gson.fromJson<Response<T>>(response.body().reader(),Response::class.java)
when{
result.code==200-> callBack.invoke(result.data)
}
}
...
}
Use it at Activity:
Request.addr(Constants.GET_TEST)
.post<TestData> {
tv.text = it[0].name
}
When i use Gson parse the server returns data,and want use JavaBean ,Logcat throw this Exception:
java.lang.ClassCastException: com.google.gson.internal.LinkedTreeMap cannot be cast to com.example.network.response.TestData
i tried to use TypeToken to solve this problem ,but it also does not work.
It cause, because the parser can't fetch the real type T at runtime.
This extension works for me
inline fun Gson.fromJson(json: String) =
this.fromJson(json, object: TypeToken() {}.type)!!
Then you need modify as in you methods, as IDE recommend you.
In the process of learning how to use Retrofit with Moshi to use APIs with Android, I have run into an issue I cannot get my head around. The goal here is to get a simple array of categories returned from an API. When I make the call to, in this case, the Behance API to list all creativefields, an array is not returned. Instead is is an object with two arrays:
{"fields":[{"id":108,"name":"Advertising"},{"id":3,"name":"Animation"},{"id":4,"name":"Architecture"},{"id":5,"name":"Art Direction"},{"id":130,"name":"Automotive Design"},{"id":109,"name":"Branding"},{"id":133,"name":"Calligraphy"},{"id":9,"name":"Cartooning"},{"id":124,"name":"Character Design"},{"id":12,"name":"Cinematography"},{"id":15,"name":"Computer Animation"},{"id":19,"name":"Copywriting"},{"id":20,"name":"Costume Design"},{"id":21,"name":"Crafts"},{"id":137,"name":"Creative Direction"},{"id":23,"name":"Culinary Arts"},{"id":122,"name":"Digital Art"},{"id":27,"name":"Digital Photography"},{"id":28,"name":"Directing"},{"id":110,"name":"Drawing"},{"id":31,"name":"Editing"},{"id":32,"name":"Editorial Design"},{"id":33,"name":"Engineering"},{"id":35,"name":"Entrepreneurship"},{"id":36,"name":"Exhibition Design"},{"id":37,"name":"Fashion"},{"id":93,"name":"Fashion Styling"},{"id":38,"name":"Film"},{"id":112,"name":"Fine Arts"},{"id":40,"name":"Furniture Design"},{"id":41,"name":"Game Design"},{"id":43,"name":"Graffiti"},{"id":44,"name":"Graphic Design"},{"id":131,"name":"Icon Design"},{"id":48,"name":"Illustration"},{"id":49,"name":"Industrial Design"},{"id":50,"name":"Information Architecture"},{"id":51,"name":"Interaction Design"},{"id":52,"name":"Interior Design"},{"id":53,"name":"Jewelry Design"},{"id":54,"name":"Journalism"},{"id":55,"name":"Landscape Design"},{"id":59,"name":"MakeUp Arts (MUA)"},{"id":63,"name":"Motion Graphics"},{"id":64,"name":"Music"},{"id":66,"name":"Packaging"},{"id":67,"name":"Painting"},{"id":69,"name":"Pattern Design"},{"id":70,"name":"Performing Arts"},{"id":73,"name":"Photography"},{"id":74,"name":"Photojournalism"},{"id":78,"name":"Print Design"},{"id":79,"name":"Product Design"},{"id":123,"name":"Programming"},{"id":136,"name":"Retouching"},{"id":86,"name":"Sculpting"},{"id":87,"name":"Set Design"},{"id":118,"name":"Sound Design"},{"id":91,"name":"Storyboarding"},{"id":135,"name":"Street Art"},{"id":95,"name":"Textile Design"},{"id":126,"name":"Toy Design"},{"id":97,"name":"Typography"},{"id":132,"name":"UI\/UX"},{"id":120,"name":"Visual Effects"},{"id":102,"name":"Web Design"},{"id":103,"name":"Web Development"},{"id":105,"name":"Writing"}],
"popular":[{"id":44,"name":"Graphic Design"},{"id":73,"name":"Photography"},{"id":51,"name":"Interaction Design"},{"id":5,"name":"Art Direction"},{"id":48,"name":"Illustration"},{"id":49,"name":"Industrial Design"},{"id":63,"name":"Motion Graphics"},{"id":37,"name":"Fashion"},{"id":4,"name":"Architecture"},{"id":109,"name":"Branding"},{"id":102,"name":"Web Design"},{"id":132,"name":"UI\/UX"}],"http_code":200}
How do I parse this JSON response to get two arrays of creative fields using Moshi and Retrofit? Below is the setup I had anticipated would work. Now I am aware that the JSON is not a List but more of a FieldList with 2 values of "fields" and "popular", but I can't see how to extract the arrays with Moshi.
Model of a Creative Field
data class Fields(val id: Int, val name: String)
Interface/Service
interface BehanceService{
#GET( "v2/fields")
fun creativeField(#Query("api_key") api_key: String): Call<List<Fields>>
}
The API class
object BehanceAPI {
private val BASE_URL = "http://www.behance.net/"
val retrofittedBuilder: Retrofit by lazy {
Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
val behanceService: BehanceService = retrofittedBuilder.create(BehanceService::class.java)
}
this is how your json looks like as Java Model
data class Response(
val httpCode: Int? = null,
val fields: List<FieldsItem?>? = null,
val popular: List<PopularItem?>? = null)
data class FieldsItem(
val name: String? = null,
val id: Int? = null)
data class PopularItem(
val name: String? = null,
val id: Int? = null)
Your service will be something like this:
interface BehanceService{
#GET("v2/fields")
fun creativeField(#Query("api_key") api_key: String): Call<Response>
}
And your Api class will be something like this:
object BehanceAPI {
private val BASE_URL = "http://www.behance.net/"
val retrofittedBuilder: Retrofit by lazy {
Retrofit.Builder().baseUrl(BASE_URL)
.addConverterFactory(MoshiConverterFactory.create())
.build()
}
val behanceService: BehanceService = retrofittedBuilder.create(BehanceService::class.java)}
you can call it in this way.
BehanceAPI.behanceService.creativeField("your_key_here").enqueue(new Call<Response>(){
#Override
public void onResponse( response: Call<Response>)
{
// Deal with the response here
val data = response.body();
}
#Override
public void onFailure(Throwable t)
{
// Deal with the error here
}})