Sorting data from json in Kotlin - android

I have data from json file which I display in recyclerview in my app. I'm trying to sort this data by year. That's how my code looks:
In MainActivity.kt everythings happend in fetchJson() function
private fun fetchJson(jsonUrl: String) {
Log.d(TAG, "Attempting to fetch json")
val request = okhttp3.Request.Builder().url(jsonUrl).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onFailure(call: Call, e: IOException) {
Log.d(TAG, "Failed to execute request")
}
override fun onResponse(call: Call, response: Response) {
val body = response.body()?.string()
Log.d(TAG, "$body")
val gson = GsonBuilder().create()
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
homeFeed.standups.sortedWith(compareBy({it.year}))
runOnUiThread {
rv.adapter = Adapter(homeFeed)
}
}
})
}
fun <T> compareBy(vararg selectors: (T) -> Comparable<*>?): Comparator<T> {
return Comparator<T> { a, b -> compareValuesBy(a, b, *selectors) }
}
class HomeFeed is here:
class HomeFeed(val standups: List<StandUps>)
and data class StandUps:
data class StandUps(
val artist: String,
val title: String,
val year: String,
val poster: String,
val description: String,
val netflix_link: String,
val imdb_rate: String,
val imdb_link: String,
val duration_min: String
)
It doesn't shows any errors or warnings, it just doesn't do anything. How could I achieve this?

You have to first store the sorted list in another variable and then use that variable to pass it to your adapter
val homeFeed = gson.fromJson(body, HomeFeed::class.java)
val sortedHomeFeed = homeFeed.standups.sortedWith(compareBy({it.year}))
runOnUiThread {
rv.adapter = Adapter(sortedHomeFeed)
}
The reason for this is, changes are not made to the original list following the concepts of immutability.

Kotlin gives you easy sorting. Jus like below
make a temp object (i.e)., tempFilterData here
val standUps = tempFilterData?.sortedWith(compareBy({ it.Year }))
Now you can get the sorted data based on YEAR

If you want to sort your list ascending by a year you can do this:
val sortedStandUps = homeFeed.standups.sortedBy { it.year }
If you want to sort list descending do this:
val sortedStandUps = homeFeed.standups.sortedByDescending { it.year }

Related

Retrofit Call.enqueue method not being called - kotlin

