supabase-community / supabase-kt

A Kotlin Multiplatform Client for Supabase.
https://supabase.com/docs/reference/kotlin/introduction
MIT License
381 stars 37 forks source link

[Bug]: Unable to receive realtime updates on an native Android app #634

Closed dshukertjr closed 3 months ago

dshukertjr commented 3 months ago

General Info

Version(s)

2.4.1

Kotlin Target(s) and their respective versions

Android targetSdk 34

What happened? (include your code)

I am unable to get any realtime data using the realtime feature of this library.

Steps To Reproduce (optional)

  1. Create a Supabase project with some tables with no RLS enabled, but Realtime enabled
  2. Create an Android app on Android Studio
  3. Paste the following code and run the app
  4. Press the button at the middle of the page to start listening
  5. Change some values of the tables with Realtime enabled on the Supabase dashboard
    
    val supabase = createSupabaseClient(
    supabaseUrl = "https://rmjwhnhfotnpbnjfnxka.supabase.co",
    supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJtandobmhmb3RucGJuamZueGthIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTkzNzMzODYsImV4cCI6MjAzNDk0OTM4Nn0.l5C85uPdndHNWDQPXr8OPPBOjqCgnsn2bvhWtiTy328"
    ) {
    install(Realtime)
    }

class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { ExampleTheme { Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { SamplePage() } } } } }

@Composable fun SamplePage() { val composableScope = rememberCoroutineScope()

Column(
    verticalArrangement = Arrangement.Center,
    horizontalAlignment = Alignment.CenterHorizontally
) {
    Button(onClick = {
        composableScope.launch {
            val channel = supabase.channel("channelId") 
            val changeFlow = channel.postgresChangeFlow<PostgresAction>(schema = "public")

            changeFlow.collect {
                when (it) {
                    is PostgresAction.Delete -> println("Deleted: ${it.oldRecord}")
                    is PostgresAction.Insert -> println("Inserted: ${it.record}")
                    is PostgresAction.Select -> println("Selected: ${it.record}")
                    is PostgresAction.Update -> println("Updated: ${it.oldRecord} with ${it.record}")
                }
            }

            channel.subscribe()
        }
    }) {
        Text("press")
    }
}

}


I am using the okhttp version of the ktor client as described in the [trouble shooting guide](https://github.com/supabase-community/supabase-kt/blob/master/TROUBLESHOOTING.MD).
```kts
    implementation(platform("io.github.jan-tennert.supabase:bom:2.4.1"))
    implementation("io.github.jan-tennert.supabase:postgrest-kt")
    implementation("io.github.jan-tennert.supabase:realtime-kt")

    implementation("io.ktor:ktor-client-okhttp:2.3.11")

Relevant log output (optional)

No response

jan-tennert commented 3 months ago

One problem I can observe (and I'm wondering why this isn't mentioned in the reference docs anymore) is that the collect method call suspends the entire coroutine until it's closed. That means the subscribe method never gets called. You could try to collect the flow in another coroutine:

                changeFlow.onEach {
                    when (it) {
                        is PostgresAction.Delete -> println("Deleted: ${it.oldRecord}")
                        is PostgresAction.Insert -> println("Inserted: ${it.record}")
                        is PostgresAction.Select -> println("Selected: ${it.record}")
                        is PostgresAction.Update -> println("Updated: ${it.oldRecord} with ${it.record}")
                    }
                }.launchIn(composableScope) //same as launching a new coroutine and collecting the flow

                channel.subscribe()
dshukertjr commented 3 months ago

Thanks, will try it out tomorrow to see if your code works. Let's definitely add this one in the reference docs.

dshukertjr commented 3 months ago

Does the following code then ever work? Should there always be a launchIn() call on the Flow?

val channel = supabase.channel("channelId") {
    //optional config
}
val changeFlow = channel.postgresChangeFlow<PostgresAction>(schema = "public")

//Collect the flow
changeFlow.collect {
    when(it) {
        is PostgresAction.Delete -> println("Deleted: ${it.oldRecord}")
        is PostgresAction.Insert -> println("Inserted: ${it.record}")
        is PostgresAction.Select -> println("Selected: ${it.record}")
        is PostgresAction.Update -> println("Updated: ${it.oldRecord} with ${it.record}")
    }
}

channel.subscribe()
jan-tennert commented 3 months ago

Well, as the collect method blocks the coroutine, it can't work. So you can either launch a new coroutine and call collect within, or use my shorthand method from above

dshukertjr commented 3 months ago

Good to know. Let's keep in mind that it's generally better if the code samples on the reference docs just work by copying and pasting. Let me fix the code on the reference docs 👍

jan-tennert commented 3 months ago

Alright, thanks!

dshukertjr commented 3 months ago

@jan-tennert Related to this, when working with Supabase realtime, would you ever use .collect on a Flow? It seems like in order to use the .launchIn(), you need to use onEach, so wondering if we should replace all the code samples on using onEach instead of collect.

I'm probably missing something, but I wanted to understand the intent of using collect in the sample code.

jan-tennert commented 3 months ago

Well, launchIn is basically calling collect under the hood, just in a new coroutine. You could also use this code:

flow
    .onEach { }
    .collect()

And it would do the same thing, apart from suspending the current coroutine. For the snippet it's probably best to use the launchIn shortcut, so we can call methods after.

jan-tennert commented 3 months ago

The docs also point to the Kotlin Flow docs, but I agree we should have copy-pastable snippets.

dshukertjr commented 3 months ago

Thanks for the clarification! I can work on the docs then!

dshukertjr commented 3 months ago

And will tag you for review!