How to close request connection via CoroutineScope and retrofit - kotlin

I have and error
OkHttpClient: A connection to url was leaked
How could iclose the connection or is my request is correct?
CoroutineScope(Dispatchers.IO).launch {
withContext(Dispatchers.IO) {
val info = service.getInfo()
val carInfo = info.carInfo
}
}
#GET("/info/")
suspend fun getInfo(): Info

Related

Cancellation in Coroutines and Okhttp

So I'm playing around using Coroutines and Okhttp to connect a websocket.
What I've done
// initialise okhttp
fun provideOkHttpClient(): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(RetryInterceptor())
.build()
}
// RetryInterceptor.kt
class RetryInterceptor : Interceptor {
companion object {
private const val RETRIES_LIMIT = 4
}
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var retries = 0
var response: Response?
response = sendRequest(chain, request)
while (response == null && retries <= RETRIES_LIMIT) {
retries++
val sleepTimer = 2.toDouble().pow(retries.toDouble())
Log.d("OkhttpClient", "Connection failed, retry in ${sleepTimer}s")
Thread.sleep(sleepTimer.toLong() * 1000)
response = sendRequest(chain, request)
}
return response ?: Response.Builder()
.request(request)
.code(400)
.build()
}
private fun sendRequest(chain: Interceptor.Chain, request: Request): Response? {
val response: Response
return try {
response = chain.proceed(request)
if (!response.isSuccessful) null else response
} catch (e: IOException) {
null
}
}
}
// define a exception handler
val handler = CoroutineExceptionHandler { _, throwable ->
when (throwable) {
is CancellationException -> {
// cancel the socket connection here
Log.d("CancellationException", "cancelled")
}
else -> onRegisterError(
throwable.localizedMessage ?: "Coroutine Error"
)
}
}
// Then inside ViewModel, fire up the okhttp client
val viewModelScopeJob = viewModelScope.launch(context = handler) {
val someOtherJob = otherContext.launch {
// launch suspend fun connectSocket()
}
}
// Then call cancel inside ViewModel like this:
viewModelScopeJob.cancel()
Problem
viewModelScopeJob is a parent job, when the cancel() is being called, it should cancel its child-jobs and invoke the CancellationException, however it doesn't.
Question
So the coroutine job will not be cancelled because of Thread.sleep() inside interceptor is not cooperative.
My questions is: given RetryInterceptor is located in a separate class, I'm not be able to use methods like delay(), how should I change my code in order to cancel the retry when viewModelScopeJob.cancel() is called?
You need to make two fixes.
First, register a coroutine cancelation listener that cancels the OkHttp call. You can see an example of this in Retrofit’s coroutine integration.
continuation.invokeOnCancellation {
cancel()
}
Next, you need to interrupt the thread sleep when the call is canceled. One way to handle this is with an EventListener. Override cancel to interrupt the OkHttp thread. You can save a reference to that thread with Thread.currentThread() in callStart(). You should also override callEnd() and callFailed() to clear that saved reference.
The Events page has more information on how to register an event listener factory so that each call gets its own EventListener instance.

Kotlin : handle timeout connection okhttp

i am beginner in Kotlin . i try to build app using the OkHttp for make web requests to a rapidapi API downloading files when i click on button . i am already added setting connect Timeout , but i want to handle connect Timeout connection, when connect Timeout i want show to user a dialog to try again or cancel .
private fun StartDwonload() {
val url = editText.text.toString()
val client = OkHttpClient.Builder()
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
val requests = Request.Builder()
.url("xxxxxxx?igurl="+url)
.get()
.addHeader("x-rapidapi-host", "xxxxx")
.addHeader("x-rapidapi-key", "xxxx")
.build()
client.newCall(requests).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {}
override fun onResponse(call: Call, response: Response){
val responseData = response.body()?.string()
runOnUiThread{
try {
var json = JSONObject(responseData)
println("Request Successful!!")
println(json)
val responseObject = json.get("downloadurl").toString()
val request = DownloadManager.Request(Uri.parse((responseObject)))
request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI or DownloadManager.Request.NETWORK_MOBILE)
request.setTitle("Download")
request.setDescription("Dwonloading ...")
request.allowScanningByMediaScanner()
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,"${System.currentTimeMillis()}.mp4")
print(DownloadManager.ACTION_DOWNLOAD_COMPLETE)
val manager = getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
manager.enqueue((request))
progressDialog.dismiss()
} catch (e: JSONException) {
e.printStackTrace()
}
}
}
})
}
any idea please ?
On your OnFailure() method you can check whether your error is SocketTimeoutException like this
override fun onFailure(call: Call, e: IOException) {
if(e is SocketTimeoutException){
//do your work
}
}

