Is there a tidy way to do chained initialisation logic in Kotlin? - kotlin

After opening a file, someone has to close it.
Kotlin has the convenient use { ... } method for the case where you're doing all the work with the file in-place.
But sometimes, you have to do some other things with the file and return another object which gains ownership.
Currently, I am doing something like this:
fun open(path: Path, fileMode: FileMode): StoreFile {
var success = false
val channel = FileChannelIO.open(path, fileMode)
try {
if (channel.size == 0 && fileMode == FileMode.READ_WRITE) {
writeInitialContent(channel)
}
val result = StoreFile(channel)
success = true
return result
} finally {
if (!success) {
channel.close()
}
}
}
writeInitialContent could fail, in which case I would have to close the file. If it doesn't fail, then StoreFile takes ownership and becomes the thing which the caller has to close.
This is a common kind of nastiness in Java, and the way I've had to write it in Kotlin isn't really any cleaner than Java, and with the way it's currently written, there's still the potential for double-closing if StoreFile itself also happens to close the file on failure.
You hit the same basic problem even trying to buffer a stream, which is also fairly common. In this case, I don't know whether buffered() will throw, and if it does, nobody closes the file. In this case, buffered() itself could deal with the ugliness, but I checked its code and it does not.
fun readSomething(path: Path): Something {
path.inputStream().buffered().use { stream ->
// ...
}
}
Is there a better way to structure this?

Related

Kotlin: retain coroutine context in scenario with nested runBlocking

I'm fairly new to kotlin coroutines, and I have what I think is a somewhat esoteric use case related to how runBlocking and coroutine contexts interact.
To start with, a simple example. Let's say I've got a dead simple context element. Nothing fancy.
class ExampleContext(val s: String) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key<ExampleContext>
}
When I run these examples, they behave exactly the way I'd expect them to:
runBlocking(ExampleContext("foo")) {
println(coroutineContext[ExampleContext.Key]?.s) // prints "foo
}
runBlocking(ExampleContext("foo")) {
launch {
println(coroutineContext[ExampleContext.Key]?.s) // prints "foo"
}
}
runBlocking(ExampleContext("foo")) {
launch(ExampleContext("bar")) {
println(coroutineContext[ExampleContext.Key]?.s) // prints "bar"
}
}
When I do this it prints null (as I would expect it to, because it runBlocking defaults to having EmptyContext in its constructor):
runBlocking(ExampleContext("foo")) {
runBlocking {
println(coroutineContext[ExampleContext.Key]?.s) // prints null
}
}
So here's my conundrum. The docs (and all the guidance I've found on the web) basically say don't do this: runBlocking is supposed to be run at the outermost layer of the coroutine logic and that's it. No nesting. What I'm working on is a library that needs to populate some context for access inside code that I don't own that gets called later (basically, you can think of it like an interceptor). The rough pseudocode looks a little like this:
class MyLibrary(otherPeoplesLogic: OtherPeoplesBusinessLogic) {
fun <IN, OUT> execute(input: IN): OUT {
... do my library's thing, including adding in a custom context element ...
try {
return otherPeoplesLogic.execute(input)
} finally {
... do my library's cleanup ...
}
}
}
To support coroutines in OtherPeoplesBusinessLogic, all I'd really have to do is add runBlocking like this:
class MyLibrary(otherPeoplesLogic: OtherPeoplesBusinessLogic) {
fun <IN, OUT> execute(input: IN): OUT {
... do my library's thing ...
runBlocking(myCustomContext) {
try {
return otherPeoplesLogic.execute(input)
} finally {
... do my library's cleanup ...
}
}
}
}
So long as all OtherPeoplesBusinessLogic::execute does is launch/async/etc, everything is fine: myCustomContext will be accessible. What I'm worried about is what happens if OtherPeoplesBusinessLogic::execute (which I'm not in control of) misbehaves and does its own runBlocking call with no context argument passed at all: what I think will happen is that myCustomContext will just silently get dropped like the example above. Not good, because it needs to be accessible.
Phew. A lot of explanation. Thanks for bearing with me. :)
So my ultimate question here is this: is there anything I can do (outside of scolding the users of my library to not call runBlocking) to prevent an accidental nested runBlocking call from dropping my context? Or am I just out of luck here and should scrap the whole idea?

Why is the value not entering the list?

