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)
}
Related
I understand how to handle errors when not using coroutines:
#GET("user/{user}")
fun getHomeData(#Path("user") user: String?): Call<HomeDataBody>
fun getHomeData(id:String, callback: (Boolean, String?) -> Unit)
{
val call = service.getHomeData(id)
call.enqueue( object : Callback<HomeDataBody> {
override fun onResponse(call: Call<HomeDataBody>, response: Response<HomeDataBody>)
{
if (response.isSuccessful)
{
dataMgr.homeData = response.body()!!.user
callback(true, null)
}
else
{
callback(false, response.message())
}
}
override fun onFailure(call: Call<HomeDataBody>, t: Throwable)
{
callback(false, t.message)
}
})
}
But I cannot for the life of me figure out how to do this with coroutines, this is what I have for a coroutine that does not return errors:
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): HomeData
suspend fun getHomeDataCoroutine(id:String) : Pair<Boolean, String>
{
val data = service.getHomeDataCoroutine(id)
if(data != null)
{
dataMgr.homeData = data
}
else
{
return Pair(false, "how do i get the error message??")
}
}
I also attempted this, but when I try to call service.getHomeDataCoroutine I get this error:
java.lang.IllegalArgumentException: Unable to create call adapter for class java.lang.Object
for method RiseServiceRetro.getHomeDataCoroutine
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): Deferred<HomeDataBody>?
sealed class Result<out T : Any>
class Success<out T : Any>(val data: T) : Result<T>()
class Error(val exception: Throwable, val message: String = exception.localizedMessage) : Result<Nothing>()
suspend fun getHomeDataCoroutine(id:String): Result<HomeDataBody>
{
try {
val response = service.getHomeDataCoroutine(id)!!.await()
return Success(response)
} catch (e: Exception) {
return Error(e)
}
}
To handle errors when calling suspend function of Retrofit service wrap it in try-catch block:
#GET("user/{user}")
suspend fun getHomeDataCoroutine(#Path("user") user: String?): HomeDataBody
suspend fun getHomeDataCoroutine(id:String): Pair<Boolean, String> {
return try {
val data = service.getHomeDataCoroutine(id)
dataMgr.homeData = data
Pair(true, "")
} catch(e: Throwable) {
Pair(false, e.message ?: "error")
}
}
I have this function in Kotlin:
class DictionaryWorker constructor(
context: Context,
private val workerParameters: WorkerParameters,
private val apiInterface: ApiInterface
) :
KneuraWorker(context, workerParameters), BaseDataSource {
private var isJobSuccess: Boolean = false
override suspend fun doWorkerJob(): Result = withContext(Dispatchers.IO) {
val call = apiInterface.downloadDictionaryFille(DICTIONARY_FILE_URL)
call!!.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
} else {
Log.d("TAG", "server contact failed")
isJobSuccess = false
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) { }
})
return#withContext if (isJobSuccess)
Result.success()
else
Result.failure()
}
}
What is currently happening:
Before this block-1 below
call!!.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
} else {
Log.d("TAG", "server contact failed")
isJobSuccess = false
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) { }
})
This block-2 executes
return#withContext if (isJobSuccess)
Result.success()
else
Result.failure()
What I am trying to do
Make sure only after block 1 is executed block 2 is executed
Not sure what call!!.enqueue() does, but it's quite likely that it starts another thread and performs it's work asynchronously.
So block 2 is not waiting till block 1 is done.
A really ugly way (which I don't recommend) handling this would be using a CountDownLatch.
But I'd rather add a callback to doWorkerJob():
override fun doWorkerJob(callback: (Result) -> Unit) {
val call = apiInterface.downloadDictionaryFille(DICTIONARY_FILE_URL)
if (call == null) {
callback(Result.failure())
}
call?.enqueue(object : Callback<ResponseBody?> {
override fun onResponse(
call: Call<ResponseBody?>?,
response: Response<ResponseBody?>
) {
if (response.isSuccessful) {
callback(Result.success())
} else {
Log.d("TAG", "server contact failed")
callback(Result.failure())
}
}
override fun onFailure(call: Call<ResponseBody?>?, t: Throwable?) {
callback(Result.failure())
}
})
}
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.
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.
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
}
})