Since AsyncTask is deprecated from Android R, i cannot parse or accept a JSON Data from the webhttp://plantplaces.com/perl/mobile/flashcard.pl. So what i did is. I created a kotlin class named DownloadingObject. Below is the code
class DownloadingObject {
#Throws(IOException::class)
fun downloadJSONDataFromLink(link: String): String {
val stringBuilder: StringBuilder = StringBuilder()
val url: URL = URL(link)
val urlConnection = url.openConnection() as HttpURLConnection
try {
val bufferedInputString: BufferedInputStream =
BufferedInputStream(urlConnection.inputStream)
val bufferedReader: BufferedReader =
BufferedReader(InputStreamReader(bufferedInputString))
// temporary string to hold each line read from the BufferedReader.
var inputLineString: String?
inputLineString = bufferedReader.readLine()
while (inputLineString != null) {
stringBuilder.append(inputLineString)
inputLineString = bufferedReader.readLine()
}
} finally {
// regardless of success of Try Block or failure of Try Block, we will disconnect from the URLConnection.
urlConnection.disconnect()
}
return stringBuilder.toString()
}
And i have to run this downloading task in the background. Below is the code
inner class DownloadingPlantTask : AsyncTask>() {
override fun doInBackground(vararg params: String?): List<Plant>? {
// Can access background thread. Not user interface thread
val downloadingObject: DownloadingObject = DownloadingObject()
var jsonData = downloadingObject.downloadJSONDataFromLink(
"http://plantplaces.com/perl/mobile/flashcard.pl"
)
Log.i("JSON", jsonData)
return null
}
Please if anyone can provide any alternative codes. I am just a bieginner in Android development.
There are several ways to achieve multithreading on Android. Since you're using Kotlin you may want to look into Kotlin Coroutines. Some other things out there are the java.concurrent.* package and RxJava, but in my opinion Coroutines are more beginner-friendly and they integrate with Android components very well.
With coroutines, your code would look like this:
#Throws(IOException::class)
suspend fun downloadJSONDataFromLink(link: String): String { ... }
GlobalScope.launch {
val downloadingObject = DownloadingObject()
val jsonData = downloadingObject.downloadJSONDataFromLink("http://plantplaces.com/perl/mobile/flashcard.pl")
Log.i("JSON", jsonData)
}
The suspend keyword means this function can only be called from a coroutine or from another suspending function.
Also you should consider using your Activity/Fragment's lifecycle scope instead of GlobalScope to avoid memory leaks.
Related
What I'm trying to do
I have an app that's using Room with Coroutines to save search queries in the database. It's also possible to add search suggestions and later on I retrieve this data to show them on a list. I've also made it possible to "pin" some of those suggestions.
My data structure is something like this:
#Entity(
tableName = "SEARCH_HISTORY",
indices = [Index(value = ["text"], unique = true)]
)
data class Suggestion(
#PrimaryKey(autoGenerate = true)
#ColumnInfo(name = "suggestion_id")
val suggestionId: Long = 0L,
val text: String,
val type: SuggestionType,
#ColumnInfo(name = "insert_date")
val insertDate: Calendar
)
enum class SuggestionType(val value: Int) {
PINNED(0), HISTORY(1), SUGGESTION(2)
}
I have made the "text" field unique to avoid repeated suggestions with different states/types. E.g.: A suggestion that's a pinned item and a previously queried text.
My Coroutine setup looks like this:
private val parentJob: Job = Job()
private val IO: CoroutineContext
get() = parentJob + Dispatchers.IO
private val MAIN: CoroutineContext
get() = parentJob + Dispatchers.Main
private val COMPUTATION: CoroutineContext
get() = parentJob + Dispatchers.Default
And my DAOs are basically like this:
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(obj: Suggestion): Long
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(objList: List<Suggestion>): List<Long>
I also have the following public functions to insert the data into the database:
fun saveQueryToDb(query: String, insertDate: Calendar) {
if (query.isBlank()) {
return
}
val suggestion = Suggestion(
text = query,
insertDate = insertDate,
type = SuggestionType.HISTORY
)
CoroutineScope(IO).launch {
suggestionDAO.insert(suggestion)
}
}
fun addPin(pin: String) {
if (pin.isBlank()) {
return
}
val suggestion = Suggestion(
text = pin,
insertDate = Calendar.getInstance(),
type = SuggestionType.PINNED
)
CoroutineScope(IO).launch {
suggestionDAO.insert(suggestion)
}
}
fun addSuggestions(suggestions: List<String>) {
addItems(suggestions, SuggestionType.SUGGESTION)
}
private fun addItems(items: List<String>, suggestionType: SuggestionType) {
if (items.isEmpty()) {
return
}
CoroutineScope(COMPUTATION).launch {
val insertDate = Calendar.getInstance()
val filteredList = items.filterNot { it.isBlank() }
val suggestionList = filteredList.map { History(text = it, insertDate = insertDate, suggestionType = suggestionType) }
withContext(IO) {
suggestionDAO.insert(suggestionList)
}
}
}
There are also some other methods, but let's focus on the ones above.
EDIT: All of the methods above are part of a lib that I made, they're are not made suspend because I don't want to force a particular type of programming to the user, like forcing to use Rx or Coroutines when using the lib.
The problem
Let's say I try to add a list of suggestions using the addSuggestions() method stated above, and that I also try to add a pinned suggestion using the addPin() method. The pinned text is also present in the suggestion list.
val list = getSuggestions() // Getting a list somewhere
addSuggestions(list)
addPin(list.first())
When I try to do this, sometimes the pin is added first and then it's overwritten by the suggestion present in the list, which makes me think I might've been dealing with some sort of race condition. Since the addSuggestions() method has more data to handle, and both methods will run in parallel, I believe the addPin() method is completing first.
Now, my Coroutines knowledge is pretty limited and I'd like to know if there's a way to enqueue those method calls and make sure they'll execute in the exact same order I invoked them, that must be strongly guaranteed to avoid overriding data and getting funky results later on. How can I achieve such behavior?
I'd follow the Go language slogan "Don't communicate by sharing memory; share memory by communicating", that means instead of maintaining atomic variables or jobs and trying to synchronize between them, model your operations as messages and use Coroutines actors to handle them.
sealed class Message {
data AddSuggestions(val suggestions: List<String>) : Message()
data AddPin(val pin: String) : Message()
}
And in your class
private val parentScope = CoroutineScope(Job())
private val actor = parentScope.actor<Message>(Dispatchers.IO) {
for (msg in channel) {
when (msg) {
is Message.AddSuggestions -> TODO("Map to the Suggestion and do suggestionDAO.insert(suggestions)")
is Message.AddPin -> TODO("Map to the Pin and do suggestionDAO.insert(pin)")
}
}
}
fun addSuggestions(suggestions: List<String>) {
actor.offer(Message.AddSuggestions(suggestions))
}
fun addPin(pin: String) {
actor.offer(Message.AddPin(pin))
}
By using actors you'll be able to queue messages and they will be processed in FIFO order.
By default when you call .launch{}, it launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job. The coroutine is canceled when the resulting job is canceled.
It doesn't care or wait for other parts of your code it just runs.
But you can pass a parameter to basically tell it to run immediately or wait for other Coroutine to finish(LAZY).
For Example:
val work_1 = CoroutineScope(IO).launch( start = CoroutineStart.LAZY ){
//do dome work
}
val work_2 = CoroutineScope(IO).launch( start = CoroutineStart.LAZY ){
//do dome work
work_1.join()
}
val work_3 = CoroutineScope(IO).launch( ) {
//do dome work
work_2.join()
}
When you execute the above code first work_3 will finish and invoke work_2 when inturn invoke Work_1 and so on,
The summary of coroutine start options is:
DEFAULT -- immediately schedules coroutine for execution according to its context
LAZY -- starts coroutine lazily, only when it is needed
ATOMIC -- atomically (in a non-cancellable way) schedules coroutine for execution according to its context
UNDISPATCHED -- immediately executes coroutine until its first suspension point in the current thread.
So by default when you call .launch{} start = CoroutineStart.DEFAULT is passed because it is default parameter.
Don't launch coroutines from your database or repository. Use suspending functions and then switch dispatchers like:
suspend fun addPin(pin: String) {
...
withContext(Dispatchers.IO) {
suggestionDAO.insert(suggestion)
}
}
Then from your ViewModel (or Activity/Fragment) make the call:
fun addSuggestionsAndPinFirst(suggestions: List<Suggestion>) {
myCoroutineScope.launch {
repository.addSuggestions(suggestions)
repository.addPin(suggestions.first())
}
}
Why do you have a separate addPin() function anyways? You can just modify a suggestion and then store it:
fun pinAndStoreSuggestion(suggestion: Suggestion) {
myCoroutineScope.launch {
repository.storeSuggestion(suggestion.copy(type = SuggestionType.PINNED)
}
}
Also be careful using a Job like that. If any coroutine fails all your coroutines will be cancelled. Use a SupervisorJob instead. Read more on that here.
Disclaimer: I do not approve of the solution below. I'd rather use an old-fashioned ExecutorService and submit() my Runnable's
So if you really want to synchronize your coroutines in a way that the first function called is also the first one to write to your database. (I'm not sure it is guaranteed since your DAO functions are also suspending and Room uses it's own threads too). Try something like the following unit test:
class TestCoroutineSynchronization {
private val jobId = AtomicInteger(0)
private val jobToRun = AtomicInteger(0)
private val jobMap = mutableMapOf<Int, () -> Unit>()
#Test
fun testCoroutines() = runBlocking {
first()
second()
delay(2000) // delay so our coroutines finish
}
private fun first() {
val jobId = jobId.getAndIncrement()
CoroutineScope(SupervisorJob() + Dispatchers.Default).launch {
delay(1000) // intentionally delay your first coroutine
withContext(Dispatchers.IO) {
submitAndTryRunNextJob(jobId) { println(1) }
}
}
}
private fun second() {
val jobId = jobId.getAndIncrement()
CoroutineScope(SupervisorJob()).launch(Dispatchers.IO) {
submitAndTryRunNextJob(jobId) { println(2) }
}
}
private fun submitAndTryRunNextJob(jobId: Int, action: () -> Unit) {
synchronized(jobMap) {
jobMap[jobId] = action
tryRunNextJob()
}
}
private fun tryRunNextJob() {
var action = jobMap.remove(jobToRun.get())
while (action != null) {
action()
action = jobMap.remove(jobToRun.incrementAndGet())
}
}
}
So what I do on each call is increment a value (jobId) that is later used to prioritize what action to run first. Since you are using suspending function you probably need to add that modifier to the action submitted too (e.g. suspend () -> Unit).
In my app, I get a set of urls to some images from an api and need to create Bitmap objects out of those urls to be able do display the images in the UI. I saw that the android docs recommend using corutines for performing such async tasks, but I am not sure how to do it properly.
Using OkHttp for my http client, I tried the following approach:
GlobalScope.launch {
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
for (i in 0 until parsedRes.size) {
val bitmap =
GlobalScope.async { createBitmapFromUrl(parsedRes[i].best_book.image_url) }
parsedRes[i].best_book.imageBitmap = bitmap.await();
}
searchResults.postValue(parsedRes)
}
Where response is what I get back from my API, and searchResults is a LiveData that hold the parsed response.
Also, here is how I am getting the images from those urls:
suspend fun createBitmapFromUrl(url: String): Bitmap? {
val client = OkHttpClient();
val req = Request.Builder().url(url).build();
val res = client.newCall(req).execute();
return BitmapFactory.decodeStream(res.body?.byteStream())
}
Even though every fetch action is done on a separate coroutine, it's still too slow. Is there a better way of doing it? I can use any other http client if there is one out there optimized for use with coroutines, although I am new to Kotlin so I don't know any.
First of all the createBitmapFromUrl(url: String) does everything synchronously, you've to first stop them from blocking the coroutine thread, you may want to use Dispatchers.IO for that because callback isn't the most idomatic thing ever in coroutines.
val client = OkHttpClient() // preinitialize the client
suspend fun createBitmapFromUrl(url: String): Bitmap? = withContext(Dispatchers.IO) {
val req = Request.Builder().url(url).build()
val res = client.newCall(req).execute()
BitmapFactory.decodeStream(res.body?.byteStream())
}
Now, when you are calling bitmap.await() you are simply saying that "Hey, wait for the deferred bitmap and once it is finished resume the loop for next iteration"
So you may want to do the assignment in the coroutine itself to stop it from suspending the loop, otherwise create another loop for that. I'd go for first option.
scope.launch {
val gson = Gson();
val parsedRes = gson.fromJson(
response.body?.charStream(),
Array<GoodreadsBook>::class.java
);
// Create the bitmap from the imageUrl
for (i in 0 until parsedRes.size) {
launch {
parsedRes[i].best_book.imageBitmap = createBitmapFromUrl(parsedRes[i].best_book.image_url)
}
}
}
Use a library like the following that doesn't use the blocking execute method and instead bridges from the async enqueue.
https://github.com/gildor/kotlin-coroutines-okhttp
suspend fun main() {
// Do call and await() for result from any suspend function
val result = client.newCall(request).await()
println("${result.code()}: ${result.message()}")
}
What this basically does is the following
public suspend fun Call.await(): Response {
return suspendCancellableCoroutine { continuation ->
enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
if (continuation.isCancelled) return
continuation.resumeWithException(e)
}
})
continuation.invokeOnCancellation {
try {
cancel()
} catch (ex: Throwable) {
//Ignore cancel exception
}
}
}
}
I need to read a file located at a certain URL. I created such a function:
private fun urlRead() {
val url = URL(MY_URL)
val stream = url.openStream()
val v = stream.read()
}
And I call this function from onCreate. But it throws exception NetworkOnMainThreadException. Android requires applications to work with networks from other thread. Documentations recommends to use coroutines for simple multithreading tasks. But I cannot find good example of using coroutines in Kotlin in Android.
Can you give a short code example with code above using coroutines?
All you need to do is launch it within a coroutine context, like this
private suspend fun urlRead() = withContext(Dispatchers.IO) {
val url = URL(MY_URL)
val stream = url.openStream()
stream.read()
}
then you call it like this
lifecycleScope.launch {
val v = urlRead()
// TODO; use v
}
See this for reference.
Well, first of all:
It will be easier if you use Lifecycle api:
https://developer.android.com/jetpack/androidx/releases/lifecycle
If you make the request from an activity, you can use "lifecyclescope"
If you make from viewModel, you can use viewModelScope
Since a request may take a while to finish, you can't do it from the Main thread. You should use IO.
So, for exemple, if you use a viewModel:
class MyViewModel(): ViewModel() {
//some code
fun urlRead() {
viewModelScope.launch(Dispatchers.IO){
//here you make the requests
}
}
}
i find this coroutines tutorial quite clear and easy to digest https://kotlinlang.org/docs/reference/coroutines-overview.html
short code example
private fun urlRead() {
GlobalScope.launch(Dispatchers.IO) {
val url = URL(MY_URL)
val stream = url.openStream()
val v = stream.read()
}
}
Dispatchers.IO means the thread on the bracket code will run in background thread
for more on that read this https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html
I know that an AsyncTask can be run only once. I know a way around that, but I need a variable from the AsyncTask that uses complicated(?) processes. This is my code for calling the AsyncTask
val thr=NewTask()
thr.delegate = this
button.setOnClickListener {
thr.execute()
}
NewTask.doOnBackground() is just a normal method sending the request to the URL. onPostExecute() is a bit different:
public override fun onPostExecute(result: String?) {
//super.onPostExecute(result)
delegate!!.processFinish(result!!)
}
with delegate being a variable of AsyncResponse? which is an interface containing processFinish abstract method taking a string and returning nothing.
My question is, how can I run the AsyncTask repeatedly while still getting the response? Thanks in advance.
Finally, I settled on using coroutines with this. Coroutines are easy to use, much easier than AsyncTask. I don't know why I was scared of them. 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)
}
}
I'm working in android studio, and am using kotlin coroutines to retrieve results from an API.
I need to wait til the coroutine is finished, so I can assign a global variable from it.
I've tested the URL and it's OK.
I've tried regular threads, which works, but am not able to make the main thread wait for it to finish.
I tried with Fuel.get() and it worked fine, but would like to use URL().
var response = "";
val req = "url.com"
runBlocking { launch {
response = URL(req).readText()
} }
Can anyone tell me why this code doesn't work? It throws an NetworkOnMainThreadException, but it's wrapped in a coroutine.
I fixed it. Ended up using a AsyncTask to read from URL, and a Handler to schedule handling the results.
var response = ""
#SuppressLint("StaticFieldLeak")
inner class Retriever : AsyncTask<String, String, String>() {
override fun doInBackground(vararg args : String?): String {
val urlRequest = args[0].toString()
var urlResponse = "";
//Try to extract url
try {
urlResponse = URL(urlRequest).readText()
println("SUCCESS in Retrieve.")
} catch (e : Exception) {
println("EXCEPTION in Retrieve.")
e.printStackTrace()
}
return urlResponse;
}
//Assigns value to response
override fun onPostExecute(result: String?) {
response = result.toString() //Result possibly void type
}
}
override fun onCreate() {
Retriever().execute("url.com")
Handler().{/*Handle response here*/, 10000)
}
Try this way
var response = "";
val req = "url.com"
runBlocking<Unit> {
GlobalScope.launch {
response = URL(req).readText()
}
//Work with the response here
}
You can preview all coroutine documentation here
If you just want handle the result of URL(req).readText() in another thread,do as following codes.
var response = "";
val req = "url.com"
runBlocking<Unit> {
GlobalScope.launch {
response = URL(req).readText()
//here is another thread,handle response here
}
//here is main thread, you can't get the result of URL(req).readText() because io operation need a long time .
}
If you handle the result in the main thread , please use Hanlder class
For me the below code works smoothly.
var response = "";
val req = "yoururl.com"
runBlocking {
try {
withTimeout(5000) { // 5 seconds for timeout
launch(Dispatchers.IO) { // using IO Dispatcher and not the default
response = URL(req).readText()
} // launch
} // timeout
} catch (e:Exception) { // Timeout, permission, URL or network error
response="Error" // Here one uses a not valid message
}
// Here one manages 'response' variable for error handling or valid output.
It's also important add the permission in AndroidManifest.xml, inside main manifest tag:
<uses-permission android:name="android.permission.INTERNET"/>
An advantage of this type of approach (for tasks that are not too long ) is simplicity because it does not require the use of a callback routine, since it is a sequential code.