Kotlin Flows обратный вызов взаимодействия с Java
Я искал подходящее решение или передовую практику, когда хочу использовать Kotlin Flows с обычными обратными вызовами. Мой вариант использования состоит в том, что я пишу библиотеку kotlin, которая использует Kotlin Flow внутри, и я должен предположить, что пользователи будут использовать, например, Java. Поэтому я подумал, что лучшее решение - перегрузить базовый интерфейс обратного вызова для моего метода потока и вызвать его вcollect
что-то вроде этого:
class KotlinClass {
interface Callback {
fun onResult(result: Int)
}
private fun foo() = flow {
for (i in 1..3) {
emit(i)
}
}
fun bar(callback: Callback) {
runBlocking {
foo().collect { callback.onResult(it) }
}
}
private fun main() {
bar(object : Callback {
override fun onResult(result: Int) {
TODO("Not yet implemented")
}
})
}
и в моем приложении Java я могу просто использовать это так:
public class JavaClass {
public void main() {
KotlinClass libraryClass = new KotlinClass();
libraryClass.bar(new KotlinClass.Callback() {
@Override
public void onResult(int result) {
// TODO("Not yet implemented")
}
});
}
}
Я не уверен, что мне делать, потому что я хотел бы иметь свою библиотеку Kotlin, которая использует потоки, подходящие для Java и Kotlin.
Я наткнулся callbackFlow
но это, кажется, только в том случае, если я хочу называть это потоком API на основе обратного вызова? Поскольку я новичок в Kotlin и Flows, пожалуйста, извините, если мой вопрос ошибочен из-за отсутствия некоторых основных концепций kotlin.
3 ответа
Я бы дал клиенту Java больше контроля над потоком. Я бы добавилonStart
а также onCompletion
к вашему интерфейсу обратного вызова. Кроме этого, я бы использовал собственныйCoroutineScope
- возможно, настраиваемый из клиента Java. И я бы не стал блокировать вызывающий поток изнутри функции Kotlin - нетrunBlocking
.
@InternalCoroutinesApi
class KotlinClass {
val coroutineScope = CoroutineScope(Dispatchers.Default)
interface FlowCallback {
@JvmDefault
fun onStart() = Unit
@JvmDefault
fun onCompletion(thr: Throwable?) = Unit
fun onResult(result: Int)
}
private fun foo() = flow {
for (i in 1..3) {
emit(i)
}
}
fun bar(flowCallback: FlowCallback) {
coroutineScope.launch {
foo().onStart { flowCallback.onStart() }
.onCompletion { flowCallback.onCompletion(it) }
.collect { flowCallback.onResult(it) }
}
}
fun close() {
coroutineScope.cancel()
}
}
Теперь клиент Java полностью контролирует запуск, сбор и отмену потока. Например, вы можете использовать защелку, чтобы дождаться завершения, установить тайм-аут и отменить область действия программы. В первую очередь это выглядит как большой объем кода, но обычно вам понадобится такая гибкость.
public class JavaClass {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
KotlinClass libraryClass = new KotlinClass();
libraryClass.bar(new KotlinClass.FlowCallback() {
@Override
public void onCompletion(@Nullable Throwable thr) {
latch.countDown();
}
@Override
public void onResult(int result) {
System.out.println(result);
}
});
try {
latch.await(5, TimeUnit.SECONDS);
} finally {
libraryClass.close();
}
}
}
Вам не нужно создавать интерфейс в коде Kotlin. Вы можете определить бар так:
fun bar(callback: (Int) -> Unit) {
runBlocking {
foo().collect { callback(it) }
}
}
Из кода Java вы можете вызвать такую функцию:
public class JavaClass {
public static void main(String[] args) {
KotlinClass libraryClass = new KotlinClass();
libraryClass.bar(v -> { System.out.println(v); return Unit.INSTANCE; });
}
}
В случае, если кому-то интересно общее решение. Вот наша версия улучшения от ответа @rene здесь .
- Принять общий тип
- Настраиваемый
coroutineScope
// JavaFlow.kt
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.cancel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onCompletion
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.launch
@InternalCoroutinesApi
class JavaFlow<T>(
private val coroutineScope: CoroutineScope = CoroutineScope(Dispatchers.Default)
) {
interface OperatorCallback <T> {
@JvmDefault
fun onStart() = Unit
@JvmDefault
fun onCompletion(thr: Throwable?) = Unit
fun onResult(result: T)
}
fun collect(
flow: Flow<T>,
operatorCallback: OperatorCallback<T>,
) {
coroutineScope.launch {
flow
.onStart { operatorCallback.onStart() }
.onCompletion { operatorCallback.onCompletion(it) }
.collect { operatorCallback.onResult(it) }
}
}
fun close() {
coroutineScope.cancel()
}
}
Вызывающая сторона Java:
// code omitted...
new JavaFlow<File>().collect(
// compressImageAsFlow is our actual kotlin flow extension
FileUtils.compressImageAsFlow(file, activity),
new JavaFlow.OperatorCallback<File>() {
@Override
public void onResult(File result) {
// do something with the result here
SafeSingleton.setFile(result);
}
}
);
// or using lambda with method references
// new JavaFlow<File>().collect(
// FileUtils.compressImageAsFlow(file, activity),
// SafeSingleton::setFile
// );
// Change coroutineScope to Main
// new JavaFlow<File>(CoroutineScopeKt.MainScope()).collect(
// FileUtils.compressImageAsFlow(file, activity),
// SafeSingleton::setFile
// );
OperatorCallback.onStart
а такжеOperatorCallback.onCompletion
является необязательным, переопределите его при необходимости.