This Kotlin routine which I cobbled together from various forum examples works BUT ONLY ON THE FIRST CALL.
class myClass: Activity(), myInterface {
override fun onCreate(...) {
...
}
override fun myCallback(response: String) {
myReturningFunction(response)
}
fun myCallingFunction() {
...
...
val myServer = myObject
myServer.myObjectInit(this, stringData)
//myServer.execute(stringData)
}
}
interface myInterface {
fun myCallback(response: String)
}
object myObject : AsyncTask<String, String, String>() {
var thisInterface: myInterface? = null
fun myObjectInit(thatInterface: myInterface, stringData: String) {
thisInterface = thatInterface
//this.executeOnExecutor(THREAD_POOL_EXECUTOR)
this.execute(stringData)
}
override fun doInBackground(vararg params: String): String? {
var response: String = ""
//return try {
try {
params.first().let {
val url = URL("- web service URL -")
val urlConnect = url.openConnection() as HttpURLConnection
with(urlConnect) {
requestMethod = "POST"
readTimeout = 5000
connectTimeout = 5000
doInput = true
doOutput = true
setRequestProperty("Content-Type", "application/json")
setRequestProperty("Accept", "application/json")
setRequestProperty("Charset", "utf-8")
val jsonByteData = it.toByteArray(Charsets.UTF_8)
outputStream.write(jsonByteData, 0, jsonByteData.size)
outputStream.flush()
outputStream.close()
//inputStream.bufferedReader().readText()
response = inputStream.bufferedReader().readText()
inputStream.close()
disconnect()
}
}
} catch (e: Exception) {
response = ""
}
return response
}
override fun onPostExecute(result: String?) {
when {
result != null -> {
thisInterface?.myCallback(result)
}
else -> {
println("null response")
}
}
}
}
I instantiate a copy of the AsyncTask object and execute it, and when I successfully receive the response via the interface, I instantiate another copy (val myServer = myObject) for a follow-up call, but this time it throws this error:
Cannot execute task: the task is already running.
I've tried many approaches, closing the input stream, disconnecting from the server, cancelling the task, but none of it works.
Is there something obviously wrong with the code that I'm missing?
TIA.
An AsyncTask can only be executed once. If you want to execute it again, you'll need to create a second one.
From the documentation:
The task can be executed only once (an exception will be thrown if a
second execution is attempted.)
What you could do is subclass the AsnycTask and use a new instance each time you want to execute it:
fun startBackgroundTask(){
CustomAsyncTask().execute()
// Or in your case:
CustomAsyncTask().myObjectInit(this, "data")
}
class CustomAsyncTask: AsyncTask<String, String, String>(){
var thisInterface: myInterface? = null
fun myObjectInit(thatInterface: myInterface, stringData: String) {
thisInterface = thatInterface
execute(stringData)
}
override fun doInBackground(vararg params: String?): String {
// Do your work.
return ""
}
}
Related
The question about post requests in android has been asked before, but all the solutions I've tried have not worked properly. On top of that, a lot of them seem to be overly complicated as well. All I wish to do is make a post to a specific sight with a few body parameters. Is there any simple way to do that?
Let me explain my request calling structure using Retrofit.
build.gradle(app)
// Retrofit + GSON
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
ApiClient.kt
object ApiClient {
private const val baseUrl = ApiInterface.BASE_URL
private var retrofit: Retrofit? = null
private val dispatcher = Dispatcher()
fun getClient(): Retrofit? {
val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
logging.level = HttpLoggingInterceptor.Level.BODY
else
logging.level = HttpLoggingInterceptor.Level.NONE
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(OkHttpClient().newBuilder().readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS).retryOnConnectionFailure(false)
.dispatcher(
dispatcher
).addInterceptor(Interceptor { chain: Interceptor.Chain? ->
val newRequest = chain?.request()!!.newBuilder()
return#Interceptor chain.proceed(newRequest.build())
}).addInterceptor(logging).build()
)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
ApiClient will be used to initialize Retrofit singleton object, also initialize logging interceptors so you can keep track of the requests and responses in the logcat by using the keyword 'okhttp'.
SingleEnqueueCall.kt
object SingleEnqueueCall {
var retryCount = 0
lateinit var snackbar: Snackbar
fun <T> callRetrofit(
activity: Activity,
call: Call<T>,
apiName: String,
isLoaderShown: Boolean,
apiListener: IGenericCallBack
) {
snackbar = Snackbar.make(
activity.findViewById(android.R.id.content),
Constants.CONST_NO_INTERNET_CONNECTION, Snackbar.LENGTH_INDEFINITE
)
if (isLoaderShown)
activity.showAppLoader()
snackbar.dismiss()
call.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
hideAppLoader()
if (response.isSuccessful) {
retryCount = 0
apiListener.success(apiName, response.body())
} else {
when {
response.errorBody() != null -> try {
val json = JSONObject(response.errorBody()!!.string())
Log.e("TEGD", "JSON==> " + response.errorBody())
Log.e("TEGD", "Response Code==> " + response.code())
val error = json.get("message") as String
apiListener.failure(apiName, error)
} catch (e: Exception) {
e.printStackTrace()
Log.e("TGED", "JSON==> " + e.message)
Log.e("TGED", "Response Code==> " + response.code())
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
}
else -> {
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
return
}
}
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
hideAppLoader()
val callBack = this
if (t.message != "Canceled") {
Log.e("TGED", "Fail==> " + t.localizedMessage)
if (t is UnknownHostException || t is IOException) {
snackbar.setAction("Retry") {
snackbar.dismiss()
enqueueWithRetry(activity, call, callBack, isLoaderShown)
}
snackbar.show()
apiListener.failure(apiName, Constants.CONST_NO_INTERNET_CONNECTION)
} else {
retryCount = 0
apiListener.failure(apiName, t.toString())
}
} else {
retryCount = 0
}
}
})
}
fun <T> enqueueWithRetry(
activity: Activity,
call: Call<T>,
callback: Callback<T>,
isLoaderShown: Boolean
) {
activity.showAppLoader()
call.clone().enqueue(callback)
}
}
SingleEnqueueCall will be used for calling the retrofit, it is quite versatile, written with onFailure() functions and by passing Call to it, we can call an API along with ApiName parameter so this function can be used for any possible calls and by ApiName, we can distinguish in the response that which API the result came from.
Constants.kt
object Constants {
const val CONST_NO_INTERNET_CONNECTION = "Please check your internet
connection"
const val CONST_SERVER_NOT_RESPONDING = "Server not responding!
Please try again later"
const val USER_REGISTER = "/api/User/register"
}
ApiInterface.kt
interface ApiInterface {
companion object {
const val BASE_URL = "URL_LINK"
}
#POST(Constants.USER_REGISTER)
fun userRegister(#Body userRegisterRequest: UserRegisterRequest):
Call<UserRegisterResponse>
}
UserRegisterRequest.kt
data class UserRegisterRequest(
val Email: String,
val Password: String
)
UserRegisterResponse.kt
data class UserRegisterResponse(
val Message: String,
val Code: Int
)
IGenericCallBack.kt
interface IGenericCallBack {
fun success(apiName: String, response: Any?)
fun failure(apiName: String, message: String?)
}
MyApplication.kt
class MyApplication : Application() {
companion object {
lateinit var apiService: ApiInterface
}
override fun onCreate() {
super.onCreate()
apiService = ApiClient.getClient()!!.create(ApiInterface::class.java)
}
}
MyApplication is the application class to initialize Retrofit at the launch of the app.
AndroidManifest.xml
android:name=".MyApplication"
You have to write above tag in AndroidManifest inside Application tag.
MainActivity.kt
class MainActivity : AppCompatActivity(), IGenericCallBack {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val call = MyApplication.apiService.userRegister(UserRegisterRequest(email, password))
SingleEnqueueCall.callRetrofit(this, call, Constants.USER_REGISTER, true, this)
}
override fun success(apiName: String, response: Any?) {
val model = response as UserRegisterResponse
}
override fun failure(apiName: String, message: String?) {
if (message != null) {
showToastMessage(message)
}
}
}
Firstly, we create a call object by using the API defined in ApiInterface and passing the parameters (if any). Then using SingleEnqueueCall, we pass the call to the retrofit along with ApiName and the interface listener IGenericCallBack by using this. Remember to implement it to respective activity or fragment as above.
Secondly, you will have the response of the API whether in success() or failure() function overriden by IGenericCallBack
P.S: You can differentiate which API got the response by using the ApiName parameter inside success() function.
override fun success(apiName: String, response: Any?) {
when(ApiName) {
Constants.USER_REGISTER -> {
val model = response as UserRegisterResponse
}
}
}
The whole concept is to focus on reusability, now every API call has to create a call variable by using the API's inside ApiInterface then call that API by SingleEnqueueCall and get the response inside success() or failure() functions.
I've recently work with Kotlin, and got really stuck with this one problem. I'm trying to return float value receive onResponse of a coroutine api call function. I'm trying to create a class that handle api call and use it on a fragment.
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
....
func getBalance(cardNo: String): Float?{
val cardBalance: Float = null
GlobalScope.launch(Dispatchers.Main) {
val cardDetails = cardApi.getCardBalance(cardNo)
cardDetails.enqueue(object : Callback<Card> {
override fun onFailure(call: Call<Card>, t: Throwable) {
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
}
override fun onResponse(call: Call<Card>, response: Response<Card>) {
if (response.isSuccessful) {
val card = response.body()!!
cardBalance = card.cardAvailableBalance
} else {
val error: ApiError = ErrorUtils.parseError(response)
val message = error.code + error.message
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
context!!.toast("Errror: " + message)
promptErrorDialog(error)
}
}
})
}}
return cardBalance
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
val galA = 10.5f
val galB = "Test"
private var pass = FunctionA(context!!, valA ,valB)
....
val point = "sasd12125"
private fun gooToo(){
val B = pass.getBalance(point)
print("TEST")
println("value B: " + B)
}
....
}
What happend right now, since the coroutine will take some time in background, val B are null and didn't get the value obtained onResponse. Only after I try to call that functionA again, then the value are updated. I'm not sure if I'm doing it right and I've tried to search for solutions, but it doesn't suit with my current situation. Probably my searching skill are soo bad.
Output
TEST
value B: null
How should I wait for the coroutine to finish before return the cardBalance value?
Proper way to return a single value from a coroutine is to use await().
Now, since you use coroutine to wrap some callback API, that wouldn't work so well. So I would suggest to go with something like this:
val scope = CoroutineScope(Dispatchers.IO)
suspend fun getBalance(cardNo: String): Float{
val res = CompletableDeferred<Float>()
scope.launch {
val cardDetails = cardApi.getCardBalance(cardNo)
cardDetails.enqueue(object : Callback<Card> {
override fun onFailure(call: Call<Card>, t: Throwable) {
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to t.message!!)
}
override fun onResponse(call: Call<Card>, response: Response<Card>) {
if (response.isSuccessful) {
val card = response.body()!!
res.complete(card.cardAvailableBalance)
} else {
val error: ApiError = ErrorUtils.parseError(response)
val message = error.code + error.message
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
res.completeExceptionally(message)
withContext(Dispatchers.Main) {
promptErrorDialog(error)
}
}
}
})
}
return res.await()
}
A few points to consider. First, I used Dispatchers.IO instead of Dispatchers.Main, and switch to Main thread only when needed using withContext(Dispatchers.Main). Otherwise, you're just running your IO on the main thread, coroutine or not.
Second, using GlobalScope is a bad practice, and you should avoid it at all cost. Instead I create a custom scope that you can .cancel() to prevent coroutine leak.
Third, the most correct way would be to return Deferred<Float>, and not Float, since await() is blocking. But I left it for simplicity.
To solve my little problem I end up using a callback to pass the response data. I found that this method work perfectly and easier to understand for my level of understanding. This method skeleton can also be reused for any api service call that I want to use in the future.
FunctionA.kt
class FunctionA(val context: Context?, val A: Float?, val B: String?){
private var cardApi: CardApi = ApiClient.createApi().create(CardApi::class.java)
private var card: Card? = null
interface CardBalanceCallback {
fun processFinish(output: Boolean, cardBalance: Float?)
}
fun getCardBalance(cardNo: String, callback: CardBalanceCallback) = runBlocking {
getBalance(cardNo, callback)
}
private fun getBalance(cardNo: String, callback: CardBalanceCallback) = CoroutineScope(Dispatchers.Main).launch {
try {
val response = cardApi.getCardBalance(cardNo).await()
if (response.isSuccessful) {
card = response.body()
callback.processFinish(true, card!!.cardAvailableBalance)
} else {
callback.processFinish(false, null)
val error: ApiError = ErrorUtils.parseError(response)
val message = when {
error.error.code.isNotEmpty() -> error.error.code + error.error.message
else -> error.code + error.message
}
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to message)
promptErrorDialog(error)
}
} catch (e: HttpException) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast(e.message.toString())
} catch (e: Throwable) {
callback.processFinish(false, null)
trackEvent(API_READ_CARD_BALANCE_ERROR, ERROR to e.message!!)
context!!.toast( e.message.toString())
}
}
....
....
}
FragmentClass.kt
class FragmentClass : BaseFragment(){
private var funcService = FunctionA(null, null ,null)
....
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity!!.application as App).component.inject(this)
val valA = 10.5f
val valB = "Test"
val cardNo = "4001526976443264"
val cardExpDate = "1119"
funcService = FunctionA(context!!, valA ,valB)
getCardBalanceApi(cardNo, cardExpDate)
}
....
private fun getCardBalanceApi(cardNo: String, cardExpDate: String?) {
showLoadingDialog()
funcService.getCardBalance(cardNo, object : SmartPayService.CardBalanceCallback {
override fun processFinish(output: Boolean, cardBalance: Float?) {
dismissLoadingDialog()
if (cardBalance != null) {
checkBalance(cardNo, cardBalance, cardExpDate)
}
}
})
}
....
}
This is some simple changes that I made for this particular problem on my first post. This approach might not be as good or smooth enough as I'm still learning. Hope it help some of you guys. cheers
Make getBalance() a suspend function and then call using lifecycleScope in your fragment
private fun gooToo(){
lifecycleScope.launch {
val B = pass.getBalance(point)
print("TEST")
println("value B: " + B)
}
}
getBalance() function signature would be something like
suspend fun getBalance(): Float = withContext(Dispatchers.IO)
I can't get one solution for this. I have searched many things and I can't get an answer. Please help me. This is my code
class NewTask : AsyncTask<Void, Void, String>() {
public override fun doInBackground(vararg params: Void?): String? {
val arr = ArrayList<String>()
val url = URL("http://boogle.org")
with(url.openConnection() as HttpURLConnection) {
requestMethod = "GET" // optional default is GET
//arr.add(responseCode)
inputStream.bufferedReader().use {
it.lines().forEach { line ->
//println(line)
arr.add(line as String)
}
}
}
return arr.get(0)
}
public override fun onPostExecute(result: String?) {
//super.onPostExecute(result)
}
}
You can call the get() method of AsyncTask (or the overloaded get(long, TimeUnit)). This method will block until the AsyncTask has completed its work, at which point it will return you the Result.
It would be wise to be doing other work between the creation/start of your async task and calling the get method, otherwise, you aren't utilizing the async task very efficiently.
I had the same problem, but I settled on coroutines. Here is the code I used:
class CoRoutine{
suspend fun httpGet(url: String = "https://boogle.org): String {
val arr = ArrayList<String>()
withContext(Dispatchers.IO) {
val url = URL(url)
with(url.openConnection() as HttpURLConnection) {
requestMethod = "GET" // optional default is GET
//arr.add(responseCode)
inputStream.bufferedReader().use {
it.lines().forEach { line ->
//println(line)
arr.add(line as String)
}
}
}
}
return arr.get(0)
}
}
Answer taken from my other answer.
I am using GRPC with proto in my project and I have KEY and AUTHORITY tokens to access the server API.
So, I need to update KEY using my AUTHORITY.
I am building Channel like this:
OkHttpChannelBuilder.forAddress(host, port)
.usePlaintext()
.intercept(auth, logger)
.build()
My interceptor looks like:
class AuthClientInterceptor(
private val prefs: Preferences,
private val keyApi: KeyApi) : ClientInterceptor {
companion object {
private const val ACCESS_TOKEN = "authorization"
}
override fun <ReqT : Any?, RespT : Any?> interceptCall(method: MethodDescriptor<ReqT, RespT>?,
callOptions: CallOptions?,
next: Channel): ClientCall<ReqT, RespT> {
val call = next.newCall(method, callOptions)
val callForwarding = object : ClientInterceptors.CheckedForwardingClientCall<ReqT, RespT>(call) {
override fun checkedStart(responseListener: Listener<RespT>?, headers: Metadata) {
synchronized(this#AuthClientInterceptor) {
val keyCreated = prefs.getAccessKeyCreated()
val keyExpires = prefs.getAccessKeyExpires()
val currentTime = System.currentTimeMillis()
if (currentTime < keyCreated || currentTime > keyExpires) {
keyApi.issueNewKey(prefs.getAuthority())
.map { it.data }
.doOnSuccess { prefs.setAccessKey(it.token) }
.doOnSuccess { prefs.setAccessKeyCreated(it.createdDate) }
.doOnSuccess { prefs.setAccessKeyExpires(it.expiresDate) }
.blockingGet()
}
}
val keyData = Metadata.Key.of(ACCESS_TOKEN, Metadata.ASCII_STRING_MARSHALLER)
if (headers[keyData] == null) {
headers.put(keyData, "Bearer ${prefs.getAccessKey()}")
}
call.start(responseListener, headers)
}
}
return callForwarding
}
}
As you can see, I just check current time and compare it with token created and expiry dates.
So, I don't like that way. I want implement this:
1) Send request to server;
2) Check response. If it means, that my KEY expired, refresh key synchronously and repeat the request (like authenticator).
But I didn't find the solution or any helpful information about implementing this with gRPC. Can somebody help me?
Here is the full client interceptor class you can use.
class Interceptor() : ClientInterceptor {
override fun <ReqT : Any?, RespT : Any?> interceptCall(method: MethodDescriptor<ReqT, RespT>?, callOptions: CallOptions?, next: Channel?): ClientCall<ReqT, RespT> {
return object : ClientCall<ReqT, RespT>() {
var listener: Listener<RespT>? = null
var metadata: Metadata? = null
var message: ReqT? = null
var request = 0
var call: ClientCall<ReqT, RespT>? = null
override fun start(responseListener: Listener<RespT>?, headers: Metadata?) {
this.listener = responseListener
this.metadata = headers
}
override fun sendMessage(message: ReqT) {
assert(this.message == null)
this.message = message
}
override fun request(numMessages: Int) {
request += numMessages
assert(this.message == null)
}
override fun isReady(): Boolean {
return false
}
override fun halfClose() {
startCall(object : ForwardingClientCallListener<RespT>() {
var delegate: Listener<RespT>? = null
override fun onReady() {
delegate = listener
super.onReady()
}
override fun delegate(): Listener<RespT> {
if (delegate == null) {
throw IllegalStateException()
}
return delegate!!
}
override fun onClose(status: Status?, trailers: Metadata?) {
if (delegate == null) {
super.onClose(status, trailers)
return
}
if (!needToRetry(status, trailers)) {
delegate = listener
super.onClose(status, trailers)
return
}
startCall(listener) // Only retry once
}
private fun needToRetry(status: Status?, trailers: Metadata?): Boolean {
if (status?.code?.toStatus() == UNAUTHENTICATED) {
Log.e("code", status?.code.toString())
return true
}
return false
}
})
}
private fun startCall(listener: Listener<RespT>?) {
call = next?.newCall(method, callOptions)
val headers = Metadata()
headers.merge(metadata)
call?.start(listener, headers)
assert(this.message != null)
call?.request(request)
call?.sendMessage(message)
call?.halfClose()
}
override fun cancel(message: String?, cause: Throwable?) {
if (call != null) {
call?.cancel(message, cause)
}
listener?.onClose(Status.CANCELLED.withDescription(message).withCause(cause), Metadata())
}
}
}
}
It buffers the message and retries, you can add your logic in needToRetry(status, trailers)
For more information, you can visit this GitHub link.
If you want to do that, I think you will have to handle it at the application level exactly as you described. This is because gRPC does not know about your application level tokens.
Make the RPC
Notice that the RPC failed due to an expired token
Call your code that refreshes the token
Repeat
What is the authenticator that you are referring to?
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.
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:
https://github.com/Kotlin/anko/blob/d5a526512b48c5cd2e3b8f6ff14b153c2337aa22/anko/library/static/commons/src/Async.kt
If you use Anko your code will be similar to this:
doAsync {
// ...
}
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()
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 ...
})
}
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
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
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)
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.
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() )
}
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)
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.
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 ?: "")
}
}