How to correctly use suspend fun in Retrofit 2.6.0 coroutines with kotlin

I'm trying to write my network request to use Retrofit 2.6.0 and coroutines suspend fun. But i keep getting null object. This is the first time am trying retrofit 2.6 and coroutines
here is my sample code
Data class
data class ProjectList (val data: List<Project>)
Sample JSON Object
{
"data": [
{
"project_id": "10824",
"project_name": "Bendor Project",
"project_number": "P010824",
"content_items": [
{
"content_id": "235",
"content_name": "Longonot Project",
"content_description": "Valves Example ",
"content_date_updated": "2019-08-31 12:29:00",
"project_id": "10824",
"media_items": []
Network Interface
suspend fun getProjects(#Query("mode") mode: String): ProjectList
Retrofit Client
class RetrofitClient{
private val gson = GsonBuilder()
.setLenient()
.create()
private fun retrofit(): Retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create(gson))
.build()
val retrofitService: ProjectAPI by lazy {
retrofit().create(ProjectAPI::class.java)
}
Repository
class ProjectRepository {
private val client: ProjectAPI = RetrofitClient().retrofitService
suspend fun getProjectData(mode : String) : ProjectList = client.getProjects(mode)
}
livedata in ViewModel
val request : LiveData<ProjectList> = liveData(Dispatchers.IO){
val response = repository.getProjectData(SOURCE_MODE)
Log.e(TAG, "${response.data}")
emit(response)
}
I keep getting null response. Where am i not getting it right?
you don't need to emit values, just call your suspend func in coroutine scope and then create a new LiveData in your ViewModel and lastly call liveData.post() in order to be able to assign the response of network request to your LiveData object.

Kotlin coroutine immediately give an exception if last operation finished with exception

When I was try to login on my service via retrofit. When my service is off, 10 seconds after clicking the button I got an SocketTimeoutException exception.
So far everything is normal but again, I clicked the button again after the error gave the same error immediately. What's wrong?
interface LoginService {
#FormUrlEncoded
#POST("/login")
fun login(#Field("id") id: String, #Field("pw") pw: String): Deferred<Response<User>>
}
class LoginViewModel : ViewModel() {
private var job: Job = Job()
private val scope: CoroutineScope = CoroutineScope(Dispatchers.Main + job)
private val service by lazy { RetrofitApiFactory().create(LoginService::class.java) }
private val excHandler = CoroutineExceptionHandler { _, throwable ->
Timber.e(throwable);
}
fun doLogin(id: String, pw: String) {
scope.launch(excHandler) {
val response = service.login(id, pw).await()
if (response.isSuccessful) {
response.body()
?.let { user -> doOnSuccess(user) }
?: doOnError(InvalidUserException())
} else doOnError(Exception())
}
}
private fun CoroutineScope.doOnError(e: Throwable) {
excHandler.handleException(coroutineContext, e)
}
private fun doOnSuccess(user: User) {
...
}
override fun onCleared() {
job.cancel()
}
}
You need to change your CoroutineScope to not re-use the same Job. It's already considered as failed, so it will not even begin executing.
See related issue on github.

Ktor unable to receive POST body on Server

I have the following Server and Client code:
Server:
fun Application.main() {
install(DefaultHeaders)
install(CallLogging)
install(Routing) {
post("/") {
val requestBody = call.receiveText()
println("Received $requestBody")
call.respond("Hello from server - received $requestBody")
}
}
}
fun main(args: Array<String>) {
embeddedServer(Netty, 8080) {
main()
}.start(wait = true)
}
Client:
fun main(args: Array<String>) = runBlocking {
HttpClient(CIO).use {
val postResult = it.post<String>("http://localhost:8080/") {
body = "Client Hello"
}
println(postResult)
}
}
So, the client just sends "Client Hello" to the server in the POST-body, and the Server responds to that.
But I didn't see the content of the body on the server-side. What am I doing wrong?
The call.receiveText() is always empty.
The issue fixed in recent alphas(>= 0.9.2-alpha-5) and would appear in next 0.9.2 release soon.
And if you have parameters in your body request, try it:
val requestBody = call.receiveParameters()
val value = requestBody["key_name"]