54

How to make an API call in Android with Kotlin?

I have heard of Anko . But I want to use methods provided by Kotlin like in Android we have Asynctask for background operations.

1
  • 2
    You can also use AsyncTask with Kotlin Commented Jun 13, 2017 at 15:14

11 Answers 11

77

AsyncTask is an Android API, not a language feature that is provided by Java nor Kotlin. You can just use them like this if you want:

class someTask() : AsyncTask<Void, Void, String>() {
    override fun doInBackground(vararg params: Void?): String? {
        // ...
    }

    override fun onPreExecute() {
        super.onPreExecute()
        // ...
    }

    override fun onPostExecute(result: String?) {
        super.onPostExecute(result)
        // ...
    }
}

Anko's doAsync is not really 'provided' by Kotlin, since Anko is a library that uses language features from Kotlin to simplify long codes. Check here:

If you use Anko your code will be similar to this:

doAsync {
    // ...
}
Sign up to request clarification or add additional context in comments.

2 Comments

how we can prevent this warning "leak might occur make the async task static" ?
@Killer Don't put it in classes that can stay too long because of the AsyncTask (such as Activity), unless you keep them in check, and cancel all of the instances of the AsyncTask when your class should be destroyed.
37

You can get a similar syntax to Anko's fairly easy. If you just wan't the background task you can do something like

class doAsync(val handler: () -> Unit) : AsyncTask<Void, Void, Void>() {
    override fun doInBackground(vararg params: Void?): Void? {
        handler()
        return null
    }
}

And use it like

doAsync {
    yourTask()
}.execute()

2 Comments

I recently migrated to Kotlin. So I have some doubts.. How can you be sure that Memory leaks won't happen when you call this doAsync from Activity or Fragment. Where will you get the result? Anko's doAsync is lifecycle aware right? How can we bring that in out doAsyn?
@Mohanakrrishna This is an old answer and I would never use this.. Use e.g. LiveData and all your worries are gone. And since it's Kotlin - use coroutines and all your worries are gone.
26

Here is an example that will also allow you to update any UI or progress displayed to the user.

Async Class

class doAsync(val handler: () -> Unit) : AsyncTask<Void, Void, Void>() {
    init {
        execute()
    }

    override fun doInBackground(vararg params: Void?): Void? {
        handler()
        return null
    }
}

Simple Usage

doAsync {
    // do work here ...

    myView.post({
        // update UI of myView ...
    })
}

2 Comments

Thanks mate. This works like a charm for people using Kotlin. Saved my day.
'AsyncTask<Params : Any!, Progress : Any!, Result : Any!>' is deprecated. Deprecated in Java
19

AsyncTask was deprecated in API level 30. To implement similar behavior we can use Kotlin concurrency utilities (coroutines).

Create extension function on CoroutineScope:

fun <R> CoroutineScope.executeAsyncTask(
        onPreExecute: () -> Unit,
        doInBackground: () -> R,
        onPostExecute: (R) -> Unit
) = launch {
    onPreExecute()
    val result = withContext(Dispatchers.IO) { // runs in background thread without blocking the Main Thread
        doInBackground()
    }
    onPostExecute(result)
}

Now it can be used on any CoroutineScope instance, for example, in ViewModel:

class MyViewModel : ViewModel() {

      fun someFun() {
          viewModelScope.executeAsyncTask(onPreExecute = {
              // ...
          }, doInBackground = {
              // ...
              "Result" // send data to "onPostExecute"
          }, onPostExecute = {
              // ... here "it" is a data returned from "doInBackground"
          })
      }
  }

or in Activity/Fragment:

lifecycleScope.executeAsyncTask(onPreExecute = {
      // ...
  }, doInBackground = {
      // ...
      "Result" // send data to "onPostExecute"
  }, onPostExecute = {
      // ... here "it" is a data returned from "doInBackground"
  })

To use viewModelScope or lifecycleScope add next line(s) to dependencies of the app's build.gradle file:

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$LIFECYCLE_VERSION" // for viewModelScope
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$LIFECYCLE_VERSION" // for lifecycleScope

Comments

9
package com.irontec.kotlintest

import android.os.AsyncTask
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.Menu
import android.view.MenuItem
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
import org.json.JSONObject
import java.io.BufferedInputStream
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL


