Will this code leak resources when the coroutine is canceled? - kotlin

Will the following code leak resources when the Kotlin coroutine is canceled?
General: The code is nested inside a ViewModel!
The method retrievePDFDocument will get triggered in the onStart Event of a Fragment.
fun retrievePDFDocument() {
job = viewModelScope.launch {
withContext(Dispatchers.IO) {
downloadFile(assetPath.value!!)
}
}
}
And here the suspend function:
private fun downloadFile(strPdfUrl: String): File? {
var inputStream: InputStream? = null
val lenghtOfFile: Int //lenghtOfFile is used for calculating download progress
//this is where the file will be seen after the download
var fileOut: FileOutputStream? = null
var localPDFFile: File? = null
if(strPdfUrl.isBlank())
return localPDFFile
try {
val pdfUrl = URL(strPdfUrl)
val urlConnection = pdfUrl.openConnection() as HttpURLConnection
if (urlConnection.responseCode == 200) {
//file input is from the url
inputStream = BufferedInputStream(urlConnection.inputStream)
lenghtOfFile = urlConnection.contentLength
localPDFFile = File(localPdfDirectory, pdfFileName.value!!)
fileOut = FileOutputStream(localPDFFile)
//here’s the download code
val buffer = ByteArray(1024)
var total: Long = 0
while (true) {
// If coroutine is cancelled
// a CancellationException will be thrown here
// Do not more work then necesarry
coroutineContext.ensureActive()
val length = inputStream.read(buffer)
if (length <= 0) break
total += length.toLong()
_currProgress.postValue( (total * 100 / lenghtOfFile).toInt() )
fileOut.write(buffer, 0, length)
}
}
} catch (e: IOException) {
Log.e("PdfViewerViewModel - downloadFile Error: ${e.message}")
localPdfFile?.delete() // remove partially downloaded file
localPDFFile = null
} catch (e1: CancellationException) {
Log.e("PdfViewerViewModel - downloadFile CancellationException: ${e1.message}")
localPdfFile?.delete() // remove partially downloaded file
localPdfFile = null
finally {
try {
inputStream?.close()
fileOut?.apply {
flush()
close()
}
} catch (e1: IOException) { //do nothing here }
}
return localPDFFile
}
Kindly regards
Frank
#Update 10.04.2020
Implementation with coroutineContext.ensureActive() and catching the exception

Related

Does cancelling a coroutine cancel the write to a file?

suspend fun copy(oldFile: File, newFile: File): Boolean{
return withContext(Dispatchers.IO) {
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
try {
val fileReader = ByteArray(4096)
inputStream = oldFile.inputStream()
outputStream = FileOutputStream(newFile)
while (true) {
val read: Int = inputStream.read(fileReader)
if (read == -1) {
break
}
outputStream.write(fileReader, 0, read)
}
outputStream.flush()
true
} catch (e: IOException) {
Log.e(TAG, "${e.message}")
false
} finally {
inputStream?.close()
outputStream?.close()
}
}
}
In the above code, if I cancel the job that is running the function, does the copying gets cancelled or do I have to manually check for state of the job inside while loop using ensureActive()?

Debug is working but release not - Kotlin receives data via Bluetooth

I am working on Bluetooth receiving and sending data. I can send data via Bluetooth but it doesn't work the receive data. I want to get string data and split it. And the split data will show the list view. Since data import is not working, I created string data and called the split method, but it didn't work here either in a release. Can you help me?
Here are my Activity codes;
var mUUID: UUID = UUID.fromString("00001101-0000-1000-8000-00805F9B34FB")
var mBluetoothSocket: BluetoothSocket? = null
lateinit var mProgress: ProgressDialog
lateinit var mBluetoothAdapter: BluetoothAdapter
var mIsConnected: Boolean = false
lateinit var mAddress: String
lateinit var inputStream: InputStream
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityListBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
mAddress = intent.getStringExtra(MainActivity.EXTRA_ADDRESS).toString()
ConnectToDevice(this).execute()
run()
binding.deneme.setOnClickListener {
sendCommand("denemebeyza")
Log.d("mesajBT", "onCreate: mesaj gonderildi")
}
val string2: String = "deneme, deneme02K, deneme90,klm08B,bitti."
splitList(string2)
}
private fun sendCommand(input: String) {
if (mBluetoothSocket != null) {
try {
mBluetoothSocket!!.outputStream.write(input.toByteArray())
} catch (e: IOException) {
e.printStackTrace()
}
}
}
fun run() {
val LOGTAG: String ="ReadListString"
Log.i(LOGTAG, Thread.currentThread().name)
val buffer = ByteArray(8)
var bytes: Int
val text1: TextView = binding.text2
var readText : String
Log.d("mesajBt", "mesaj metodu")
//Loop to listen for received bluetooth messages
if (mBluetoothSocket != null) {
while (true) {
bytes = try {
bytes = inputStream.read(buffer) ?:0
readText = String(buffer, 0, bytes)
Log.e("Arduino Message", readText)
} catch (e: IOException) {
e.printStackTrace()
Log.d("Else", "message alinamadi.")
break
}
text1.text =readText
}
}
}
fun splitList(output: String) {
val listView : ListView= binding.list1
val textView: TextView= binding.text1
if (mBluetoothSocket != null) {
try {
val list: List<String> = output.split(",").toList()
var arrayAdapter: ArrayAdapter<*>
for (it in list) {
Log.e("TAG", "splitList: $list ")
val list2: ArrayList<String> = ArrayList(list)
textView.text = list2.toString()
}
arrayAdapter = ArrayAdapter(this, android.R.layout.simple_list_item_1, list)
listView.adapter=arrayAdapter
} catch (e: IOException) {
e.printStackTrace()
}
// [A, B, C, D]
}
}
private class ConnectToDevice(c: Context) : AsyncTask<Void, Void, String>() {
private var connectSuccess: Boolean = true
#SuppressLint("StaticFieldLeak")
val context: Context = c
#Deprecated("Deprecated in Java")
override fun onPreExecute() {
super.onPreExecute()
mProgress = ProgressDialog.show(
context,
context.getString(R.string.connecting),
context.getString(R.string.connecting)
)
}
#SuppressLint("MissingPermission")
override fun doInBackground(vararg p0: Void?): String? {
try {
if (mBluetoothSocket == null || !mIsConnected) {
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
val device: BluetoothDevice = mBluetoothAdapter.getRemoteDevice(mAddress)
mBluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(mUUID)
BluetoothAdapter.getDefaultAdapter().cancelDiscovery()
mBluetoothSocket!!.connect()
inputStream = mBluetoothSocket!!.inputStream
}
}catch (e: IOException) {
connectSuccess = false
e.printStackTrace()
}
return null
}
override fun onPostExecute(result: String?) {
super.onPostExecute(result)
if (!connectSuccess) {
Log.i("data", "couldn't connect")
} else {
mIsConnected = true
}
mProgress.dismiss()
}
}
}
Here is debugged console log.e output
Thanks, advance :)
I/ReadList-run: main
E/mesajBt: mesaj metodu
W/BluetoothAdapter:getBluetoothService() called with no BluetoothManagerCallback
W/libEGL: EGLNativeWindowType 0xc9a7d808 disconnect failed
W/libEGL:EGLNativeWindowType 0xc95be008 disconnect failed
I/InputMethodManager: startInputInner mService.startInputOrWindowGainedFocus E/ViewRootImpl:sendUserActionEvent() returned.
I/InputMethodManager: startInputInner - mService.startInputOrWindowGainedFocus
Since there will be commas between words in data coming from Bluetooth, I used a comma as a separator in the split method. After some research, I corrected a few mistakes. You can change the value of the bytes as 8-16-32-64 according to the incoming data type and size.
Receive method is here
private fun run() {
//Loop to listen for received bluetooth messages
if (mBluetoothSocket != null) {
val TAG: String = "ReadList-run"
Log.i(TAG, Thread.currentThread().name)
val buffer = ByteArray(1024)
var bytes: Int
var readText: String
Log.e("mesajBt", "mesaj metodu")
while (true) {
try {
bytes = inputStream.read(buffer)
readText = String(buffer, 0, bytes)
Log.e("Arduino Message", readText)
splitlist(readText)
break
} catch (e: IOException) {
e.printStackTrace()
Log.d("Else", "message alinamadi.")
break
}
}
}
}
Here is my split method
fun splitList(output: String) {
if (mBluetoothSocket != null) {
try {
val list: List<String> = output.split(",").toList()
for (it in list) {
Log.e("TAG", "splitList: $list ")
}
} catch (e: IOException) {
e.printStackTrace()
}
// [A, B, C, D]
}
}

