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
}
}
})
Related
I am new android developer, how can I get result form this snippet, what way does exist, because it doesn't return anything, because of I'm adding element inside onResponse, but using only kotlin module:
private fun foo(list: ArrayList<CurrencyModel> = ArrayList()): ArrayList<CurrencyModel> {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
list.add(CurrencyModel("USD", data.rates.USD, 0.0))
list.add(CurrencyModel("SEK", data.rates.SEK, 0.0))
list.add(CurrencyModel("EUR", data.rates.EUR, 0.0))
}
})
return list
}
}
You can give your function a callback parameter that's called when the response is receieved. And you shouldn't have an input list in this case, because if you have multiple sources modifying it at unpredictable future moments, it will be difficult to track.
The function can look like this:
private fun getCurrencyModels(callback: (ArrayList<CurrencyModel>) {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
val list = arrayListOf(
CurrencyModel("USD", data.rates.USD, 0.0)),
CurrencyModel("SEK", data.rates.SEK, 0.0)),
CurrencyModel("EUR", data.rates.EUR, 0.0))
)
callback(list)
}
})
}
And then to use it:
getCurrencyModels { modelsList ->
// do something with modelsList when it arrives
}
An alternative is to use coroutines, which allow you to do asynchronous actions without callbacks. Someone has already created a library that lets you use OkHttp requests in coroutines here. You could write your function as a suspend function like this:
private suspend fun getCurrencyModels(): ArrayList<CurrencyModel> {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
val response = client.newCall(request).await()
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
return arrayListOf(
CurrencyModel("USD", data.rates.USD, 0.0)),
CurrencyModel("SEK", data.rates.SEK, 0.0)),
CurrencyModel("EUR", data.rates.EUR, 0.0))
)
}
and then use it like this:
lifecycleScope.launch {
try {
val currencyModels = getCurrencyModels()
// do something with currencyModels
} catch (e: IOException) {
// request failed
}
}
Coroutines make it really easy to avoid leaking memory when your asynchronous calls outlive your Activity or Fragment. In this case, if your Activity closes while the request is going, it will be cancelled automatically and references to your Activity will be removed so the garbage collector can release your Activity.
The onResponse() function is only called when the HTTP response is successfully returned by the remote server. Since this response doesn't happen immediately, you can't use the result in your code immediately. What you could do is use a ViewModel and LiveData variable and add the values to that variable in onResponse(). Something like:
private fun foo(list: ArrayList<CurrencyModel> = ArrayList()) {
val request = Request.Builder().url(BASE_URL_YESTERDAY).build()
val client = OkHttpClient()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
}
override fun onResponse(call: Call, response: Response) {
val body = response.body?.string()
val data = Gson().fromJson(body, Currencies::class.java)
val list: ArrayList<CurrencyModel> = arrayListOf()
list.add(CurrencyModel("USD", data.rates.USD, 0.0))
list.add(CurrencyModel("SEK", data.rates.SEK, 0.0))
list.add(CurrencyModel("EUR", data.rates.EUR, 0.0))
viewModel.list.postValue(list)
}
})
}
when I use retrofit2 with no coroutine, the result is null. but when using that with coroutine, the result is right. I think it's the problem of syncronization. but I found something strange
using mutablelivedata, the result is right.
retrofit2 with coroutine
override suspend fun getRetrofit(id : Int): DetailEntity {
withContext(ioDispatcher){
val request = taskNetworkSource.searchItem(id)
val response = request.await()
if(response.body !=null){
Log.d("TAG",""+response.toString())
data = response
}
}
return data
}
good result
D/TAG: DetailEntity(body=DetatilItem(oily_score=6, full_size_image=url, price=54840, sensitive_score=76, description=description, id=5, dry_score=79, title=title), statusCode=200)
retrofit2 with no coroutine
override suspend fun getRetrofit(id : Int): DetailEntity {
taskNetworkSource.searchItem(id).enqueue(object: Callback<DetailEntity> {
override fun onFailure(call: Call<DetailEntity>, t: Throwable) {
}
override fun onResponse(call: Call<DetailEntity>, response: Response<DetailEntity>){
if(response.body()!=null) {
Log.d("TAG",response.toString())
data = response.body()!!
}
}
})
return data
}
bad result
D/TAG: Response{protocol=h2, code=200, message=, url=https://6uqljnm1pb.execute-api.ap-northeast-2.amazonaws.com/prod/products/5}
strange result with mutablelivedata(another project code)
lateinit var dataSet : DetailModel
var data = MutableLiveData<DetailModel>()
fun getDetailRetrofit(id:Int) : MutableLiveData<DetailModel>{
Retrofit2Service.getService().requestIndexItem(id).enqueue(object:
Callback<DetailResponse> {
override fun onFailure(call: Call<DetailResponse>, t: Throwable) {
}
override fun onResponse(call: Call<DetailResponse>, response: Response<DetailResponse>) {
if(response.body()!=null) {
var res = response.body()!!.body
dataSet = DetailModel( res.get(0).discount_cost,
res.get(0).cost,
res.get(0).seller,
res.get(0).description+"\n\n\n",
res.get(0).discount_rate,
res.get(0).id,
res.get(0).thumbnail_720,
res.get(0).thumbnail_list_320,
res.get(0).title
)
data.value = dataSet
}
}
})
return data
}
and this another project code result is right. comparing this code to retrofit2 with no coroutine code, the difference is only mutablelivedata or not. do I have to use asyncronouse library or livedata?
added
data class DetailEntity(val body: DetatilItem,
val statusCode: Int = 0)
data class DetatilItem(val oily_score: Int = 0,
val full_size_image: String = "",
val price: String = "",
val sensitive_score: Int = 0,
val description: String = "",
val id: Int = 0,
val dry_score: Int = 0,
val title: String = "")
retrofit with no coroutine it seem to be no problem.
But, respnose at your code written to log are the completely different object.
with coroutine, response is DetailEntity
with no coroutine, response is Response<DetailEntity>
if you want same log print, try as below
override fun onResponse(call: Call<DetailEntity>, response: Response<DetailEntity>){
if(response.body()!=null) {
Log.d("TAG",response.body()!!.toString())
data = response.body()!!
}
}
Reference
Retrofit - Response<T>
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.
I got a Product class, which is constructed with a code. This code is made to call the open food facts API to instanciate all the class variables. The fact is the API call is an asynchronous function. So in my main Thread, when i try to access my object parameters, it's empty. Since i cannot interrupt the main Thread, how am i suppose to make a callback on my object instanciation ?
Here is the code
Product.kt
class Product(code: Long) {
val client = OkHttpClient()
var name: String? = null
var imageUrl: String? = null
var packerCode: Int? = null
var packerCity: String? = null
var lat: Int? = null
var long: Int? = null
init {
run("https://fr.openfoodfacts.org/api/v0/produit/$code.json")
}
private fun run(url: String) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException) {}
override fun onResponse(call: Call?, response: Response){
val jsonData = response.body()?.string()
val Jobject = JSONObject(jsonData)
name = Jobject.getJSONObject("product").getString("product_name")
}
})
}
}
GameActivity.kt
class GameActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_game)
val textView: TextView = findViewById(R.id.productName) as TextView
val product = Product(3564700014677)
// Product.name empty
textView.text = product.name
}
First of all we assume you don't want to use MVVM or so architecture. But I really recommend you to read about android architecture components like ViewModel, LiveData stuffs to understand how data flow should be done in android applications.
Coming to basics(Not very clean way), We have to create a interface and pass the reference to Product class and on success you have use the reference to call the activity to update the textview.
Step 1: create interface
interface ProductListener
{
fun onSuccess()
}
Step 2: Implement ProductListener in your activity
class GameActivity : AppCompatActivity(),ProductListener {
...
...
...
override fun onSuccess() {
}
}
Step 3: Pass the listener/activity reference to the Product class
val product = Product(3564700014677, this) //inside your activity
class Product(code: Long, var listener: ProductListener) {
...
...
private fun run(url: String) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call?, e: IOException) {}
override fun onResponse(call: Call?, response: Response){
val jsonData = response.body()?.string()
val Jobject = JSONObject(jsonData)
name = Jobject.getJSONObject("product").getString("product_name")
// invoke listener here to let activity know the response
listener.onSuccess()
}
})
}
}
Step 4: Update the textview inside the onSuccess() implementation of activity
class GameActivity : AppCompatActivity(),ProductListener {
...
...
...
override fun onSuccess() {
textView.text = product.name
}
}
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 }