This might be a little weird but I can't figure out how to pass data after an API call. I'm very new to object oriented programming.
fetchedTags is null after the fetchTags() call. How do I get the data?
For example:
class MainActivity : AppCompatActivity() {
var fetchedTags: List<Tags>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fetchTags()
println(fetchedTags[0].name)
fetchBooks()
makeMapOutOfTagsAndBooks()
}
fun fetchTags () {
//some processing
val request = Request.Builder().url(url).build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call?, response: Response?) {
val jsonData = response?.body()?.string()
val gson = GsonBuilder().setPrettyPrinting().create()
val tagList: List<Tags> = gson.fromJson(jsonData, object : TypeToken<List<Tags>>() {}.type)
fetchedTags = tagList
}
}
fetchBooks()
println(fetchedTags[0].name)
Your are calling println(fetchedTags[0].name) right after making your http call, since it's asynchronous your list is null at this point.
call it on your onResponse function
Related
I am developing android app and I am getting error screenshot below when I have implemented network call in mainactivity.kt I want to know where I am making mistake
below my MainActivity.kt
class MainActivity : AppCompatActivity() {
private var adapter: CurrenciesAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
recycler_main.layoutManager = LinearLayoutManager(this#MainActivity)
adapter = CurrenciesAdapter(this)
recycler_main.adapter = adapter
if (isInternetAvailable()) {
getUsersData()
}
}
private fun getUsersData() {
showProgressBar()
var apiInterface: CurrenciesResponse = CurrencyClient().getApiClient()!!.create(
CurrenciesResponse::class.java
)
apiInterface.getCurrencies().enqueue(object : Callback <List<CurrencyResponse>> {
override fun onResponse(
call: Call<List<CurrencyResponse>>,
response: Response<List<CurrencyResponse>>)
{
hideProgressBar()
val currencyResponse = response.body()
adapter?.list = currencyResponse!!
}
override fun onFailure(call: Call<List<CurrencyResponse>>, t: Throwable) {
hideProgressBar()
Log.e("error", t.localizedMessage)
}
})
}
}
what I have done I have changed to response type from <List to CurrencyResponse but I am still getting response below whole gist code
https://gist.github.com/kyodgorbek/d0d9b3749ac64f15b4db87874cfe13e7
Your getCurrencies() method in CurrenciesResponse.class has a return type of CurrenciesResponse whereas it should be List<CurrenciesResponse>.
You need to fix your retrofit's service interface.
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)
}
})
}
I am changing a global variable value inside a method and trying to return it later .
In FetchData.kt ( The called class )
var homeFeed: HomeFeed? = null // the variable that needs to be changed
fun execute() {
val client = OkHttpClient()
val url =
"..."
val request = Request.Builder().url(url).build()
val res = client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
val ch = response?.body?.string()
val gson = GsonBuilder().create()
homeFeed= gson.fromJson(ch, HomeFeed::class.java) // where the change happens
}
})
}
fun GetData(): HomeFeed? {
return homeFeed
}
In MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
btn.setOnClickListener {
val destination = "destination"
val places = FetchData(destination)
places.execute()
val data = places.GetData()
}
}
The problem is that a Null value is assigned to "data" variable in MainActivity as if the returned "homeFeed" variable wasn't changed at all .
I debugged the code to get ensured that no errors occur with api call and the variable is changed inside the method ( but not outside it! )
I am really stuck with that , any help ?
You are calling getData() immediately after making the async request, so it hasn't had a chance to be updated. Asynchronous functions do not immediately return. Some background thread does something (a network request), and in this case calls onResponse when the result is returned some time in the future.
It's just like the listener on your button. The code in the listener isn't run immediately, but some time in the future when the user presses it.
Instead of using this member property, your function can take a callback parameter that it invokes when the result is ready:
fun execute(resultHandler: (HomeFeed) -> Unit) {
//... snip ...
client.newCall(request).enqueue(object : Callback {
// ... snip ...
override fun onResponse(call: Call, response: Response) {
val ch = response?.body?.string()
val gson = GsonBuilder().create()
resultHandler(gson.fromJson(ch, HomeFeed::class.java))
}
})
}
Then when you call it, you pass a lambda that will be called when the result is ready:
btn.setOnClickListener {
val destination = "destination"
val places = FetchData(destination)
places.execute { homeFeedData ->
// Do something with homeFeedData when it arrives
}
}
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
}
}