Need help completing the kotlinlang suggested exercise supporting Webhooks with javafx

In the tutorial, they teach how to support real-time p2p command-line messaging using websockets by implementing both client and server. I'm trying to finish an exercise where I have the client input messages via a javafx gui and receive messages inside the gui in the form of a chat log (basically a chat room)
I'm having trouble simply starting up the gui and the websocket together. I tried GlobalScope.launch in hopes that both would get run, but only the GUI gets launched. If I use runBlocking instead, only the websocket is active.
Here's what I have so far.
Other issues:
Don't know how to reference the javafx label variable inside the outputMessages function, so that we can update the chatlog. I try placing the label variable in the global scope, but it only results in a compile error, so I put it back inside SAKApplication.
How to update the label field to move to the next line (tried adding "/n" but it literally added "\n")
import java.util.Queue
import java.util.LinkedList
//var a = Label("s")
val messagesToSend: Queue<String> = LinkedList<String>()
class SAKApplication : Application() {
val l = Label("no text")
override fun start(primaryStage: Stage) {
val btn = Button()
btn.text = "Say 'Hello World'"
btn.onAction = EventHandler<ActionEvent> { println("Hello World!") }
val root = StackPane()
root.children.add(btn)
val textField = TextField()
// a = l
// action event
val event: EventHandler<ActionEvent> =
EventHandler {
l.text += "/n" + textField.getText()
messagesToSend.add(textField.getText())
}
// when enter is pressed
textField.setOnAction(event)
// add textfield
root.children.add(textField)
root.children.add(l)
val scene = Scene(root, 300.0, 250.0)
if (primaryStage != null) {
primaryStage.title = "Hello World!"
primaryStage.scene = scene
primaryStage.show()
}
val client = HttpClient {
install(WebSockets)
}
GlobalScope.launch {
client.webSocket(method = HttpMethod.Get, host = "127.0.0.1", port = 8080, path = "/chat") {
while(true) {
val messageOutputRoutine = launch { outputMessages() }
val userInputRoutine = launch { inputMessages() }
userInputRoutine.join() // Wait for completion; either "exit" or error
messageOutputRoutine.cancelAndJoin()
}
}
}
client.close()
}
}
fun main(args: Array<String>) {
Application.launch(SAKApplication::class.java, *args)
}
suspend fun DefaultClientWebSocketSession.outputMessages() {
try {
for (message in incoming) {
message as? Frame.Text ?: continue
// a.text += "/n" + message.readText()
println(message.readText())
}
} catch (e: Exception) {
println("Error while receiving: " + e.localizedMessage)
}
}
suspend fun DefaultClientWebSocketSession.inputMessages() {
val name = readLine() ?: ""
send(name)
while (true) {
sleep(1)
if (messagesToSend.isEmpty()) { continue }
val message = messagesToSend.remove()
if (message.equals("exit", true)) return
try {
send(message)
} catch (e: Exception) {
println("Error while sending: " + e.localizedMessage)
return
}
}
}

