51

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.

Sergio
  • 27,326
  • 8
  • 128
  • 149
Rakesh Gujari
  • 899
  • 1
  • 8
  • 12

11 Answers11

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 {
    // ...
}
shiftpsh
  • 1,926
  • 17
  • 24
  • 4
    how we can prevent this warning "leak might occur make the async task static" ? – Shubham AgaRwal Jan 30 '18 at 10:06
  • @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. – android developer Aug 03 '19 at 22:21
36

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()
Algar
  • 5,734
  • 3
  • 34
  • 51
  • 1
    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 May 08 '19 at 09:36
  • 4
    @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. – Algar May 09 '19 at 13:08
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 ...
    })
}
quemeful
  • 9,542
  • 4
  • 60
  • 69
13

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
Sergio
  • 27,326
  • 8
  • 128
  • 149
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

Gopi.cs
  • 985
  • 1
  • 16
  • 41
  • 1
    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. – Radu Dec 19 '18 at 16:23
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)
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
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.

Roy Scheffers
  • 3,832
  • 11
  • 31
  • 36
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() )
}
Diego Favero
  • 1,969
  • 2
  • 22
  • 32
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)
Mark
  • 7,446
  • 5
  • 55
  • 75
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.

ceving
  • 21,900
  • 13
  • 104
  • 178
-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 ?: "")

            }
        }
abhilasha Yadav
  • 217
  • 1
  • 9