This function get data from API.
I don't understand its process.
Here the problem,
it returned weatherModel first and then it execute onResponse later.
It show in logcate Log.d("data", "Here") first
My function
fun get(city: String?): WeatherModel? {
var weatherModel: WeatherModel? = WeatherModel()
val retrofit =
retrofit2.Retrofit.Builder().baseUrl(Util.BASE_URL).addConverterFactory(GsonConverterFactory.create())
.build()
val service = retrofit.create(WeatherInterface::class.java)
val call = service.searchCity(city!!, "metric", "789af9673b393eb97f2acdea022f2005")
call.enqueue(object : Callback<WeatherModel> {
override fun onFailure(call: Call<WeatherModel>, t: Throwable) {
Log.d("data", "error ${t.message}")
}
override fun onResponse(call: Call<WeatherModel>, response: Response<WeatherModel>) {
Log.d("data", "success ${response.body()!!.weather!!.size}")
weatherModel = response.body()
data!!.getDataTrigger(weatherModel!!)
}
})
Log.d("data", "Here")
return weatherModel
}
So what is the problem?
It show in logcate Log.d("data", "Here") first
call.enqueue is a asynchronous call, which means it doesn't run on main thread, that's the reason you will get Log.d("data", "Here") first.
This is the reason you are getting null returned
If you are using this method asynchronously or in some service and you want to execute synchronously then kindly use call.execute but this will only work if you are using in other thread apart from Main thread as network operation are not allowed on main thread, in this case this method wont return null.
Either you should use live data or an interface for the callback to receive WeatherModel.
Related
I saw many many stackoverflow about the same question but I still stuck in my circle.
I'm trying to return a user token from API. The response is success and I can LOG it. but I can't return it because the function will return before the enqueue completed (this is what I think)
Please see the code below:
fun login(username: String, password: String): Result<LoggedInUser> {
val client = OkHttpClient.Builder().build()
val retrofit = Retrofit.Builder()
.baseUrl("https:api/customers/")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build()
val userApi= retrofit.create<UserApiKotlin>()
var pass: String ="SecYourPa$$"
val r= UserRequestModel("coding", pass)
var fakeUser = LoggedInUser(
java.util.UUID.randomUUID().toString(), "Jane Doe",
"gfgfgfgf", "trtrtr", "trtrtrtr", "abababababab"
)
var sss="1"
userApi.login(r).enqueue(object : Callback<LoggedInUser> {
override fun onResponse(
call: Call<LoggedInUser>, response: Response<LoggedInUser>
) {
fakeUser = LoggedInUser(
java.util.UUID.randomUUID().toString(), "Jane Doe",
"gfgfgfgf", "trtrtr", "trtrtrtr", "xcxcxcxc"
)
sss = "tttttt"
fakeUser = response.body()!!
Log.e("inside Retrofit", sss)
}
override fun onFailure(call: Call<LoggedInUser>, t: Throwable) {
Log.e("fauluer", "Unable to submit post to API.")
}
})
Log.e("outside retrofit", sss)
return Result.Success(fakeUser)
}
and this the output show that it will show and execute the function (and return the initial value) before api get the value from server
userApi.login(r).enqueue is an asynchronous call which will run on another thread. A network call can take severall milliseconds or seconds so you won't get the result immediately.
The code happens in this order:
You call userApi.login(r).enqueue -> Network call starts in parallel on another thread
You return Result.Success(fakeUser)
The network call finishes -> Code inside your callback in onResponse or onFailure is executed
As you see, you are returning a result before the network call even finishes. Your log shows the same.
This is the object where all my API calls are made. Sometimes when I make a call to this function, I receive a NetworkOnMainThread exception. It doesn't happen every time. I'm confused because I've made this function asynchronous... why am I still getting this exception?
object APICaller{
private const val apiKey = "API_KEY_HERE"
//Live Data Objects
var errorCode = MutableLiveData<Int>()
var fetchedResponse = MutableLiveData<Response>()
//Asynchronous network call
suspend fun networkCall(query: String) = withContext(Dispatchers.Default){
val apiURL = "API_URL_HERE"
try{
//Get response
val response = OkHttpClient().newCall(Request.Builder().url(apiURL).build()).execute()
if(response.isSuccessful){
//UI changes (including changes to LiveData values) must be performed on main thread.
Handler(Looper.getMainLooper()).post{
fetchedResponse.value = response
}.also{
Log.i("Response Succ", response.toString())
}
} else {
Handler(Looper.getMainLooper()).post{
errorCode.value =
ToastGenerator.REQUEST_ERROR
}.also{
Log.i("Response Fail", response.toString())
}
}
//Catch any thrown network exceptions whilst attempting to contact API
} catch(e: Exception){
Handler(Looper.getMainLooper()).post{
errorCode.value =
ToastGenerator.NETWORK_ERROR
}.also{
Log.i("Network Fail", e.message.toString())
}
}
}
}
There are three other classes that make utilise return value from the APICaller networkCall() function. The first is a ViewModel that references it directly.
class BrowseViewModel: ViewModel() {
//LiveData Objects
//Transformations listen to LiveData in APICaller and map it to LiveData in this ViewModel
var errorCode: LiveData<Int>? = Transformations.map(APICaller.errorCode){ code ->
return#map code
}
var obtainedResponse: LiveData<String> = Transformations.map(APICaller.fetchedResponse){ response ->
return#map response.body()?.string()
}
//Upon a search request, make a network call
fun request(query: String) {
GlobalScope.launch{
APICaller.networkCall(query)
}
}
//Convert API response to GameData object
fun handleJSONString(jsonString: String, file: String) : List<GameData>{
return DataTransformer.JSONToGameData(JSONObject(jsonString), JSONObject(file))
}
}
The second is an fragment that calls the ViewModel's function.
fun request(query: String){
browseViewModel.request(query)
progressSpinner?.visibility = View.VISIBLE
}
The third is an Activity that calls upon the Fragment's function.
private fun makeRequest(query: String){
browseFragment.let{
supportFragmentManager.beginTransaction().replace(R.id.fragmentContainer, it).commit()
it.request(query)
}
}
Could it be related to these other functions?
Any feedback is greatly appreciated. Thank you in advance :)
I don't see exactly what could go wrong in your code, but it could definitely be much cleaner, which possibly would resolve your issue, or at least make it easier to find the error.
A good strategy with suspend functions is to design them to always be called from a Main dispatcher. Then you can freely make UI calls in them and wrap only the background parts with withContext. So your above function could move the withContext() down to only wrap the execute() call, and all your Handler usage could be removed. (Incidentally, those would have been cleaner using withContext(Dispatchers.Main).)
However, Retrofit already provides a suspend function version of making a call, so you don't even need the withContext wrapper. Just use await() instead of execute() and your suspend function would collapse down to:
suspend fun networkCall(query: String) {
val apiURL = "API_URL_HERE"
try{
val response = OkHttpClient().newCall(Request.Builder().url(apiURL).build()).await()
if (response.isSuccessful){
Log.i("Response Succ", response.toString())
fetchedResponse.value = response
} else {
Log.i("Response Fail", response.toString())
errorCode.value = ToastGenerator.REQUEST_ERROR
}
} catch(e: Exception){
Log.i("Network Fail", e.message.toString())
errorCode.value = ToastGenerator.NETWORK_ERROR
}
}
And then instead of using GlobalScope, you should use a viewModelScope or lifecycleScope so your coroutines don't leak UI components. And the await() suspending function above supports cancellation, so if for instance your ViewModel is destroyed due to the associated Fragment or Activity going out of scope, your network call will be cancelled automatically for you.
fun request(query: String) {
viewModelScope.launch {
APICaller.networkCall(query)
}
}
If the problem persists, study your stack trace closely to see where you might be making the error.
I'm trying to use the Android MVVM pattern with a repository class and Retrofit for network calls. I have the common problem that I can't get the coroutine to wait for the network response to return.
This method is in my ViewModel class:
private fun loadConfigModel() {
val model = runBlocking {
withContext(Dispatchers.IO) {
configModelRepository.getConfigFile()
}
}
configModel.value = model
}
In ConfigModelRepository, I have this:
suspend fun getConfigFile(): ConfigModel {
val configString = prefs.getString(
ConfigViewModel.CONFIG_SHARED_PREF_KEY, "") ?: ""
return if (configString.isEmpty() || isCacheExpired()) {
runBlocking { fetchConfig() }
} else {
postFromLocalCache(configString)
}
}
private suspend fun fetchConfig(): ConfigModel {
return suspendCoroutine { cont ->
dataService
.config() // <-- LAST LINE CALLED
.enqueue(object : Callback<ConfigModel> {
override fun onResponse(call: Call<ConfigModel>, response: Response<ConfigModel>) {
if (response.isSuccessful) {
response.body()?.let {
saveConfigResponseInSharedPreferences(it)
cont.resume(it)
}
} else {
cont.resume(ConfigModel(listOf(), listOf()))
}
}
override fun onFailure(call: Call<ConfigModel>, t: Throwable) {
Timber.e(t, "config fetch failed")
cont.resume(ConfigModel(listOf(), listOf()))
}
})
}
}
My code runs as far as dataService.config(). It never enters onResponse or onFailure. The network call goes and and returns properly (I can see this using Charles), but the coroutine doesn't seem to be listening for the callback.
So, my question is the usual one. How can I get the coroutines to block such that they wait for this callback from Retrofit? Thanks.
The problem must be that response.body() returns null since that is the only case that is missing a call to cont.resume(). Make sure to call cont.resume() also in that case and your code should at least not get stuck.
But like CommonsWare points out, even better would be to upgrade to Retrofit 2.6.0 or later and use native suspend support instead of rolling your own suspendCoroutine logic.
You should also stop using runBlocking completely. In the first case, launch(Dispatchers.Main) a coroutine instead and move configModel.value = model inside of it. In the second case you can just remove runBlocking and call fetchConfig() directly.
I'm trying to releaze function for authorization my android app. I know there are many similar questions, but I'm interesting exectly to get function witch return "success" or "failure".
override fun authorization(login: String, password: String): String {
var result: String = "none"
val call: Call<UserAuthorizationDataModelRetrofit> =
dataBase.getWordApiRetrofit().userAuthorization(login, password)
call.enqueue(object : Callback<UserAuthorizationDataModelRetrofit> {
override fun onResponse(
call: Call<UserAuthorizationDataModelRetrofit>?,
response: Response<UserAuthorizationDataModelRetrofit>?
) {
val dataModel = response?.body()
result = dataModel?.result ?: "failure"
}
override fun onFailure(call: Call<UserAuthorizationDataModelRetrofit>?, t: Throwable?) {
result = "failure"
}
})
return result
}
I'm expecting function return "success" or "failure" acording to response of http request. But http request is asynchronous and every time I get "none". Can anybody help me?
The function .enqueue is asynchronous. You will always get a "none" because enqueue is always not finished when authorization returns.
You can do two things.
One is use enqueue and do all your work inside onResponse, open new Activity for the user, set your View or show a Toast, etc.
Another is use .execute. But you will have to call authorization in another Thread and do your work in there. Note that this is asynchronous and you should not do UI stuff here.
I am new to Kotlin and I am making a method that makes a call to an interface of Endpoints and uses one of the methods present there. I am using Observable<> instead of Call<> into the response. I wanted to know how to obtain the response body() in the "result" above. This is my method
private fun refreshUser(userLogin: String) {
executor.execute {
// Check if user was fetched recently
val userExists = userDao.hasUser(userLogin, getMaxRefreshTime(Date())) != null
// If user have to be updated
if (!userExists) {
disposable = endpoints.getUser(userLogin)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> /*Get the response body() HERE*/},
{ error -> Log.e("ERROR", error.message) }
)
}
}
}
It all depends on how you have defined the Retrofit interface. In order to get the Response you need to return something from the interface that looks like:
fun getUsers() : Observable<Response<User>>
Then inside { result -> /*Get the response body() HERE*/}, you will get something of the form Response<User>, which has the response's body.
Also to note, you do not need to enclosing executor if you leverage Room for the dao interactions; it has RxJava support. You can use RxJava operators to combine the dao lookup with the server call.
See this tutorial
https://medium.freecodecamp.org/rxandroid-and-kotlin-part-1-f0382dc26ed8
//Kotlin
Observable.just("Hello World")
.subscribeOn(Schedulers.newThread())
//each subscription is going to be on a new thread.
.observeOn(AndroidSchedulers.mainThread()))
//observation on the main thread
//Now our subscriber!
.subscribe(object:Subscriber<String>(){
override fun onCompleted() {
//Completed
}
override fun onError(e: Throwable?) {
//TODO : Handle error here
}
override fun onNext(t: String?) {
Log.e("Output",t);
}
})
if you wanna use retrofit 2 and rxjava 2
https://medium.com/#elye.project/kotlin-and-retrofit-2-tutorial-with-working-codes-333a4422a890
interface WikiApiService {
#GET("api.php")
fun hitCountCheck(#Query("action") action: String,
#Query("format") format: String,
#Query("list") list: String,
#Query("srsearch") srsearch: String):
Observable<Model.Result>
}
Observable is the class response.
private fun beginSearch(srsearch: String) {
disposable =
wikiApiServe.hitCountCheck("query", "json", "search", srsearch)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result -> showResult(result.query.searchinfo.totalhits) },
{ error -> showError(error.message) }
)
}
If, as you mentioned to #Emmanuel, the return type of your getUser() method is Observable<Response<User>> then calling result.body() will yield the resulting User.
{ result ->
val user: User = result.body()
}
If however, you are looking for the the raw response, you can instead call result.raw().body(); which will return an okhttp3.ResponseBody type.
{ result ->
val body: ResponseBody = result.raw().body()
val text: String = body.string()
}