Kotlin coroutine for executing external process

The traditional approach in Java to execute an external process is to start a new Process, start two threads to consume its inputStream and errorStream and then call its blocking Process.waitFor() to wait till the external command has exited.
How can this be done in a (almost) non-blocking style with Kotlin coroutines?
I tried it this way. Do you have any suggestions to improve it?
(How to asynchronously read the streams, also call ProcessBuilder.start() in withContext(Dispatchers.IO), are there too many calls to Dispatchers.IO, ...?)
suspend fun executeCommand(commandArgs: List<String>): ExecuteCommandResult {
try {
val process = ProcessBuilder(commandArgs).start()
val outputStream = GlobalScope.async(Dispatchers.IO) { readStream(process.inputStream) }
val errorStream = GlobalScope.async(Dispatchers.IO) { readStream(process.errorStream) }
val exitCode = withContext(Dispatchers.IO) {
process.waitFor()
}
return ExecuteCommandResult(exitCode, outputStream.await(), errorStream.await())
} catch (e: Exception) {
return ExecuteCommandResult(-1, "", e.localizedMessage)
}
}
private suspend fun readStream(inputStream: InputStream): String {
val readLines = mutableListOf<String>()
withContext(Dispatchers.IO) {
try {
inputStream.bufferedReader().use { reader ->
var line: String?
do {
line = reader.readLine()
if (line != null) {
readLines.add(line)
}
} while (line != null)
}
} catch (e: Exception) {
// ..
}
}
return readLines.joinToString(System.lineSeparator())
}

