I tried to use retrofit to get the response data of web api, but the result of response data seems not sync as the same.
fun fetchData(): LiveData<String> {
val auth = Credentials.basic(name, pass)
val request: Call<JsonElement> = webApi.fetchData()
val response: MutableLiveData<String> = MutableLiveData()
request.enqueue(object : Callback<JsonElement> {
override fun onFailure(call: Call<JsonElement>, t: Throwable) {
Log.e(TAG, "Failed to fetch token", t)
}
override fun onResponse(call: Call<JsonElement>, response: Response<JsonElement>) {
response.value = response.body()
Log.d(TAG, "response: ${response.value}") // I can get the result of response
}
})
return response // But the function return with the null
}
You might need handler.
The enqueue method doesn´t wait to the response so is normal the null result in your return response.
To solve this, you doesn´t need to return nothing, only put your livedata in the scope class and update the value:
class YourClass {
private var responseMutableLiveData: MutableLiveData<String> = MutableLiveData()
val responseLiveData: LiveData<String>
get() = responseMutableLiveData
fun fetchData() {
webApi.fetchData().enqueue(object : Callback<JsonElement> {
override fun onFailure(call: Call<JsonElement>, t: Throwable) {
Log.e(TAG, "Failed to fetch token", t)
}
override fun onResponse(call: Call<JsonElement>, response: Response<JsonElement>) {
responseMutableLiveData.postValue(response.body())
Log.d(TAG, "response: ${response.value}")
}
})
}
}
The livedata is observed and, when the value changes, then the other class reacts to it.
Related
When working with the MVVM pattern in Android developments, we create a repository class where we execute all the network requests. The problem is since retrofit's .enqueue() method is asynchronous, my method that calls .enqueue doesn't wait until the callback is obtained(which is pretty logical) and returns null.
One way to solve this problem is to pass MutableLiveData object to my repository method and set its value in the callback, but I don't want to observe all my ViewModel properties in my view(fragment).
What is the common way to solve this problem?
fun createRoute(newRoute: RouteToSend): String {
var responseMessage: String? = null
webService.createRoute(authToken!!, newRoute).enqueue(object: Callback<Message> {
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let { responseMessage = it.message }
}
})
return responseMessage!!
}
Pass a callback as an argument, e.g.
createRoute(newRoute: RouteToSend, callback: CreateRouteListener)
with
interface CreateRouteListener {
fun onFailure()
fun onResponse(response: String)
}
and call the corresponding method when the async process finishes:
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
callback.onFailure()
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let {
responseMessage = it.message
callback.onResponse(responseMessage)
}
}
Calling createRoute will then look like this:
createRoute(RouteToSend(), object: CreateRouteListener {
override fun onFailure() {
// handle failure
}
override fun onResponse(response: String) {
// handle response
}
}
Yes, using MutableLiveData is one way, on the other hand using callback mechanism is another and more suitable way.
If you want to use callbacks you can change your method like
fun createRoute(newRoute: RouteToSend, callback : (String?) -> Unit): String {
var responseMessage: String? = null
webService.createRoute(authToken!!, newRoute).enqueue(object: Callback<Message> {
override fun onFailure(call: Call<Message>, t: Throwable) {
Log.e(TAG, t.message!!)
callback(responseMessage)
}
override fun onResponse(call: Call<Message>, response: Response<Message>) {
response.body()?.let { responseMessage = it.message
callback(responseMessage)}
}
})
}
then you can call your createRoute method like this
createRoute(route_to_send_variable,
callback = {
it?.let {
// use the response of your createRoute function here
}
})
When I use retrofit request to get value from web api, then assign it to liveData. But it always return null before it set value to responseLiveData in onResponse.
fun fetchContents(): LiveData<String> {
val responseLiveData: MutableLiveData<String> = MutableLiveData()
val flickrRequest: Call<String> = flickrApi.fetchContents()
flickrRequest.enqueue(object : Callback<String> {
override fun onFailure(call: Call<String>, t: Throwable) {
Log.e(TAG, "Failed to fetch photos", t)
}
override fun onResponse(
call: Call<String>,
response: Response<String>
) {
Log.d(TAG, "Response received")
responseLiveData.value = response.body()
}
})
return responseLiveData
}
// then assign to the liveData in viewModel
val flickrLiveData: LiveData<String> = FlickrFetchr().fetchContents()
This code from the big nerd ranch guide, I find it can not work outOfBox, so I issue this error.
I want to make my network request synchronous because the input of second request comes from the output of first request.
override fun onCreate(savedInstanceState: Bundle?) {
retrofit1 =Retrofit.Builder()
.baseUrl("https://jsonplaceholder.typicode.com/").addConverterFactory(GsonConverterFactory.create()).build()
retrofit2 =Retrofit.Builder()
.baseUrl("https://samples.openweathermap.org/").addConverterFactory(GsonConverterFactory.create()).build()
button.setOnClickListener { view ->
CoroutineScope(IO).launch {
fakeApiRequest()
}}
In my fakeApiRequest(),I am making two network request.
private suspend fun fakeApiRequest() {
val result1 :Geo?= getResult1FromApi()
val result2: Long? = getResult2FromApi(result1)}
Since,this is an asynchronous call,I am getting Null Pointer Exception in my getResult2FromApi(result1) method because the argument passed is null.
In order to fix this issue,I had to add delay(1500) in first call.
private suspend fun getResult1FromApi(): Geo? {
val service:CallService = retrofit1!!.create(CallService::class.java)
val call = service.getUsers()
call.enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
g = users.get(0).address.geo
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
}
})
delay(1500)
return g
}
-----------------------------------------------------------
private suspend fun getResult2FromApi(result1: Geo?): Long? {
val service2:CallService = retrofit2!!.create(CallService::class.java)
val call2 = service2.getWeather(result1?.lat!!, result1.lng,"b6907d289e10d714a6e88b30761fae22")
call2.enqueue(object : Callback<WeatherData> {
override fun onResponse(call: Call<WeatherData>, response: Response<WeatherData>) {
}
override fun onFailure(call: Call<WeatherData>, t: Throwable) {
}
})
return dt
}
Is there anyway I can make this synchronous, so that I don't have to pass any delay time.
You haven't implemented the suspendable function correctly. You must use suspendCoroutine:
suspend fun getResult1FromApi(): Geo? = suspendCoroutine { continuation ->
val service = retrofit1!!.create(CallService::class.java)
service.getUsers().enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
continuation.resume(response.result.getOrNull(0)?.address?.geo)
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
continuation.resumeWithException(t)
}
})
}
Now your function is synchronous and returns a Geo object.
I'm trying to create a POST request to login a user with email and password parameters inside a JSON.
I'm getting the following error:
AuthService.kt
interface AuthService {
#POST("/user/signin")
fun login(#Body request: JSONObject) : Call<PostLoginResponse>
}
PostLoginResponse.kt
data class PostLoginResponse(
val access_token: String,
val expires_in: Number,
val token_type: String
)
LoginActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.login)
email = findViewById(R.id.input_email)
password = findViewById(R.id.input_password)
signinButton = findViewById(R.id.btn_login)
signinButton.setOnClickListener {
val authJsonData = JSONObject()
authJsonData.put("email", email.text.toString().trim())
authJsonData.put("password", password.text.toString().trim())
login(authJsonData);
}
}
private fun login(jsonData: JSONObject) {
val call = App.authService.login(jsonData)
call.enqueue(object : Callback<PostLoginResponse> {
override fun onResponse(call: Call<PostLoginResponse>, response: Response<PostLoginResponse>) {
Log.i(TAG, "login() - onResponse() Result = ${response?.body()}")
}
override fun onFailure(call: Call<GetSitesResponse>, t: Throwable) {
Log.e(TAG, "login() - onFailure() ", t)
}
})
}
Change the call argument type from Call<GetSitesResponse> to Call<PostLoginResponse> in the onFailure method:
override fun onFailure(call: Call<PostLoginResponse>, t: Throwable) {
Log.e(TAG, "login() - onFailure() ", t)
}
write this code :
fun getStoreTitles():List<sample> {
var responseResult:List<sample>
responseResult= listOf(sample("","",""))
val service = getRetrofitInstance()!!.create(GetDataService::class.java)
val call = service.getAllPhotos()
call.enqueue(object : Callback<List<sample>> {
override fun onResponse(call: Call<List<sample>>, response: Response<List<sample>>) {
responseResult=response.body()!!
var t=0
}
override fun onFailure(call: Call<List<sample>>, t: Throwable) {
/*progressDoalog.dismiss()*/
//Toast.makeText(this#MainActivity, "Something went wrong...Please try later!", Toast.LENGTH_SHORT).show()
}
});
return responseResult
}
and want to call that method from main activity with this way:
var responseResult:List<sample>
val FrameWork=StoreTitle()
responseResult=FrameWork.getStoreTitles()
when run the app,retrofit run the successful but nothing return to the responseResult and that is null,i think retrofit run other thread and that's reason.how can i solve that problem?
Update your api call method:
fun getStoreTitles(callback : Callback<List<sample>>) {
var responseResult:List<sample>
responseResult= listOf(sample("","",""))
val service = getRetrofitInstance()!!.create(GetDataService::class.java)
val call = service.getAllPhotos()
call.enqueue(callback);
}
you have to call like this :
val FrameWork=StoreTitle()
FrameWork.getStoreTitles(object : Callback<List<sample>> {
override fun onResponse(call: Call<List<sample>>, response: Response<List<sample>>) {
val responseResult : List<sample>? =response.body()
//handle your success
}
override fun onFailure(call: Call<List<sample>>, t: Throwable) {
//handle your failure
}
})