Home > Software engineering >  Convert java listeners to kotlin flow
Convert java listeners to kotlin flow

Time:03-25

I'm developing on Android and in my source code there are a lot of listeners based on callback . I want to convert some part of those to a kotlin flow. I read many articles, where talks about callbackFlow, but I don't think it right choice to me.

In my case there is something like this:

interface ActionListener{
   public void actionStarted(actionId:Int)
   public void actionProgress(actionId:Int, elementCreated:ExampleElement)
   public void actionEnd(actionId:Int)
}

class MainProductor{
   ActionListener actionListener;
   
   MainProductor(ActionListener actionListener){
    this.actionListener = actionListener
   }
   
   void start(){
        //start some heavy works that start to call actionListener
   }
}

UseCase: when I call start some action with certain id starts and begin to create some ExampleElement In the UI I have to show a list of ExampleElement produced by the actions with id A and with id B.

In the UI I have to show a progress when one of both action starts and until all elements produced by the two actionIds are been created, (this part is soo tricky)

how can continue to use this ActionListener and adapt it to new kotlin flow ? Is possible way ?

CodePudding user response:

The main point of using callbackFlow would be that it automatically registers and unregisters a listener upon being started/cancelled. A possible solution to deal with multiple callback methods would be to create corresponding event classes and emit those.

sealed interface ActionListenerEvent {
    class Started(val actionId: Int) : ActionListenerEvent
    class ProgressChanged(val actionId: Int, val progress: Float, val elementCreated: ExampleElement) : ActionListenerEvent
    class Finished(val actionId: Int) : ActionListenerEvent
}


fun actionEvents() = callbackFlow {
    val listener = object : ActionListener {
        override fun actionStarted(actionId: Int) {
            trySend(ActionListenerEvent.Started(actionId))
        }

        override fun actionProgress(actionId: Int, progress: Float, elementCreated: ExampleElement) {
            trySend(ActionListenerEvent.ProgressChanged(actionId, progress, elementCreated))
        }

        override fun actionEnd(actionId: Int) {
            trySend(ActionListenerEvent.Finished(actionId))
        }
    }

    registerListener(listener)

    awaitClose {
        unregisterListener(listener)
    }
}

If you want a solution that has more control over when your listener is registered/unregistered, you can also use MutableSharedFlow to expose the events.

class MainProductor{
    val events = MutableSharedFlow<ActionListenerEvent>() // may need to configure replay and buffer depending on your specific use case
    private val listener  = object : ActionListener {
        override fun actionStarted(actionId: Int) {
            events.tryEmit(ActionListenerEvent.Started(actionId))
        }

        override fun actionProgress(actionId: Int, progress: Float, elementCreated: ExampleElement) {
            events.tryEmit(ActionListenerEvent.ProgressChanged(actionId, progress, elementCreated))
        }

        override fun actionEnd(actionId: Int) {
            events.tryEmit(ActionListenerEvent.Finished(actionId))
        }
    }
    
    fun start(){
        registerListener(this.listener)
    }
    
    fun stop(){
        unregisterListener(this.listener)
    }
}

Edit: example usage

Although I am not entirely sure about the expected behavior, especially what to do with the elementCreated: ExampleElement part, I would assume you want to display a list of currently running actions with their respective progress. Aggregating events into such a list might look something like this:

data class RunningAction(val id: Int, val progress: Float)// TODO integrate "elementCreated"

fun collectEvents() = flow {
    val runningActions = mutableMapOf<Int, RunningAction>()
    actionEvents().collect { event ->
        when (event) {
            is ActionListenerEvent.Started -> {
                runningActions[event.actionId] = RunningAction(id = event.actionId, progress = 0f)
            }
            is ActionListenerEvent.ProgressChanged -> {
                val action = runningActions[event.actionId]
                if(action != null){
                    runningActions[event.actionId] = action.copy(progress = event.progress) // TODO integrate "elementCreated"
                }
            }
            is ActionListenerEvent.Finished -> {
                runningActions.remove(event.actionId)
            }
        }
        emit(runningActions.values)
    }
}

CodePudding user response:

 interface ActionListener {
        fun actionStarted(actionId: Int)
        fun actionProgress(actionId: Int, progress: Float, elementCreated: ExampleElement)
        fun actionEnd(actionId: Int)
 }

 class MainProductor(val actionListener: ActionListener) {
        fun start() {
            //start some heavy works that start to call actionListener
        }
 }
  • Related