At 'urichecking2' log, I can see there is value. But in 'uriChecking' the uriList is null.
why the uriList.add not work??
private fun getPhotoList() {
val fileName = intent.getStringExtra("fileName")
Log.d("fileNameChecking", "$fileName")
val listRef = FirebaseStorage.getInstance().reference.child("image").child(fileName!!)
var tmpUrl:Uri = Uri.parse(fileName)
Log.d("firstTmpUri","$tmpUrl")
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
tmpUrl = task.result
Log.d("secondTmpUri","$tmpUrl")
Log.d("urichecking2","$task.result")
uriList.add(task.result)
} else {
}
}.addOnFailureListener {
// Uh-oh, an error occurred!
}
}
}
Log.d("thirdTmpUri","$tmpUrl")
Log.d("urichecking", "$uriList")
}
If I do this, the log is output in the order of first, third, and second, and the desired value is in second, but when third comes out, it returns to the value of first.
The listAll method (like most cloud APIs these days, including downloadUrl which you also use) is asynchronous, since it needs to make a call to the server - which may take time. This means the code executes in a different order than you may expect, which is easiest to see if you add some logging:
Log.d("Firebase","Before starting listAll")
listRef.listAll()
.addOnSuccessListener { listResult ->
Log.d("Firebase","Got listResult")
}
Log.d("Firebase","After starting listAll")
When you run this code it outputs:
Before starting listAll
After starting listAll
Got listResult
This is probably not the order you expected, but it perfectly explains why you can't see the list result. By the time your Log.d("urichecking", "$uriList") runs, none of the uriList.add(task.result) has been called yet.
The solution for this is always the same: any code that needs the list result, has to be inside the addOnCompleteListener callback, be called from there, or be otherwise synchronized.
So in its simplest way:
listRef.listAll()
.addOnSuccessListener { listResult ->
for (item in listResult.items) {
item.downloadUrl.addOnCompleteListener { task ->
if (task.isSuccessful) {
uriList.add(task.result)
Log.d("urichecking", "$uriList")
}
}
}
}
This is an incredibly common mistake to make if you're new to programming with asynchronous APIs, so I recommend checking out
Asynchronous programming techniques in the Kotlin language guide
How to get URL from Firebase Storage getDownloadURL
Can someone help me with logic of the firebase on success listener
Why does my function that calls an API or launches a coroutine return an empty or null value?

kotlin flow using flatmap cannot call method in collect

lifecycleScope.launch {
adapter?.getData()?.let {
val flowable = it.asFlow()
flowable.onEach {
doCompress(it)
}.flatMapConcat {
flow<Unit> {
updateProgressInMain()
}.flowOn(Dispachers.Main)
}.catch {
dismissLoading()
}.flowOn(Dispatchers.IO).collect {
Log.d("Collect", "" + Thread.currentThread())
}
}
}
As above code, I cannot print 'Collect' log in console but other code can run well. However, I can print the log when I use 'WithContext()' in onEach period instead of flatMapConcat to switch Thread. Could anyone discribe what happened?
You produce an empty Flow that never emits in flatMapConcat, so the resulting Flow will never emit anything either.
Your code doesn't quite make sense to me, but supposing the task you want to do is, for each item emitted by the source LiveData as Flow:
Pass it to doCompress() on the IO Dispatcher. Apparently doCompress() doesn't return anything.
Call updateProgressInMain() on the main thread after eeach item is compressed.
And then call dismissLoading() whether or not it failed.
Then this simpler code should do it:
adapter?.getData()?.asFlow()?.onEach {
runCatching {
withContext(Dispatchers.IO) {
doCompress(it)
Log.d("Collect", "" + Thread.currentThread())
}
updateProgressInMain()
}
dismissLoading()
}?.launchIn(lifecycleScope)

CoroutineScope cancel listener

I'm performing some work in a class that is using a Scope:
class MyClass(val scope: CoroutineScope) {
private val state: StateFlow<Int> = someFlow()
.shareIn(scope, started = SharingStared.Eagerly, initialValue = 0)
fun save() {
scope.launch {
save(state.value)
}
}
}
Now I want to clean up when the scope is cancelled. What is the best way to do this? I could come up with this, but that doesn't really sound stable.
init {
scope.launch {
try { delay(10000000000000) }
finally { withContext(Noncancellable) { save(state.value) } }
}
}
Edit: I've modified my snippet to more reflect what I'm doing. The state Flow updates several times per second, and when I invoke the save() method I want to save the state to disk (So I don't want to do this every time the state changes).
Next to that, I want to save the state when the scope is cancelled (i.e. at the very end). This is where I'm having trouble.
There is no such "onCancellation" mechanism on CoroutineScope to my knowledge.
In general, clean up can be "prepared" on the spot when executing the code that requires cleanup. For instance, using an input stream with use { ... } or closing resources with finally blocks.
This will be automatically honored on cancellation (or any other failures, btw), because cancellation of the scope simply generates CancellationExceptions inside running coroutines.
Now, sometimes (as in your case) you have more complex needs, and in that case I would say that the cancellation of the scope is just one thing that happens at the end of some kind of lifecycle, and you can do the cleanup you need at the same place where you cancel the scope.
If you really want to use a workaround like your current parallel coroutine, you can use awaitCancellation instead of a huge delay:
init {
scope.launch {
try { awaitCancellation() }
finally { withContext(Noncancellable) { save(state.value) } }
}
}
But I still don't find it very appealing tbh.
You can use a Exception handler
// Destroy service when completed or in case of an error.
val handler = CoroutineExceptionHandler { _, exception ->
Log.e("CoroutineExceptionHandler Error", exception.message!!)
stopSelf(startId)
}
Then you can use this Handler as
scope.launch(handler){
// do stuff
}
handler will be called only if an exception is thrown

How to replace blocking code for reading bytes in Kotlin

I have ktor application which expects file from multipart in code like this:
multipart.forEachPart { part ->
when (part) {
is PartData.FileItem -> {
image = part.streamProvider().readAllBytes()
}
else -> // irrelevant
}
}
The Intellij IDEA marks readAllBytes() as inappropriate blocking call since ktor operates on top of coroutines. How to replace this blocking call to the appropriate one?
Given the reputation of Ktor as a non-blocking, suspending IO framework, I was surprised that apparently for FileItem there is nothing else but the blocking InputStream API to retrieve it. Given that, your only option seems to be delegating to the IO dispatcher:
image = withContext(Dispatchers.IO) { part.streamProvider().readBytes() }