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 ?: "")
}
}
Related
I am using MutableSharedFlow in project. My main project concept is very big, so I cannot add in here, instead I made a very small sample to reproduce my problem. I know this example is very wrong, but I have same scenario in my main project. I am using MutableSharedFlow as a Queue implementation with single Thread execution with the help of Mutex.
ExampleViewModel
class ExampleViewModel : ViewModel() {
val serviceNumber = ServiceNumber()
val serviceNumberEventFlow = serviceNumber.eventFlow
val mutex = Mutex()
var delayCounter = 0
suspend fun addItem(itemOne: Int = 2, itemTwo: Int = 2): Add {
return mutex.queueWithTimeout("add") {
serviceNumberEventFlow.onSubscription {
serviceNumber.add(itemOne, itemTwo)
delayCounter++
if (delayCounter == 1) {
delay(1000)
Log.w("Delay ", "Delay Started")
serviceNumber.add(8, 8)
}
}.firstOrNull {
it is Add
} as Add? ?: Add("No value")
}
}
suspend fun subItem(itemOne: Int = 2, itemTwo: Int = 2): Sub {
return mutex.queueWithTimeout("sub") {
serviceNumberEventFlow.onSubscription {
serviceNumber.sub(itemOne, itemTwo)
}.firstOrNull {
it is Sub
} as Sub? ?: Sub("No value")
}
}
private suspend fun <T> Mutex.queueWithTimeout(
action: String, timeout: Long = 5000L, block: suspend CoroutineScope.() -> T
): T {
return try {
withLock {
return#withLock withTimeout<T>(timeMillis = timeout, block = block)
}
} catch (e: Exception) {
Log.e("Wrong", " $e Timeout on BLE call: $action")
throw e
}
}
}
class ServiceNumber : Number {
val eventFlow = MutableSharedFlow<Event>(extraBufferCapacity = 50)
private val scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun add(itemOne: Int, itemTwo: Int) {
Log.i("ServiceNumber", " Add event trigger with $itemOne -- $itemTwo")
eventFlow.emitEvent(Add("Item added ${itemOne + itemTwo}"))
}
override fun sub(itemOne: Int, itemTwo: Int) {
eventFlow.emitEvent(Sub("Item subtract ${itemOne - itemTwo}"))
}
private fun <T> MutableSharedFlow<T>.emitEvent(event: T) {
scope.launch { emit(event) }
}
}
interface Number {
fun add(itemOne: Int, itemTwo: Int)
fun sub(itemOne: Int, itemTwo: Int)
}
sealed class Event
data class Add(val item: String) : Event()
data class Sub(val item: String) : Event()
MainActivity.kt
class MainActivity : AppCompatActivity() {
private val viewModel: ExampleViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
Theme {
Column {
Button(onClick = {
lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
val result = viewModel.addItem()
Log.e("Result", "$result")
}
}
}) {
Text("Add")
}
Button(onClick = {
lifecycleScope.launchWhenCreated {
withContext(Dispatchers.IO) {
val result = viewModel.subItem()
Log.e("Result", "$result")
}
}
}) {
Text("Sub")
}
}
}
}
}
}
#Composable
fun Theme(content: #Composable () -> Unit) {
MaterialTheme(content = content)
}
Problem
This example is simple Add and subtract of two number. When I am click on Add Button first time, viewmodel.addItem(...) -> ... ->ServiceNumber.add() will trigger and emit the value and we can see log in console. Inside the Add Button function, I was also added a delay to trigger ServiceNumber.add() again to see that onSubscription will be also retrigger or not. MutableSharedFlow emit the value as I can see in log but onSubscription method not called. I don't understand what is the problem in here.
onSubscription is an operator so it creates a new copy of your shared flow. The lambda code will only be run when there are new collectors on this new flow. The only time you collect this new flow is when you call firstOrNull() on it, a terminal operator that collects a single value.
I have the following ViewModel class -
class VerifyOtpViewModel : ViewModel() {
private var existingUserProfileData: MutableLiveData<TwoVerteUsers.TwoVerteUser>? = null
fun checkInfoForAuthenticatedUser(authorization: String, user: String) {
ProfileNetworking.getUsersProfiles(authorization, GetUserProfilesBodyModel(listOf(user)), object : ProfileNetworking.OnGetUserProfilesListener {
override fun onSuccess(model: TwoVerteUsers) {
existingUserProfileData?.value = model[0]
}
override fun onError(reason: String) {
Log.d("existingProfile", reason)
}
})
}
fun getExistingUserProfileData(): LiveData<TwoVerteUsers.TwoVerteUser>? {
if (existingUserProfileData == null) return null
return existingUserProfileData as LiveData<TwoVerteUsers.TwoVerteUser>
}
}
and the following observer -
private fun initViewModel() {
verifyOtpViewModel = ViewModelProvider(this).get(VerifyOtpViewModel::class.java)
verifyOtpViewModel.getExistingUserProfileData()?.observe(this, Observer {
if (it != null)
Log.d("existingProfile", it.username)
})
}
For some reason the observe is never triggered even after the MutableLiveData object is being given a value
Tried to search for a solution here at stackoverflow but nothing helped
what am I missing?
refactor your code to this, and you should be good to go:
class VerifyOtpViewModel : ViewModel() {
private val _existingUserProfileData = MutableLiveData<TwoVerteUsers.TwoVerteUser>()
val existingUserProfileData: LiveData<TwoVerteUsers.TwoVerteUser>
get() = _existingUserProfileData
fun checkInfoForAuthenticatedUser(authorization: String, user: String) {
ProfileNetworking.getUsersProfiles(
authorization,
GetUserProfilesBodyModel(listOf(user)),
object : ProfileNetworking.OnGetUserProfilesListener {
override fun onSuccess(model: TwoVerteUsers) {
existingUserProfileData.value = model[0]
}
override fun onError(reason: String) {
Log.d("existingProfile", reason)
}
})
}
}
And observing:
verifyOtpViewModel.existingUserProfileData.observe(this, Observer {
.....
})
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 ""
}
}
I'm making an Android app that follows the MVVM pattern using Room and LiveData, but I need to return the ID of the last insertion in one table because is used to add values in other table where the ID is the foreign key, I know that I can achive this returning it from the DAO:
#Dao
interface TratamientoDao {
#Insert
fun insert(tratamiento : Tratamiento): Long
}
But the problem is that I need to return a value from the AsyncTask that inserts the new entry,I tried the approach explained in this question: How to get the result of OnPostExecute() to main activity because AsyncTask is a separate class?
But In my case the method processFinish is never called.
This is my code:
interface AsyncValue{
fun processFinish(lastID: Long?) : Long?
}
class TratamientoRepository (application: Application): AsyncValue {
val tratamientoDao : TratamientoDao
init{
val database = MMRDataBase.getInstance(application)
tratamientoDao = database.tratamientoDao()
}
fun insert(tratamiento: Tratamiento) : Long? {
InsertTratamientoAsyncTask(tratamientoDao).execute(tratamiento)
return 45
}
override fun processFinish(lastID: Long?): Long? {
Log.d("ValorIngresado:", lastID.toString())
return lastID
}
private class InsertTratamientoAsyncTask constructor(private val tratamientoDao: TratamientoDao) : AsyncTask<Tratamiento, Void, Long>(){
var completionCode : AsyncValue? = null
override fun doInBackground(vararg params: Tratamiento): Long?{
return tratamientoDao.insert(params[0])
}
override fun onPostExecute(result: Long?) {
completionCode?.processFinish(result)
//Log.i("TAMANO", result.toString())
}
}
}
So, How can I return a value from an AsyncTask in Kotlin?
I spent a full day trying to figure this and, 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() )
}
There is probably a way, but I prefer using coroutines.
Like for example:
val tratamiento = GetTratamiento()//or whatever you do to get the object
var result = 0
var scope = CoroutineScope(Job() + Dispatchers.IO)
scope.launch {
result = tratamientoDao.insert(tratamiento)
}
Remember to add the coroutines to the app build.gradle
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.27.0-eap13"
This is the way to return a value from AsyncTask you can convert in to Kotlin and try this
public class MyClass extends Activity {
private class myTask extends AsyncTask<Void, Void, String> {
protected Void doInBackground(String... params) {
//do stuff return results;
}
#Override protected void onPostExecute(String result) {
//do stuff
myMethod(myValue);
}
}
private myValueType myMethod(String myValue) {
//handle value
return myValueType;
}
}
The best way to handle AyncTask in Kotlin is using the Anko library for doing background task. It simplifies a lot the use of it. Just like this:
doAsync {
var result = runLongTask()
uiThread {
toast(result)
}
}
For more information check this article:
https://antonioleiva.com/anko-background-kotlin-android/
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)
}
}