Reputation: 3366
After a break I am trying to finish my first android app and in the process of converting it to Kotlin. All has gone well but I am getting the warning on Async tasks that are making calls to a locally stored SQL database and the error is that the Async call should be static or it will leak.
So I intend on doing it right and from what I have read so far I need to use Globalscope.launch.
Here is the code I used to use to access the database on another thread.
private class MyAsyncTask extends AsyncTask<String, String, String>
{
@Override protected String doInBackground (String... params)
{
//SQL tasks, open read and close database
}
@Override protected void onPostExecute(String result)
{
// Tasks on retrieved database.
}
@Override protected void onPreExecute()
{ }
@Override protected void onProgressUpdate(String... text) {}
}
I did a Kotlin conversion and it produced this code of which I am receiving the should be static or will cause a memory leak warning:
private inner class MyAsyncTask : AsyncTask<String, String, String>() {
override fun doInBackground(vararg params: String): String?
{
//SQL tasks, open read and close database
}
override fun onPostExecute(result: String)
{
// Tasks on retrieved database.
}
override fun onPreExecute() {}
override fun onProgressUpdate(vararg text: String)
{}
}
This is how I believe I should now perform a SQL call on a seperate thread in Kotlin
private inner class MyAsyncTask()
{
GlobalScope.launch {
//SQL tasks, open read and close database
}
Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
// Tasks on retrieved database.
}
Is the GlobalScope.launch the best and safest way to make a call to a locally stored SQL database and if not, what is the correct method?
Upvotes: 0
Views: 673
Reputation: 3366
After a week reading lots and trying to get the right solution for my needs I found the solution by Ian Alexander the most helpful. @Rene's solution is good but not exactly what I needed but it did give me clues so thanks @Rene.
Caveat, this is for Kotlin 1.3 so Android Studio might recommend you upgrade to the later versions.
Step 1. Ensure your build.gradle has both the following as both are needed for Dispatch.Main
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.0.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.0.0'
Step 2.
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
protected val mySQLScope = CoroutineScope(Dispatchers.Main)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//MAIN UI setup
println("Setting up Activity Main")
//get SQL database loaded in background
getSQLDatabase()
}
fun getSQLDatabase() {
mySQLScope.launch {
val user = withContext(Dispatchers.IO){
getSQLTASK()
}
//Any code here is blocked till getSQLTASK is finished
println("getSQLTASK now finished")
//Retrieved Database Now Usable
}
}
suspend fun getSQLTASK(){
//Code here blocks the main coroutine but does not affect the main thread.
delay(2000)
println("In getSQLTASK")
//SQL DATABASE LOADED HERE
}
}
So this works but if I want to ensure the process stops if the user swaps to another app then I will need to do the following:
import kotlinx.coroutines.*
class MainActivity : AppCompatActivity() {
protected val coroutineSup = SupervisorJob()
protected val mySQLScope = CoroutineScope(Dispatchers.Main + coroutineSup)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//MAIN UI setup
println("Setting up Activity Main")
//get SQL database loaded in background
getSQLDatabase()
}
fun getSQLDatabase() {
mySQLScope.launch {
val user = withContext(Dispatchers.IO){
getSQLTASK()
}
//Any code here is blocked till getSQLTASK is finished
println("getSQLTASK now finished")
//Retrieved Database Now Usable
}
}
suspend fun getSQLTASK(){
//Code here blocks the main coroutine but does not affect the main thread.
delay(2000)
println("In getSQLTASK")
//SQL DATABASE LOADED HERE
}
@CallSuper
override fun onPause() {
super.onPause()
coroutineSup.cancel()
//now crash avoided if user leaves app.
}
}
This adds a supervisor that cancels the SQL retrieval if the app is no longer actively used.
Hope this helps someone as it took a week of reading to get my head around it.
Upvotes: 0
Reputation: 6148
The combination of AsyncTask
and Coroutines makes no sense. Both are ways to execute something on a background thread. Especially the Thread.sleep()
is against the main idea of coroutines: "non blocking threads".
A good explanation of coroutines and UI is this: https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md#structured-concurrency-lifecycle-and-coroutine-parent-child-hierarchy
I just adapted the main part of the example to get your an idea how to use:
//Create an own coroutine scope for your activity
class MainActivity : AppCompatActivity(), CoroutineScope {
protected lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
job = Job()
}
//destroy all coroutines, when the activity is going down
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
//start a new coroutine
fun loadDataFromSQL() = launch { // Is invoked in UI context with Activity's job as a parent
val data = withContext(Dispatchers.IO) { // runs in background
//sql access
}
//runs in UI thread
// display data
}
}
Upvotes: 2
Reputation: 691
Using GlobalScope is possible, but it's not the best way. You should use local CoroutineScope. See this article by Roman Elizarov: https://medium.com/@elizarov/structured-concurrency-722d765aa952
Upvotes: 0