class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        GetWeatherTask(this.text).execute()
    }

    class GetWeatherTask(textView: TextView) : AsyncTask<Unit, Unit, String>() {

        val innerTextView: TextView? = textView

        override fun doInBackground(vararg params: Unit?): String? {
            val url = URL("https://raw.githubusercontent.com/irontec/android-kotlin-samples/master/common-data/bilbao.json")
            val httpClient = url.openConnection() as HttpURLConnection
            if (httpClient.responseCode == HttpURLConnection.HTTP_OK) {
                try {
                    val stream = BufferedInputStream(httpClient.inputStream)
                    val data: String = readStream(inputStream = stream)
                    return data
                } catch (e: Exception) {
                    e.printStackTrace()
                } finally {
                    httpClient.disconnect()
                }
            } else {
                println("ERROR ${httpClient.responseCode}")
            }
            return null
        }

        fun readStream(inputStream: BufferedInputStream): String {
            val bufferedReader = BufferedReader(InputStreamReader(inputStream))
            val stringBuilder = StringBuilder()
            bufferedReader.forEachLine { stringBuilder.append(it) }
            return stringBuilder.toString()
        }

        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)

            innerTextView?.text = JSONObject(result).toString()

            /**
             * ... Work with the weather data
             */

        }
    }

    override fun onCreateOptionsMenu(menu: Menu): Boolean {
        menuInflater.inflate(R.menu.menu_main, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId
        if (id == R.id.action_settings) {
            return true
        }
        return super.onOptionsItemSelected(item)
    }
}

link - Github Irontec

1 Comment

That looks very wrong. Even though the asynctask is an inner class... I don't see where you check the state of the activity in onPostExecute? Also, stop using Asynctask all together.
5

This is how I do in my projects to avoid memory leaks:

I created an abstract base Async Task class for Async loading

import android.os.AsyncTask

abstract class BaseAsyncTask(private val listener: ProgressListener) : AsyncTask<Void, Void, String?>() {

    interface ProgressListener {
        // callback for start
        fun onStarted()

        // callback on success
        fun onCompleted()

        // callback on error
        fun onError(errorMessage: String?)

    }

    override fun onPreExecute() {
        listener.onStarted()

    }

    override fun onPostExecute(errorMessage: String?) {
        super.onPostExecute(errorMessage)
        if (null != errorMessage) {
            listener.onError(errorMessage)
        } else {
            listener.onCompleted()
        }
    }
}

USAGE:

Now every time I have to perform some task in background, I create a new LoaderClass and extend it with my BaseAsyncTask class like this:

class LoadMediaTask(listener: ProgressListener) : BaseAsyncTask(listener) {

    override fun doInBackground(vararg params: Void?): String? {

        return VideoMediaProvider().allVideos
    }
}

Now you can use your new AsyncLoader class any where in your app.

