I'm feeling kinda lost (might be best to go to sle...
# multiplatform
c
I'm feeling kinda lost (might be best to go to sleep) but I essentially have a compose multiplatform app for ios and Android. In Android, I override the
onResume()
callback in activity and I check if a value exists from the intent. If it does, I want to send it "down" into my compose app. What's the best way to do this? I currently have this working with a MutableStateFlow, but can't I just do this with callbacks somehow? My Android Activity and my compose app are inside the thread. About 15 lines of code
Copy code
class MainActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val state = MutableStateFlow("")
        setContent {
            App(state)
        }
    }

    override fun onResume() {
        if (intent.data.getThing != null){
            state.emit(authCode)
        }
        super.onResume()
    }
My shared App()
Copy code
@Composable
fun App(callback: MutableStateFlow<String>) {
  Column {
    Text("Token was: ${callback.collectAsState().value}")
  }
}
So the above seems to work, but yeah... I should be able to do this without mutablestateflow right?
Anytime I try to implement a callback/lambda, I have the "listener" at the wrong file. i.e.
Copy code
@Composable
fun App(callback: (String)->Unit) {
  // Can only call callback.invoke("blah") here, when what I actually want to do is call callback.invoke("blah") in my activity
  Column {
    Text("Token was: ${callback.collectAsState().value}")
  }
}
FWIW, the reason why i care to do this with a callback vs a mutableStateFlow is because i need to implement the same thing in ios. And I dont believe i can create a mutableStateFlow in swift code.
I tried doing something like this, but no dice
Copy code
class Blah {
    private var callback: ((String) -> Unit)? = null

    fun registerCallback(input: (String) -> Unit) {
        callback = input
    }

    fun callCallback(input: String) {
        callback?.invoke(input)
    }
}
and
Copy code
@Composable
fun App(blah: Blah) {
    var currentToken by remember { mutableStateOf("no token") }

    println("registering callback")
    blah.registerCallback {
        println("got callbacked")
        currentToken = it
    }
but "got callbackd" never gets called
p
Perhaps creating an intermediate layer, that receives a Dispatcher interface from swift and pass down the MutableState. Sort of:
Copy code
class Dispatcher {
    callback: (String) -> Unit

    // subscribe/unsubscribe functions maybe better
}

class AppState(val dispatcher: IntentDispatcher)

@Composable
fun AppCalledFromSwift(dispatcher: Dispatcher) {

    val stateFlow = remember { MutableStateFlow<String>() }

    DisposableEffect {
       dispatcher.callback = { data ->
         stateFlow.update { value = data }
       }
    }.onDispose {
       dispatcher.callback = null
    }

    SharedApp(stateFlow)
}
I would prefer passing down an
AppState
class that contains this interface impl rather than the StateFlow.
c
Hm. That still didn't work.
I changed things up slightly for the sake of testing really quick. Let me know if you see something wrong
Copy code
@Composable
fun App(dispatcher: Dispatcher) {
    var currentToken by remember { mutableStateOf("no token") }

    LaunchedEffect(Unit){
        dispatcher.callback = { data ->
            currentToken = data
        }
    }
... Column with text
then my android MainActivity is
Copy code
class MainActivity : ComponentActivity() {
    val dispatcher = Dispatcher()
  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            App(dispatcher)
        }
with an onResume of dispatcher.callback?.invoke("my token here")
Interesting. If I put a delay in my onResume... then it works
Looks like my onResume is called before my composable is available. I wonder if my activity is being killed?
Okay, so it just seems like my VM is going away somehow when I leave the app and come back.
Phew. so i probably did have a working solution at some point. Thanks @Pablichjenkov! Seems like i just need to understand something a bit better here. I still suppose being able to somehow pass mutable/observable state down from MainActivity or from iOSViewController would still be good instead of using callbacks.
Not sure how I'd do that though. I'm going to re-read your comments tomorrow and something might click. thanks again
👍 1
p
Sounds like onResume is called before you subscribe to the dispatcher. You can do a little trick and make the dispatcher wrap a MutableState. Create a factory function in kotlin that builds it and return the interface. But the implementation internally would have a StateFlow.
Copy code
interface IntentDispatcher {
  fun dispatch(data: String)
  fun subscribe(callback: (String) -> Unit)
  fun unsubscribe(callback: (String) -> Unit)
}

class IntentDispatcherKtImpl : IntentDispatcher {

  private val _stateFlow = MutableStateFlow("")
  private val corouitneScope = CorouitneScope(Disptacher.Default)
  private val listeners = mutableListOf<(String) -> Unit>()
  

  init() {
    corouitneScope.launch {
      _stateFlow.collect { data ->
         listeners.forEach { it.invoke(data) }
      }
    }
  }

  override fun dispatch(data: String) {
    _stateFlow.update { value = data }
  }

  override fun subscribe(callback: (String) -> Unit) {
    listeners.addIfNotExist(callback)
  }

  override fun unsubscribe(callback: (String) -> Unit) {
    listeners.remove(callback)
  }
}
Then in a public
kotlin file
you can expose a
public factory function
that returns the implementation to swift. Binder.kt
Copy code
// Call this one from swift
fun createIntentDispatcher(): IntentDispatcher {
  return IntentDispatcherKtImpl()
}
*Above can be improved in many ways. Is just a quick test through
c
@Pablichjenkov example here is probably along the lines of how I would tackle this. Creating a class in KMP that abstracts away the complexities here and just offers a simple and straight forward API to the clients to send the data when its got it.
2
p
Essentially that. Trying to put more on the kotlin side, less on the platform side.
c
Thanks. Yeah I was just getting frustrated that a simple "callback" wasn't working but it ended up being a lifecycle thing that I just didn't consider. I'll try to refactor this a bit. Seems like I can go the callback route. Passing state down route. Or passing some observable type down.
👍 1