Kotlin -Coroutine request won't wait till first request finish

I have 2 requests: SIGNUP and SIGNUP_UPLOAD_AVATAR
#POST(SIGNUP)
fun registerUser(#QueryMap(encoded = true) userCredentials: HashMap<String, Any>): Deferred<Response<UserResponse>>
#Multipart
#POST(SIGNUP_UPLOAD_AVATAR) //SHOULD BE A PUT, DUE TO ONLY SEND FILE.
fun uploadAvatar(#Part file: MultipartBody.Part): Deferred<Response<ResponseBody>>
currently i decided to change for the use of COROUTINES however im having an issue, that is when the first post SIGNUP_UPLOAD_AVATAR start his request, i need to wait till it finish to go with the SIGNUP process. However the second coroutine start immediately, without asking if the first request finish or is still working.
This is my function:
fun getImageUrlCoRoutine(){
val requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), profilePicture)
val body = MultipartBody.Part.createFormData("file", "android.${getFileExtension(profilePicture)}", requestFile)
val service = APIService.create()
val request = service.uploadAvatar(body)
try {
GlobalScope.launch(Dispatchers.Main) {
val response = CoroutineUtil().retryIO (times = 3){ request.await() }
val responseCode = StatusCode(response.code()).description
when(responseCode){
StatusCode.Status.OK -> {
response.body()?.let {
it.let {
println("RESULT: " + it.string())
avatarUrl = it.string()
println("Avatar: " + avatarUrl)
registrationCoroutine(this) <- here goes for the second request(registration)
}
}
}
else -> {}
}
}
}catch (e: HttpException){
val responseCode = StatusCode(e.code()).description
when(responseCode){
StatusCode.Status.NotAcceptable -> {
}
else -> {
}
}
}catch (e: Throwable){
println("Houston we got a Coroutine Problem")
println(e)
}
}
fun registrationCoroutine(suspended: CoroutineScope) {
val service = APIService.create()
val data = HashMap<String, Any>()
data["email"] = email
data["phone"] = phoneNumber
data["username"] = email
data["password"] = password
data["fullName"] = fullname
data["token"] = ""
data["appName"] = BuildConfig.APP_NAME
data["avatarUrl"] = avatarUrl
data["deviceType"] = BuildConfig.PLATFORM
data["needsVerify"] = false
suspended.launch {
val request = service.registerUser(data)
try {
val response = CoroutineUtil().retryIO(times = 3) { request.await() }
val responseCode = StatusCode(response.code()).description
when(responseCode){
StatusCode.Status.OK -> {
response.body()?.let {
it.let {
println(it)
}
}
}
else -> {}
}
} catch (e: HttpException) {
val responseCode = StatusCode(e.code()).description
when(responseCode){
StatusCode.Status.NotAcceptable -> {}
else -> {}
}
} catch (e: Throwable) {
println("Houston we have a coroutine problem")
println(e)
}
}
}
and the responses that i get...
2019-06-25 08:41:28.858 19886-19886/com.multirequest.development
I/System.out: RESULT:
2019-06-25 08:41:28.859 19886-19886/com.multirequest.development
I/System.out: Avatar:
2019-06-25 08:41:28.880 19886-20735/com.multirequest.development
D/OkHttp: --> POST
http://myCustomURL.com/signup?deviceType=ANDROID&password=demdemdem&needsVerify=false&phone=+1123456789&avatarUrl=&appName=DEMO&fullName=demmmm&email=demm#gmail.com&username=demm#gmail.com&token=
201
And i need that when i get the AvatarURL the signup process begin...
Thanks :)