My MainActivity implements the Observer class. I also have a class called ObservedObject that extends the Observable class.
Here is my custom Observable , called ObservedObject:
class ObservedObject(var value: Boolean) : Observable() {
init {
value = false
}
fun setVal(vals: Boolean) {
value = vals
setChanged()
notifyObservers()
}
fun printVal() {
Log.i("Value" , "" + value)
}
}
Here is my Application called SpeechApp which contains my ObservedObject (an Observable actually):
class SpeechApp: Application() {
var isDictionaryRead = ObservedObject(false)
override fun onCreate() {
super.onCreate()
wordslist = ArrayList()
Thread {
execute()
}.start()
}
fun execute() {
while (/* Condition */) {
//Log.i("Read" , line)
/*Does Something Here*/
}
isDictionaryRead.setVal(true)
}
}
In my MainActivity, I mainly have a dialog, that should be displayed after I have got the output after Speech Recognition. It will display as long as the value of isDictionaryRead doesn't change to true:
class MainActivity(private val REQ_CODE_SPEECH_INPUT: Int = 100) : AppCompatActivity() , Observer{
override fun update(o: Observable?, arg: Any?) {
(o as ObservedObject).printVal()
dialog.hide()
}
private lateinit var app : SpeechApp
private lateinit var dialog: MaterialDialog
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dialog = MaterialDialog.Builder(this)
.title("Please Wait")
.content("Loading from the Dictionary")
.progress(true , 0)
.build()
app = application as SpeechApp
app.isDictionaryRead.addObserver(this)
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_speech, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem?): Boolean {
val id = item?.itemId
when(id) {
R.id.menu_option_speech -> {
invokeSpeech()
}
}
return super.onOptionsItemSelected(item)
}
private fun invokeSpeech() {
/* Does Something, Works Fine */
try {
startActivityForResult(intent , REQ_CODE_SPEECH_INPUT)
}
catch (ex: ActivityNotFoundException) {
/* Does Something */
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQ_CODE_SPEECH_INPUT -> {
if (resultCode == Activity.RESULT_OK && null != data) {
dialog.show()
}
}
}
super.onActivityResult(requestCode, resultCode, data)
}
}
Now the problem is, when the SpeechApp sets the value of isDictionaryRead to true, I expect it to call the MainActivity update() method, wherein I have given the code to hide the dialog. That particular code is not working, and my dialog box doesn't go away. Where am I going wrong?
PS. I've pushed my code to Github now, just in case anyone could help me where I am going wrong.
The only thing I can think of that would cause this problem is that the execute() thread that was started in SpeechApp.onCreate finished execution and called isDictionaryRead.setVal(true) before the activity could call app.isDictionaryRead.addObserver(this). As a result, notifyObservers is called before the activity even starts observing, and as a result it is not notified. Here's my proposed solution: Start the execute thread in the activity's onCreate method after adding it as an observer.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dialog = MaterialDialog.Builder(this)
.title("Please Wait")
.content("Loading from the Dictionary")
.progress(true , 0)
.build()
app = application as SpeechApp
app.isDictionaryRead.addObserver(this)
app.asyncReadDictionary()
}
Then remove the thread call from SpeechApp.onCreate and use this instead
// in SpeechApp
fun asyncReadDictionary() {
if (!isDictionaryRead.value) {
Thread { execute() }.start()
}
}
private fun execute() {
while (/* Condition */) {
//Log.i("Read" , line)
/*Does Something Here*/
}
isDictionaryRead.value = true
}
Also, reimplement ObservableObject as follows
class ObservedObject : Observable() {
var value: Boolean = false
set(newValue) {
field = newValue
setChanged()
notifyObservers()
}
fun printVal() {
Log.i("Value" , "" + value)
}
}
Related
How to get result from another activity (registerForActivity) from with in ktor's Routing API call (eg. /POST) running in a non-activity class?
Background: For an Android app, I run ktor server engine 'netty' in a non-activity class HttpServer.kt. I need to call another app's activity from with in ktor's Routing' POST handler, so I pass 'appCompatActivity' from MainActivity.kt. That's done, just because, I assume, registerForActivityResult() has dependency on UI/life cycle class.
Problem arises when running this as below, as registerForActivityResult() requires to be run earlier (like onCreate() ?), and I don't have such a class in this non-activity class. Moreover, the callback to run when ActivityResult is returned needs to call ktor ApplicationCall's respond which is also a suspend function.
class HttpServer(
private val applicationContext: AppCompatActivity
) {
private val logger = LoggerFactory.getLogger(HttpServer::class.java.simpleName)
private val server = createServer()
private fun ApplicationCall.startSaleActivityForResult() { // <====== *
val activityLauncherCustom =
applicationContext.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK || result.resultCode == Activity.RESULT_CANCELED) {
val transactionResultReturned = result.data
// Handle the returned result properly using transactionResultReturned
GlobalScope.launch {
respond(status = HttpStatusCode.OK, TransactionResponse())
}
}
}
val intent = Intent()
// Ignoring statements to create proper action/data intent
activityLauncherCustom.launch(intent) // <====== *
}
fun start() = server.start()
fun stop() = server.stop(0, 0)
private fun createServer(): NettyApplicationEngine {
return GlobalScope.embeddedServer(Netty) {
install(CallLogging)
install(ContentNegotiation) {
gson {
setPrettyPrinting()
}
}
routing {
route("/") {
post {
call.startSaleActivityForResult() // <====== *
}
}
}
}
}
private fun <TEngine : ApplicationEngine, TConfiguration : ApplicationEngine.Configuration>
CoroutineScope.embeddedServer(
factory: ApplicationEngineFactory<TEngine, TConfiguration>,
module: Application.() -> Unit
): TEngine {
val environment = applicationEngineEnvironment {
this.parentCoroutineContext = coroutineContext + parentCoroutineContext
this.log = logger
this.module(module)
connector {
this.port = 8081
}
}
return embeddedServer(factory, environment)
}
}
Above is what I tried, but gives below error. And I don't have onCreate on this non-activity class.
java.lang.IllegalStateException: LifecycleOwner com.youtap.upti.MainActivity#38dcf06 is attempting to register while current state is RESUMED. LifecycleOwners must call register before they are STARTED.
Any suggestions to resolve this problem would be grateful.
Below same above snippet as a screenshot to display helper text on declaration/param types from Android Studio:
And I invoke this server class from onCreate() of MainActivity:
To solve your problem and to hide the complexity you can create an intermediate class for launching activity and waiting for a result to come:
import kotlinx.coroutines.channels.Channel
class Repository(private val activity: MainActivity) {
private val channel = Channel<Int>(1)
suspend fun get(input: String): Int {
activity.activityLauncher.launch(input)
return channel.receive()
}
suspend fun callback(result: Int) {
channel.send(result)
}
}
You can store a reference to a repository and an activity launcher in the MainActivity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
CoroutineScope(Dispatchers.IO).launch {
HttpServer(this#MainActivity).also { it.start() }
}
}
val activityLauncher = registerForActivityResult(MySecondActivityContract()) { result ->
GlobalScope.launch {
repository.callback(result!!)
}
}
val repository = Repository(this)
}
My second activity and a contract looks like the following:
class ChildActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_child)
val result = Intent()
result.putExtra("name", 6666)
result.data = Uri.parse("http://mydata")
setResult(Activity.RESULT_OK, result)
finish()
}
}
class MySecondActivityContract : ActivityResultContract<String, Int?>() {
override fun createIntent(context: Context, input: String?): Intent {
return Intent(context, ChildActivity::class.java)
.putExtra("my_input_key", input)
}
override fun parseResult(resultCode: Int, intent: Intent?): Int? = when {
resultCode != Activity.RESULT_OK -> null
else -> intent?.getIntExtra("name", 42)
}
override fun getSynchronousResult(context: Context, input: String?): SynchronousResult<Int?>? {
return if (input.isNullOrEmpty()) SynchronousResult(42) else null
}
}
The most simplest part is routing handler:
routing {
route("/") {
post {
val result = (applicationContext as MainActivity).repository.get("input")
call.respondText { result.toString() }
}
}
}
This solution works but only one request is processed at the same time and it's not robust because Activity may be destroyed before HTTP server or repository objects.
I am trying to use the MainScope() scope. I thought this allowed me to just call launch whenever I need a new coroutine with the option of specifying which Dispatcher I want it to run on if I don't want it on Dispatchers.Main. However some of my calls to launch don't execute at all.
This code first retrieves a setting db value from Room.
When the preference switch is clicked, it performs a network request to write a new setting on the server.
If the response is successful, I want to update the local db value in Room. The second launch within the observed response is never executed. Without a call to launch, I am (correctly) told by Room not to write on the main thread.
class PrefsFragment : PreferenceFragmentCompat(), Injectable, CoroutineScope by MainScope() {
#Inject
lateinit var settings: BranchSettingsRepository
#Inject
lateinit var preferencesHelper: PreferencesHelper
var groupID = 0
var branchID = 0
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
}
override fun onViewCreated(view: View, #Nullable savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
groupID = preferencesHelper.merchantGroupID.toInt()
branchID = preferencesHelper.merchantBranchID.toInt()
createAutoAcceptSetting(view)
}
private fun createAutoAcceptSetting(root: View) {
launch(Dispatchers.IO) {
val setting = settings.getSettingByName(preferencesHelper.merchantBranchID, AUTO_ACCEPT)
if (setting != null) {
val preference = findPreference<SwitchPreferenceCompat>(AUTO_ACCEPT)
preference?.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { pref, newValue ->
var valueAsIntString = "0"
if (newValue as Boolean) {
valueAsIntString = "1"
}
updateSetting(groupID, branchID, setting.id, valueAsIntString)
.observe(viewLifecycleOwner, Observer { response ->
response?.let {
if (it.status == Status.SUCCESS) {
Timber.i("New setting updated (old value was: $setting)")
Timber.i("launching db write with value: $valueAsIntString")
launch(Dispatchers.IO) {
//This code is never executed
setting.value = valueAsIntString
Timber.i("writing new setting: $setting")
settings.insertSetting(setting)
}
}
}
})
true
}
}
}
}
override fun onDestroy() {
super.onDestroy()
cancel()
}
}
I had an Activity with a calculation and I extracted the functionality of that Activity in MVP pattern, for simplicity:
CalcActivity
CalcPresenter
Earlier I had all the calculation in one single CalcActivity. There I did some calculations in that activity:
private fun Calculator.doCalculation() {
this.complexCalcualtion(intArrayOf(1, 2, 3, 4, 5, 6), object : CalculationCallback {
override fun onSuccess(result: String) {
runOnUiThread {
result_textview.text = result
}
}
})
}
This whole doCalculation() is done on another thread I guess. I moved this method to the presenter and I wanted to forward result to view:
private fun Calculator.doCalculation() {
this.complexCalcualtion(intArrayOf(1, 2, 3, 4, 5, 6), object : CalculationCallback {
override fun onSuccess(result: String) {
view?.showResult(result)
}
})
}
But view? is never called since it is null in the CalculationCallback.onSuccess() and I cant see view there.
Also I do not have access to activity there, so I can not runOnUiThread there..
How can I forward my result back to view/activity?
You can deliver calculation result by LiveData. This tool is integrated with activity life cycle and your data will be delivered when activity (and it views) will be in active state.
You can implement your calculator like this:
class Calculator {
private val resultLiveData = MutableLiveData<String>().apply { value = "" }
// expose result as live data
val result: LiveData<String> = resultLiveData
fun calculate(input: String) {
// calculation in worker thread
// ...
// return result for live data observers
resultLiveData.postValue("$input CALCULATED!")
}
fun cancel() {
// depending on background worker
}
}
And use it in activity (or fragment)
class MyActivity : Activity() {
private val calculator = Calculator()
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
calculator.result.observe(this::getLifecycle) { result ->
// setup result in your view
text_view.text = result
}
}
override fun onStart() {
super.onStart()
calculator.calculate("Input data")
}
override fun onStop() {
super.onStop()
calculator.cancel()
}
}
I have recently seen a weird issue that is acting as a barrier to my project.
Multiple calls to set the live data value does not invoke the observer in the view.
It seems that only the last value that was set actually invokes the Observer in the view.
Here is the code snippet for a review.
MainActivity.kt
class MainActivity : AppCompatActivity() {
private lateinit var viewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel = ViewModelProviders.of(this).get(MainViewModelImpl::class.java)
viewModel.state().observe(this, Observer {
onStateChange(it!!)
})
viewModel.fetchFirstThree()
}
private fun onStateChange(state: MainViewModel.State) {
when (state) {
is One -> {
show(state.data)
}
is Two -> {
show(state.data)
}
is Three -> {
show(state.data)
}
}
}
private fun show(data: String) {
Log.d("Response", data)
}
}
MainViewModel.kt
abstract class MainViewModel : ViewModel() {
sealed class State {
data class One(val data: String) : State()
data class Two(val data: String) : State()
data class Three(val data: String) : State()
}
abstract fun state(): LiveData<State>
abstract fun fetchFirstThree()
}
MainViewModelImpl.kt
class MainViewModelImpl : MainViewModel() {
private val stateLiveData: MediatorLiveData<State> = MediatorLiveData()
override fun state(): LiveData<State> = stateLiveData
override fun fetchFirstThree() {
stateLiveData.value = State.One("One")
stateLiveData.value = State.Two("Two")
stateLiveData.value = State.Three("Three")
}
}
Expected output:
Response: One
Response: Two
Response: Three
Actual Output:
Response: Three
As per the output above, the Observer is not being called for the first two values.
I did some science, re-implementing LiveData and MutableLiveData to log out some data.
Check the source code here.
setValue value=Test1
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test2
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
setValue value=Test3
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
Returned at !observer.active
dispatchingValue mDispatchingValue=false mDispatchInvalidated=false
considerNotify
ITEM: Test3
It looks like the observer hasn't reached an active state when you send the initial values.
private void considerNotify(LifecycleBoundObserver observer) {
// <-- Three times it fails here. This means that your observer wasn't ready for any of them.
if (!observer.active) {
return;
}
Once the observer reaches an active state, it sends the last set value.
void activeStateChanged(boolean newActive) {
if (newActive == active) {
return;
}
active = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
LiveData.this.mActiveCount += active ? 1 : -1;
if (wasInactive && active) {
onActive();
}
if (LiveData.this.mActiveCount == 0 && !active) {
onInactive();
}
if (active) {
// <--- At this point you are getting a call to your observer!
dispatchingValue(this);
}
}
I had such issue too.
To resolve it was created custom MutableLiveData, that contains a queue of posted values and will notify observer for each value.
You can use it the same way as usual MutableLiveData.
open class MultipleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
private val values: Queue<T> = LinkedList()
#MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w(this::class.java.name, "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner, { t: T ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
//call next value processing if have such
if (values.isNotEmpty())
pollValue()
}
})
}
override fun postValue(value: T) {
values.add(value)
pollValue()
}
private fun pollValue() {
value = values.poll()
}
#MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
/**
* Used for cases where T is Void, to make calls cleaner.
*/
#Suppress("unused")
#MainThread
fun call() {
value = null
}
}
You could use custom LiveData like this:
class ActiveMutableLiveData<T> : MutableLiveData<T>() {
private val values: Queue<T> = LinkedList()
private var isActive: Boolean = false
override fun onActive() {
isActive = true
while (values.isNotEmpty()) {
setValue(values.poll())
}
}
override fun onInactive() {
isActive = false
}
override fun setValue(value: T) {
if (isActive) {
super.setValue(value)
} else {
values.add(value)
}
}
}
FWIW I had the same problem but solved it like this...
I originally had some code similar to this...
private fun updateMonth(month: Int){
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
updateMonth(1)
updateMonth(2)
updateMonth(3)
I experienced the same problem as described...
But when I made this simple change....
private fun updateMonth(month: Int) {
CoroutineScope(Dispatchers.Main).launch {
updateMonth.value = UpdateMonth(month, getDaysOfMonth(month))
}
}
Presumably, each updateMonth is going onto a different thread now, so all of the updates are observed.
You should call viewModel.fetchFirstThree() after Activity's onStart() method. for example in onResume() method.
Because in LiveData the Observer is wrapped as a LifecycleBoundObserver. The field mActive set to true after onStart().
class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserver {
#Override
boolean shouldBeActive() {
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);// return true after onStart()
}
#Override
public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
activeStateChanged(shouldBeActive());// after onStart() change mActive to true
}
}
When the observer notify the change it calls considerNotify, before onStart it will return at !observer.mActive
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {// called in onCreate() will return here.
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
//noinspection unchecked
observer.mObserver.onChanged((T) mData);
}
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 ?: "")
}
}