Below is an example to Show/Hide progress bar & handle Error/ Success scenario:

   LoadMediaTask(object : BaseAsyncTask.ProgressListener {
            override fun onStarted() {
                //Show Progrss Bar
                loadingBar.visibility = View.VISIBLE
            }

            override fun onCompleted() {
                // hide progress bar
                loadingBar.visibility = View.GONE
                // update UI on SUCCESS
                setUpUI()
            }

            override fun onError(errorMessage: String?) {
                // hide progress bar
                loadingBar.visibility = View.GONE
                // Update UI on ERROR
                Toast.makeText(context, "No Videos Found", Toast.LENGTH_SHORT).show()
            }

        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)

2 Comments

how to read the value returned from task? i call http and there is string response thnkx
not the latest solution library, thats why it suited my old code! thanks
3

I always use this form:

open class LoadingProducts : AsyncTask<Void, Void, String>() {

private var name = ""

    override fun doInBackground(vararg p0: Void?): String {

        for (i in 1..100000000) {
            if (i == 100000000) {
                name = "Hello World"
            }
        }
        return name
    }
}

You invoke it in the following way:

loadingProducts = object : LoadingProducts() {
        override fun onPostExecute(result: String?) {
            super.onPostExecute(result)

            Log.e("Result", result)
        }
    }

loadingProducts.execute()

I use the open so that I can call the onPostExecute method for the result.

Comments

3

I spent a full day trying to figure how to get back the result produced by an Async Task : co-routines was my solution !!!

First, create your AsyncTask Object ... Do not forget to use corrects parameter type instead all Any

@SuppressLint("StaticFieldLeak")
class AsyncTaskExample(private var activity: MainActivity?) : AsyncTask<Any, Int, Any?>() {

    override fun onPreExecute() {
        super.onPreExecute()
        // do pre stuff such show progress bar
    }

    override fun doInBackground(vararg req: Any?): Any? {

        // here comes your code that will produce the desired result
        return result 

    }

    // it will update your progressbar
    override fun onProgressUpdate(vararg values: Int?) {
        super.onProgressUpdate(*values)

    }


    override fun onPostExecute(result: Any?) {
        super.onPostExecute(result)

        // do what needed on pos execute, like to hide progress bar
        return
    }

}

and Then, call it ( in this case, from main activity )

var task = AsyncTaskExample(this)
var req = { "some data object or whatever" }

GlobalScope.launch( context = Dispatchers.Main){

   task?.execute(req)
}

GlobalScope.launch( context = Dispatchers.Main){

   println( "Thats the result produced by doInBackgorund: " +  task?.get().toString() )
}

Comments

0

if in the case you want to do it without using Anko and the correct way is to use the following way

open class PromotionAsyncTask : AsyncTask<JsonArray, Void, MutableList<String>>() {

private lateinit var out: FileOutputStream
private lateinit var bitmap: Bitmap
private lateinit var directory: File
private var listPromotion: MutableList<String> = mutableListOf()

override fun doInBackground(vararg params: JsonArray?): MutableList<String> {

    directory = Environment.getExternalStoragePublicDirectory("Tambo")

    if (!directory.exists()) {
        directory.mkdirs()
    }

    for (x in listFilesPromotion(params[0]!!)) {
        bitmap = BitmapFactory.decodeStream(URL(x.url).content as InputStream)
        out = FileOutputStream(File(directory, "${x.name}"))

        bitmap.compress(Bitmap.CompressFormat.PNG, 100, out)
        out.flush()
        out.close()

        listPromotion.add(File(directory, "${x.name}").toString())
    }

    return listPromotion
}

private fun listFilesPromotion(jsonArray: JsonArray): MutableList<Promotion> {
    var listString = mutableListOf<Promotion>()

    for (x in jsonArray) {
        listString.add(Promotion(x.asJsonObject.get("photo")
                .asString.replace("files/promos/", "")
                , "https://tambomas.pe/${x.asJsonObject.get("photo").asString}"))
    }

    return listString}
}

and the way to execute it is as follows

promotionAsyncTask = object : PromotionAsyncTask() {
                    override fun onPostExecute(result: MutableList<String>?) {
                        super.onPostExecute(result)
                        listFile = result!!

                        contentLayout.visibility = View.VISIBLE
                        progressLottie.visibility = View.GONE
                    }
                }
                promotionAsyncTask.execute(response!!.body()!!.asJsonObject.get("promos").asJsonArray)

Comments

0

I use LaunchedEffect in a composable

LaunchedEffect ("http_get") {
    withContext (Dispatchers.IO) {
        http_get() }}

and rememberCoroutineScope in a callback

val scope = rememberCoroutineScope()
Button (
    onClick = {
        scope.launch {
            withContext (Dispatchers.IO) {
                http_get() }}})

It seems to work, but I don't know why.

Comments

-3
  private fun updateUI(account: GoogleSignInAccount?) {
    if (account != null) {
        try {
            AsyncTaskExample().execute()
        } catch (e: Exception) {
        }
    }
}
inner class AsyncTaskExample : AsyncTask<String, String, String>() {

            override fun onPreExecute() {
                super.onPreExecute()

            }

            override fun doInBackground(vararg p0: String?): String {

                var Result: String = "";
                try {
                    googleToken = GoogleAuthUtil.getToken(activity, accountVal, "oauth2:https://www.googleapis.com/auth/userinfo.profile")
                    signOut()
                } catch (e: Exception) {
                    signOut()
                }


                signOut()

                return Result
            }

            override fun onPostExecute(result: String?) {
                super.onPostExecute(result)
                socialPrsenter.setDataToHitApiGoogleLogin(googleToken ?: "")

            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.