i was able to make Edit and Delete work in a test project, using Retrofit2, but now i needed to used it in my real project, and despite the code and webservice used (created using Slim 4 and Notorm) are the same, both Edit and Delete started not working for some reason (get and Post both work correctly)
For both Edit i am using a page that get all the date from the DB and put it in some views, and then i click in a button that have the Edit Function . I tried debugging and everything is fine, until it tries to enter the call.enqueue method, and it fails, without giving any error on my logcat. I have a toast on my onResponse and another toast on my OnFailure and neither shows on my screen.
With the Delete the situation is similar i have a page, with a call.enqueue (on the on.create) that gets all the data that appears in the views, and then a button that trigger the delete method, and here also the call.enqueue doens't work. I tested this, without having the call.enqueue on the oncreate, but it happens the same thing.
Here is my Edit button code
fun editar(view: View) {
val request = ServiceBuilder.buildService(EndPoints::class.java)
val latitude = latitude.text.toString().toDouble()
val longitude = longitude.text.toString().toDouble()
val morada = editMoradaView.text.toString()
val n_quartos = editNQuartosView.text.toString().toInt()
val casaBanho = casaBanho.text.toString().toInt()
val contacto = contacto.text.toString()
val mobilada = mobilada.text.toString()
val preco = preco.text.toString().toDouble()
val observacao = observacao.text.toString()
val utilizador_id = shared_preferences.getInt("id", 0)
if (isBitMap) {
val base = getBase64String(decodedByte)
fotografia = base
} else {
fotografia = base64
}
val call = request.editar(
id = ID,
users_id = utilizador_id,
morada = morada,
n_quartos = n_quartos,
latitude = latitude.toString().toDouble(),
longitude = longitude.toString().toDouble(),
fotografia = fotografia,
preco = preco,
ncasas_banho = casaBanho,
telemovel = contacto,
mobilado = mobilada,
outros_atributos = observacao
)
call.enqueue(object : Callback<OutputEditar> {
override fun onResponse(call: Call<OutputEditar>, response: Response<OutputEditar>) {
if (response.isSuccessful) {
val c: OutputEditar = response.body()!!
Toast.makeText(this#EditarAnuncios, c.MSG, Toast.LENGTH_LONG).show()
val intent = Intent(this#EditarAnuncios, MapsActivity::class.java)
startActivity(intent)
finish()
}
}
override fun onFailure(call: Call<OutputEditar>, t: Throwable) {
Toast.makeText(this#EditarAnuncios, "${t.message}", Toast.LENGTH_SHORT).show()
}
})
}
Here is my Delete Button Code
fun delete(view: View) {
var id = intent.getStringExtra("PARAM_ID")
var idString = id.toString()
val request = ServiceBuilder.buildService(EndPoints::class.java)
val call = request.apagarAnuncio(id = idString.toInt())
call.enqueue(object : Callback<OutputApagar> {
override fun onResponse(call: Call<OutputApagar>, response: Response<OutputApagar>) {
if (response.isSuccessful) {
val c: OutputApagar = response.body()!!
Toast.makeText(this#DetalhesAnuncioLogado, c.MSG, Toast.LENGTH_LONG).show()
val intent = Intent(this#DetalhesAnuncioLogado, MapsActivity::class.java)
setResult(Activity.RESULT_OK, intent)
finish()
}
}
override fun onFailure(call: Call<OutputApagar>, t: Throwable) {
Toast.makeText(this#DetalhesAnuncioLogado, "${t.message}", Toast.LENGTH_SHORT)
.show()
}
})
}
Here are my 2 data classes (in separated files)
data class OutputEditar(
val users_id: Int,
val morada: String,
val n_quartos: Int,
val latitude: Double,
val longitude: Double,
val fotografia: String,
val preco: Double,
val ncasas_banho: Int,
val telemovel: String,
val mobilado: String,
val outros_atributos: String,
val status: String,
val MSG: String
)
data class OutputApagar(
val status: String,
val MSG: String
)
Here are my endpoints
#FormUrlEncoded
#POST("/editar_anuncios/{id}")
fun editar(#Path("id") id: Int?,
#Field("users_id") users_id: Int?,
#Field("morada") morada: String?,
#Field("n_quartos") n_quartos: Int?,
#Field("latitude") latitude: Double?,
#Field("longitude") longitude: Double?,
#Field("fotografia") fotografia: String?,
#Field("preco") preco: Double?,
#Field("ncasas_banho") ncasas_banho: Int?,
#Field("telemovel") telemovel: String?,
#Field("mobilado") mobilado: String?,
#Field("outros_atributos") outros_atributos: String?): Call<OutputEditar>
#POST("/apagarAnuncios/{id}")
fun apagarAnuncio(#Path("id") id: Int?): Call<OutputApagar>
Both Endpoints work well on my Test Project and on Postman
My Service Builder
object ServiceBuilder {
private val client = OkHttpClient.Builder().build()
private val retrofit = Retrofit.Builder()
.baseUrl("https://tneveda.000webhostapp.com/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
fun<T> buildService(service: Class<T>): T {
return retrofit.create(service)
}
}
Thank You in advance
That moment, when 10 minutes i made this Question, i found out my problem. Forgot to update my Endpoints on my Real Project, and that is why it was not working

Unable to invoke no-args constructor for retrofit2.Call MVVM Coroutines Retrofit

I want to use coroutines in my project only when I use coroutines I get the error :Unable to invoke no-args constructor. I don't know why it's given this error. I am also new to coroutines.
here is my apiclient class:
class ApiClient {
val retro = Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
Here is my endpoint class:
#GET("v2/venues/search")
suspend fun get(
#Query("near") city: String,
#Query("limit") limit: String = Constants.limit,
#Query("radius") radius: String = Constants.radius,
#Query("client_id") id: String = Constants.clientId,
#Query("client_secret") secret: String = Constants.clientSecret,
#Query("v") date: String
): Call<VenuesMainResponse>
my Repository class:
class VenuesRepository() {
private val _data: MutableLiveData<VenuesMainResponse?> = MutableLiveData(null)
val data: LiveData<VenuesMainResponse?> get() = _data
suspend fun fetch(city: String, date: String) {
val retrofit = ApiClient()
val api = retrofit.retro.create(VenuesEndpoint::class.java)
api.get(
city = city,
date = date
).enqueue(object : Callback<VenuesMainResponse>{
override fun onResponse(call: Call<VenuesMainResponse>, response: Response<VenuesMainResponse>) {
val res = response.body()
if (response.code() == 200 && res != null) {
_data.value = res
} else {
_data.value = null
}
}
override fun onFailure(call: Call<VenuesMainResponse>, t: Throwable) {
_data.value = null
}
})
}
}
my ViewModel class:
class VenueViewModel( ) : ViewModel() {
private val repository = VenuesRepository()
fun getData(city: String, date: String): LiveData<VenuesMainResponse?> {
viewModelScope.launch {
try {
repository.fetch(city, date)
} catch (e: Exception) {
Log.d("Hallo", "Exception: " + e.message)
}
}
return repository.data
}
}
part of activity class:
class MainActivity : AppCompatActivity(){
private lateinit var venuesViewModel: VenueViewModel
private lateinit var adapter: HomeAdapter
private var searchData: List<Venue>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val editText = findViewById<EditText>(R.id.main_search)
venuesViewModel = ViewModelProvider(this)[VenueViewModel::class.java]
venuesViewModel.getData(
city = "",
date = ""
).observe(this, Observer {
it?.let { res ->
initAdapter()
rv_home.visibility = View.VISIBLE
adapter.setData(it.response.venues)
searchData = it.response.venues
println(it.response.venues)
}
})
this is my VenuesMainResponse data class
data class VenuesMainResponse(
val response: VenuesResponse
)
I think the no-args constructor warning should be related to your VenuesMainResponse, is it a data class? You should add the code for it as well and the complete Log details
Also, with Coroutines you should the change return value of the get() from Call<VenuesMainResponse> to VenuesMainResponse. You can then use a try-catch block to get the value instead of using enqueue on the Call.
Check this answer for knowing about it and feel free to ask if this doesn't solve the issue yet :)
UPDATE
Ok so I just noticed that it seems that you are trying to use the foursquare API. I recently helped out someone on StackOverFlow with the foursquare API so I kinda recognize those Query parameters and the Venue response in the code you provided above.
I guided the person on how to fetch the Venues from the Response using the MVVM architecture as well. You can find the complete code for getting the response after the UPDATE block in the answer here.
This answer by me has code with detailed explanation for ViewModel, Repository, MainActivity, and all the Model classes that you will need for fetching Venues from the foursquare API.
Let me know if you are unable to understand it, I'll help you out! :)
RE: UPDATE
So here is the change that will allow you to use this code with Coroutines as well.
Repository.kt
class Repository {
private val _data: MutableLiveData<mainResponse?> = MutableLiveData(null)
val data: LiveData<mainResponse?> get() = _data
suspend fun fetch(longlat: String, date: String) {
val retrofit = Retro()
val api = retrofit.retro.create(api::class.java)
try {
val response = api.get(
longLat = longlat,
date = date
)
_data.value = response
} catch (e: Exception) {
_data.value = null
}
}
}
ViewModel.kt
class ViewModel : ViewModel() {
private val repository = Repository()
val data: LiveData<mainResponse?> = repository.data
fun getData(longLat: String, date: String) {
viewModelScope.launch {
repository.fetch(longLat, date)
}
}
}
api.kt
interface api {
#GET("v2/venues/search")
suspend fun get(
#Query("ll") longLat: String,
#Query("client_id") id: String = Const.clientId,
#Query("client_secret") secret: String = Const.clientSecret,
#Query("v") date: String
): mainResponse
}
MainActivity.kt
private val viewModel by viewModels<ViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.getData(
longLat = "40.7,-74",
date = "20210718" // date format is: YYYYMMDD
)
viewModel.data
.observe(this, Observer {
it?.let { res ->
res.response.venues.forEach { venue ->
val name = venue.name
Log.d("name ",name)
}
}
})
}
}

How to add Text Composable in onResponse ? Jetapck Compose

My English is weak so please manage
I'm trying to get data from json after decoding json How can i add the decoded data to a Text Composable
Here is my Code
#Composable
fun Api(){
val url = "https://jsonplaceholder.typicode.com/posts"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback{
override fun onResponse(call: Call, response: Response) {
val body = response.body()?.string()
val gson = Gson()
val bundle = gson.fromJson(body, Array<Json>::class.java)
for(element in bundle){
val title = element.title
TODO("How to add title value to Text Composable")
}
}
override fun onFailure(call: Call, e: IOException) {
print("Error")
}
})
}
Here is my Data class
data class Json(
#field:SerializedName("title")
val title: String? = null,
)
You should move your logic in a ViewModel, loading the data and expose the value to the Text as a state.
If you want to use your example you can define your Text:
var text by remember { mutableStateOf("") }
Text (text = text)
Then just update the text value:
client.newCall(request).enqueue(object : Callback {
//...
override fun onResponse(call: Call, response: Response) {
response.use {
//....
text = //your logic
}
}
})

How to parse a WebSocket response to a list of objects

I am using Socket IO to fetch data from the API in JSON format
In my ListViewModal I have:
private fun getTorrentsSocketIO() {
coroutineScope.launch {
try {
val socket = IO.socket("...")
socket.on("torrents") {
_torrents.postValue(...???...)
}
socket.connect()
} catch (t: Throwable) {
Log.i("LIST_VIEW_MODEL", t.message.toString())
}
}
}
socket.on is returning an array of type any if I see correctly
How can I parse the response to a List<Torrent> cause I want to update my _torrent live data
I am using moshi as my json converter
Torrent class looks like this
data class Torrent(
val name: String,
val magnetURI: String,
val length: Double,
val received: Double,
val downloaded: Double,
val uploaded: Double,
val downloadSpeed: Double,
val uploadSpeed: Double,
val progress: Double,
val ratio: Double,
val paused: Boolean,
val done: Boolean
)
And json response like this
[
{
"name":"Sintel",
"magnetURI":"",
"length":123.31236934661865,
"received":46.98424434661865,
"downloaded":46.98424434661865,
"uploaded":0,
"downloadSpeed":0,
"uploadSpeed":0,
"progress":0.38101809733742664,
"ratio":0,
"paused":true,
"done":false
}
]
Also if anyone has a recommendation on how to make this code nicer, please answer
I tried multiple things, but I always get Expected BEGIN_OBJECT but was STRING error
You need to objectify the string you get as json via Moshi.
val moshi: Moshi = Moshi.Builder().build()
val adapter: JsonAdapter<Torrent> = moshi.adapter(Torrent::class.java)
val torrent= adapter.fromJson(torrentJsonStr))
I solved the problem by doing this:
private fun getTorrentsSocketIO() {
coroutineScope.launch {
try {
val socket = IO.socket("...")
socket.on("torrents") {
val responseBody = it[0].toString()
val torrents = Gson().fromJson(responseBody, Array<Torrent>::class.java).toList()
_torrents.postValue(torrents)
}
socket.connect()
} catch (t: Throwable) {
Log.i("LIST_VIEW_MODEL", t.message.toString())
}
}
}

How to get a value in JSON by an id value in the class in Kotlin

I want to get the "temp" value inside the "weather" array of a city that i search with the "name" of the city.
I did it with a for loop but it slow, is there another better way to do it?
Here is the JSON file: https://ws.smn.gob.ar/map_items/weather
Here is my code so far:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun fetchJson(view:View){
println("attemting to fetch JSON")
val url = "https://ws.smn.gob.ar/map_items/weather"
val request = Request.Builder().url(url).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object: Callback {
override fun onResponse(call: Call, response: Response) {
var body = response?.body()?.string()
println(body)
val gson = GsonBuilder().create()
val cities = gson.fromJson<List<Cities>>(body, object : TypeToken<List<Cities>>() {}.type)
for(city in cities){
if(city.name.equals(nameOfCity.text.toString())){
showsTemp.text = city.weather.temp.toString()
}}
}
override fun onFailure(call: Call, e: IOException) {
println("Se fallo en establecer la comunicacion")
}
})
}
class Cities(val _id:String,
val name:String,
val province:String,
val weather: Weather)
class Weather(val humidity: Int,val temp: Double)
}
Yes, You can use list.find{} for finding a city with its name without looping.
Here in your code it should be like this.
val cities = gson.fromJson<List<Cities>>(body, object : TypeToken<List<Cities>>() {}.type)
val cityFounded = cities.find{ it.name == nameOfCity.text.toString() }
showsTemp.text = cityFounded?.weather?.temp.toString()
Hope it will help you.

Categories

Resources