diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts index f571526b632..d155d19b283 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.constants.ts @@ -208,7 +208,7 @@ export const REFERENCES: References = { kotlin: { name: 'Kotlin', library: 'supabase-kt', - versions: ['v1'], + versions: ['v2', 'v1'], icon: '/docs/img/libraries/kotlin-icon.svg', }, cli: { @@ -1624,7 +1624,14 @@ export const reference_swift_v1 = { parent: '/reference', } -export const reference_kotlin_v0 = { +export const reference_kotlin_v1 = { + icon: 'reference-kotlin', + title: 'kotlin', + url: 'guides/reference/kotlin', + parent: '/reference', +} + +export const reference_kotlin_v2 = { icon: 'reference-kotlin', title: 'kotlin', url: 'guides/reference/kotlin', @@ -1754,7 +1761,7 @@ export const references = [ }, { label: 'supabase-kt', - versions: ['v0'], + versions: ['v2', 'v1'], description: 'something about the reference', icon: '/docs/img/icons/kotlin-icon.svg', url: '/reference/kotlin/start', diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index 611da280cfa..4289975b6c6 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -159,12 +159,19 @@ const menus: Menu[] = [ type: 'reference', }, { - id: 'reference_kotlin_v0', - path: '/reference/kotlin', + id: 'reference_kotlin_v1', + path: '/reference/kotlin/v1', commonSectionsFile: 'common-client-libs-sections.json', specFile: 'supabase_kt_v1.yml', type: 'reference', }, + { + id: 'reference_kotlin_v2', + path: '/reference/kotlin', + commonSectionsFile: 'common-client-libs-sections.json', + specFile: 'supabase_kt_v2.yml', + type: 'reference', + }, { id: 'reference_cli', path: '/reference/cli', diff --git a/apps/docs/docs/ref/kotlin/installing.mdx b/apps/docs/docs/ref/kotlin/installing.mdx index 9022359b3f7..e8e7c65b15f 100644 --- a/apps/docs/docs/ref/kotlin/installing.mdx +++ b/apps/docs/docs/ref/kotlin/installing.mdx @@ -51,7 +51,17 @@ custom_edit_url: https://github.com/supabase/supabase/edit/master/web/spec/supab - The available modules are: [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue), [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime), [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage), [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions), [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest), [**apollo-graphql**](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ApolloGraphQL), [**compose-auth**](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuth) and [**compose-auth-ui**](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuthUI) + The available modules are: + - [**gotrue-kt**](https://github.com/supabase-community/supabase-kt/tree/master/GoTrue) + - [**realtime-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Realtime) + - [**storage-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Storage) + - [**functions-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Functions) + - [**postgrest-kt**](https://github.com/supabase-community/supabase-kt/tree/master/Postgrest) + - [apollo-graphql](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ApolloGraphQL) - Creates an [Apollo GraphQL Client](https://github.com/apollographql/apollo-kotlin) for interacting with the Supabase API. + - [compose-auth](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuth) - Provides easy Native Google & Apple Auth for Compose Multiplatform targets. + - [compose-auth-ui](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ComposeAuthUI) - Provides UI Components for Compose Multiplatform. + - [coil-integration](https://github.com/supabase-community/supabase-kt/tree/master/plugins/CoilIntegration) - Provides a [Coil](https://github.com/coil-kt/coil) Integration for displaying images stored in Supabase Storage. + - [imageloader-integration](https://github.com/supabase-community/supabase-kt/tree/master/plugins/ImageLoaderIntegration) - Provides a [Compose ImageLoader](https://github.com/qdsfdhvh/compose-imageloader) Integration for displaying images stored in Supabase Storage. When using multiple modules, you can also use the BOM dependency to ensure that all modules use the same version: @@ -265,7 +275,10 @@ By default, [KotlinX Serialization](https://github.com/Kotlin/kotlinx.serializat ```kotlin val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { - //Already the default serializer + //Already the default serializer, but you can provide a custom Json instance (optional): + defaultSerializer = KotlinXSerializer(Json { + //apply your custom config + }) } ``` diff --git a/apps/docs/docs/ref/kotlin/introduction.mdx b/apps/docs/docs/ref/kotlin/introduction.mdx index 7160b61ce1f..955aa8d9036 100644 --- a/apps/docs/docs/ref/kotlin/introduction.mdx +++ b/apps/docs/docs/ref/kotlin/introduction.mdx @@ -18,6 +18,8 @@ This reference documents every object and method available in Supabase's Kotlin To see supported Kotlin targets, check the corresponding module README on [GitHub](https://github.com/supabase-community/supabase-kt). +To migrate from version 1.4.X to 2.0.0, see the [migration guide](https://github.com/supabase-community/supabase-kt/blob/master/MIGRATION.md) +
diff --git a/apps/docs/internals/files/reference-lib.mjs b/apps/docs/internals/files/reference-lib.mjs index d13bd596726..0b3f40e6010 100644 --- a/apps/docs/internals/files/reference-lib.mjs +++ b/apps/docs/internals/files/reference-lib.mjs @@ -12,7 +12,7 @@ const clientLibFiles = [ { fileName: 'supabase_py_v2', label: 'python', version: 'v2', versionSlug: false }, { fileName: 'supabase_csharp_v0', label: 'csharp', version: 'v0', versionSlug: false }, { fileName: 'supabase_swift_v1', label: 'swift', version: 'v1', versionSlug: false }, - { fileName: 'supabase_kt_v1', label: 'kotlin', version: 'v0', versionSlug: false }, + { fileName: 'supabase_kt_v2', label: 'kotlin', version: 'v2', versionSlug: false }, ] export function generateReferencePages() { diff --git a/apps/docs/layouts/SiteLayout.tsx b/apps/docs/layouts/SiteLayout.tsx index 5b5f3521eae..f6048290264 100644 --- a/apps/docs/layouts/SiteLayout.tsx +++ b/apps/docs/layouts/SiteLayout.tsx @@ -102,9 +102,13 @@ const levelsData = { icon: '/docs/img/icons/menu/reference-swift', name: 'Swift Reference v1.0', }, - reference_kotlin_v0: { + reference_kotlin_v1: { icon: '/docs/img/icons/menu/reference-kotlin', - name: 'Kotlin Reference v0.0', + name: 'Kotlin Reference v1.0', + }, + reference_kotlin_v2: { + icon: '/docs/img/icons/menu/reference-kotlin', + name: 'Kotlin Reference v2.0', }, reference_cli: { icon: '/docs/img/icons/menu/reference-cli', diff --git a/apps/docs/pages/guides/auth/auth-email.mdx b/apps/docs/pages/guides/auth/auth-email.mdx index f6cb45692c9..9b1849f0670 100644 --- a/apps/docs/pages/guides/auth/auth-email.mdx +++ b/apps/docs/pages/guides/auth/auth-email.mdx @@ -65,7 +65,7 @@ To sign up the user, call [signUpWith(Email)](/docs/reference/kotlin/auth-signup ```kotlin suspend fun signUpNewUser() { - supabase.gotrue.signUpWith(Email) { + supabase.auth.signUpWith(Email) { email = "example@email.com" password = "example-password" } @@ -110,7 +110,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun logout() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` @@ -156,11 +156,11 @@ Future signInWithEmail() async { -When your user signs in, call [loginWith(Email)](/docs/reference/kotlin/auth-signinwithpassword) with their email address and password: +When your user signs in, call [signInWith(Email)](/docs/reference/kotlin/auth-signinwithpassword) with their email address and password: ```kotlin suspend fun signInWithEmail() { - supabase.gotrue.loginWith(Email) { + supabase.auth.signInWith(Email) { email = "example@email.com" password = "example-password" } @@ -208,7 +208,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun logout() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/auth-password-reset.mdx b/apps/docs/pages/guides/auth/auth-password-reset.mdx index d36dca1c48f..476c61f2a2f 100644 --- a/apps/docs/pages/guides/auth/auth-password-reset.mdx +++ b/apps/docs/pages/guides/auth/auth-password-reset.mdx @@ -35,10 +35,10 @@ await supabase.auth.resetPasswordForEmail('hello@example.com', { -Supabase provides a convenient method [`.sendRecoveryEmail`](/docs/reference/kotlin/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectUrl` which we will use to pass an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user. +Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/kotlin/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectUrl` which we will use to pass an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user. ```kotlin -supabase.gotrue.sendRecoveryEmail( +supabase.auth.resetPasswordForEmail( email = "hello@example.com", redirectUrl = "http://example.com/account/update-password" ) @@ -76,7 +76,7 @@ await supabase.auth.updateUser({ password: new_password }) To update the password we call the [`.modifyUser`](/docs/reference/kotlin/auth-updateuser) method and pass along the new password to this method. ```kotlin -supabase.gotrue.modifyUser { +supabase.auth.modifyUser { password = "new_password" } ``` @@ -114,10 +114,10 @@ We are using `next` as our query parameter, but this can name whatever you like. -Supabase provides a convenient method [`.sendRecoveryEmail`](/docs/reference/kotlin/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectUrl` which we will use to pass an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user. +Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/kotlin/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectUrl` which we will use to pass an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user. ```kotlin -supabase.gotrue.sendRecoveryEmail( +supabase.auth.resetPasswordForEmail( email = "hello@example.com", redirectUrl = "http://example.com/api/auth/callback?next=/account/update-password" ) @@ -237,7 +237,7 @@ await supabase.auth.updateUser({ password: new_password }) To update the password we call the [`.modifyUser`](/docs/reference/kotlin/auth-updateuser) method and pass along the new password to this method. ```kotlin -supabase.gotrue.modifyUser { +supabase.auth.modifyUser { password = "new_password" } ``` diff --git a/apps/docs/pages/guides/auth/managing-user-data.mdx b/apps/docs/pages/guides/auth/managing-user-data.mdx index d46061f8205..08218011bb3 100644 --- a/apps/docs/pages/guides/auth/managing-user-data.mdx +++ b/apps/docs/pages/guides/auth/managing-user-data.mdx @@ -114,14 +114,14 @@ const { data: profile } = await supabase ```kotlin // This will return nothing while the user is logged out -val data = supabase.postgrest["profiles"].select(Columns.list("id", "username", "avatar_url", "website")) +val data = supabase.from("profiles").select(Columns.list("id", "username", "avatar_url", "website")) // After the user is logged in, this will only return // the logged-in user's data - in this case a single row -supabase.gotrue.sendOtpTo(Email) { +supabase.auth.signInWith(OTP) { this.email = email } -val data = supabase.postgrest["profiles"].select(Columns.list("id", "username", "avatar_url", "website")) +val data = supabase.from("profiles").select(Columns.list("id", "username", "avatar_url", "website")) ``` @@ -169,7 +169,7 @@ const { data, error } = await supabase.auth.signUp({ ```kotlin -val data = supabase.gotrue.signUpWith(Email) { +val data = supabase.auth.signUpWith(Email) { email = "example@email.com" password = "example-password" data = buildJsonObject { @@ -204,9 +204,9 @@ let metadata = user.user_metadata ```kotlin -val user = supabase.gotrue.retrieveUserForCurrentSession() +val user = supabase.auth.retrieveUserForCurrentSession() //Or you can use the user from the current session: -val user = supabase.gotrue.currentUserOrNull() +val user = supabase.auth.currentUserOrNull() val metadata = user?.userMetadata ``` diff --git a/apps/docs/pages/guides/auth/passwordless-login/auth-email-otp.mdx b/apps/docs/pages/guides/auth/passwordless-login/auth-email-otp.mdx index 68afb7ef4d0..1e3478d716f 100644 --- a/apps/docs/pages/guides/auth/passwordless-login/auth-email-otp.mdx +++ b/apps/docs/pages/guides/auth/passwordless-login/auth-email-otp.mdx @@ -59,11 +59,11 @@ Future signInWithEmailOtp() async { -When your user signs in, call [sendOtpTo()](/docs/reference/kotlin/auth-signinwithotp) with their email address: +When your user signs in, call [signInWith(OTP)](/docs/reference/kotlin/auth-signinwithotp) with their email address: ```kotlin suspend fun signInWithEmailOtp() { - supabase.gotrue.sendOtpTo(Email) { + supabase.auth.signInWith(OTP) { email = "example@email.com" } } diff --git a/apps/docs/pages/guides/auth/passwordless-login/auth-magic-link.mdx b/apps/docs/pages/guides/auth/passwordless-login/auth-magic-link.mdx index 1078bc1de2b..9ca6be133c5 100644 --- a/apps/docs/pages/guides/auth/passwordless-login/auth-magic-link.mdx +++ b/apps/docs/pages/guides/auth/passwordless-login/auth-magic-link.mdx @@ -70,7 +70,7 @@ Read the [Deep Linking Documentation](/docs/guides/auth/native-mobile-deep-linki -To start the sign in process call [signIn()](/docs/reference/dart/auth-signinwithotp) with their email address: +When your user signs in, call [signIn()](/docs/reference/dart/auth-signinwithotp) with their email address: ```dart Future signInWithEmail() async { @@ -81,11 +81,11 @@ Future signInWithEmail() async { -To start the sign in process call [sendOtpTo()](/docs/reference/kotlin/auth-signinwithotp) with their email address: +To start the sign in process call [signInWith(OTP)](/docs/reference/kotlin/auth-signinwithotp) with their email address: ```kotlin -suspend fun loginWithEmail() { - supabase.gotrue.sendOtpTo(Email) { +suspend fun signInWithEmail() { + supabase.auth.signInWith(OTP) { email = "example@email.com" } } @@ -128,7 +128,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun logout() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/phone-login/messagebird.mdx b/apps/docs/pages/guides/auth/phone-login/messagebird.mdx index 3d37e28a26b..d88a23d153a 100644 --- a/apps/docs/pages/guides/auth/phone-login/messagebird.mdx +++ b/apps/docs/pages/guides/auth/phone-login/messagebird.mdx @@ -105,8 +105,8 @@ const { ```kotlin -supabase.gotrue.signUpWith(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signUpWith(Phone) { + phone = "+13334445555" password = "some-password" } ``` @@ -156,9 +156,9 @@ const { You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyPhoneOtp`: ```kotlin -supabase.gotrue.verifyPhoneOtp( +supabase.auth.verifyPhoneOtp( type = OtpType.Phone.SMS, - phoneNumber = "+13334445555", + phone = "+13334445555", token = "123456" ) ``` @@ -215,8 +215,8 @@ const { user, error } = await supabase.auth.signInWithPassword({ ```kotlin -supabase.gotrue.loginWith(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signInWith(Phone) { + phone = "+13334445555" password = "some-password" } ``` @@ -261,11 +261,11 @@ const { data, error } = await supabase.auth.signInWithOtp({ -In Kotlin, we can use the `sendOtpTo` method and change the property `phoneNumber` +In Kotlin, we can use the `signInWith(OTP)` method and change the property `phone` ```kotlin -supabase.gotrue.sendOtpTo(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signInWith(OTP) { + phone = "+13334445555" } ``` @@ -309,9 +309,9 @@ const { ```kotlin -supabase.gotrue.verifyPhoneOtp( +supabase.auth.verifyPhoneOtp( type = OtpType.Phone.SMS, - phoneNumber = "+13334445555", + phone = "+13334445555", token = "123456" ) ``` diff --git a/apps/docs/pages/guides/auth/phone-login/twilio.mdx b/apps/docs/pages/guides/auth/phone-login/twilio.mdx index 98b16a07138..0a29cd6bc73 100644 --- a/apps/docs/pages/guides/auth/phone-login/twilio.mdx +++ b/apps/docs/pages/guides/auth/phone-login/twilio.mdx @@ -159,8 +159,8 @@ const { ```kotlin -supabase.gotrue.signUpWith(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signUpWith(Phone) { + phone = "+13334445555" password = "some-password" } ``` @@ -211,9 +211,9 @@ const { You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyPhoneOtp`: ```kotlin -supabase.gotrue.verifyPhoneOtp( +supabase.auth.verifyPhoneOtp( type = OtpType.Phone.SMS, - phoneNumber = "+13334445555", + phone = "+13334445555", token = "123456" ) ``` @@ -270,8 +270,8 @@ const { user, error } = await supabase.auth.signInWithPassword({ ```kotlin -supabase.gotrue.loginWith(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signInWith(Phone) { + phone = "+13334445555" password = "some-password" } ``` @@ -316,11 +316,11 @@ const { data, error } = await supabase.auth.signInWithOtp({ -In Kotlin, we can use the `sendOtpTo` method and change the property `phoneNumber` +In Kotlin, we can use the `signInWith(OTP)` method and change the property `phone` ```kotlin -supabase.gotrue.sendOtpTo(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signInWith(OTP) { + phone = "+13334445555" } ``` @@ -365,9 +365,9 @@ const { ```kotlin -supabase.gotrue.verifyPhoneOtp( +supabase.auth.verifyPhoneOtp( type = OtpType.Phone.SMS, - phoneNumber = "+13334445555", + phone = "+13334445555", token = "123456" ) ``` diff --git a/apps/docs/pages/guides/auth/phone-login/vonage.mdx b/apps/docs/pages/guides/auth/phone-login/vonage.mdx index b45c7b05671..ac260899565 100644 --- a/apps/docs/pages/guides/auth/phone-login/vonage.mdx +++ b/apps/docs/pages/guides/auth/phone-login/vonage.mdx @@ -100,8 +100,8 @@ const { ```kotlin -supabase.gotrue.signUpWith(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signUpWith(Phone) { + phone = "+13334445555" password = "some-password" } ``` @@ -151,9 +151,9 @@ const { You should present a form to the user so they can input the 6 digit pin, then send it along with the phone number to `verifyPhoneOtp`: ```kotlin -supabase.gotrue.verifyPhoneOtp( +supabase.auth.verifyPhoneOtp( type = OtpType.Phone.SMS, - phoneNumber = "+13334445555", + phone = "+13334445555", token = "123456" ) ``` @@ -210,8 +210,8 @@ const { user, error } = await supabase.auth.signInWithPassword({ ```kotlin -supabase.gotrue.loginWith(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signInWith(Phone) { + phone = "+13334445555" password = "some-password" } ``` @@ -256,11 +256,11 @@ const { data, error } = await supabase.auth.signInWithOtp({ -In Kotlin, we can use the `sendOtpTo` method and change the property `phoneNumber` +In Kotlin, we can use the `signInWith(OTP)` method and change the property `phone` ```kotlin -supabase.gotrue.sendOtpTo(Phone) { - phoneNumber = "+13334445555" +supabase.auth.signInWith(OTP) { + phone = "+13334445555" } ``` @@ -304,9 +304,9 @@ const { ```kotlin -supabase.gotrue.verifyPhoneOtp( +supabase.auth.verifyPhoneOtp( type = OtpType.Phone.SMS, - phoneNumber = "+13334445555", + phone = "+13334445555", token = "123456" ) ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-apple.mdx b/apps/docs/pages/guides/auth/social-login/auth-apple.mdx index 22d205f2809..47349e5ead1 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-apple.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-apple.mdx @@ -315,7 +315,7 @@ When developing with Expo, you can test Sign in with Apple via the Expo Go app, ## Using Native Sign in with Apple in Kotlin - When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On iOS it uses Native Apple Login automatically and on other platforms it uses `gotrue.loginWith(Apple)`. + When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On iOS it uses Native Apple Login automatically and on other platforms it uses `gotrue.signInWith(Apple)`. **Initialize the Supabase Client** diff --git a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx index 46b8bb4ae68..67e5ef0abb9 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-azure.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-azure.mdx @@ -140,11 +140,11 @@ async function signInWithAzure() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Azure` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Azure` as the `Provider`: ```kotlin suspend fun signInWithAzure() { - supabase.gotrue.loginWith(Azure) { + supabase.auth.signInWith(Azure) { scopes.add("email") } } @@ -177,7 +177,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` @@ -213,7 +213,7 @@ async function signInWithAzure() { ```kotlin suspend fun signInWithAzure() { - supabase.gotrue.loginWith(Azure) { + supabase.auth.signInWith(Azure) { scopes.add("offline_access") } } diff --git a/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx b/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx index 7b588b2e611..4f4d9515da3 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-bitbucket.mdx @@ -70,11 +70,11 @@ async function signInWithBitbucket() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Bitbucket` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Bitbucket` as the `Provider`: ```kotlin suspend fun signInWithBitbucket() { - supabase.gotrue.loginWith(Bitbucket) + supabase.auth.signInWith(Bitbucket) } ``` @@ -105,7 +105,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-discord.mdx b/apps/docs/pages/guides/auth/social-login/auth-discord.mdx index 1f6048fa84e..bb2ed32426c 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-discord.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-discord.mdx @@ -69,11 +69,11 @@ async function signInWithDiscord() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Discord` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Discord` as the `Provider`: ```kotlin suspend fun signInWithDiscord() { - supabase.gotrue.loginWith(Discord) + supabase.auth.signInWith(Discord) } ``` @@ -106,7 +106,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx index 2e26c47a947..6d120c2305b 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-facebook.mdx @@ -86,11 +86,11 @@ async function signInWithFacebook() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Facebook` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Facebook` as the `Provider`: ```kotlin suspend fun signInWithFacebook() { - supabase.gotrue.loginWith(Facebook) + supabase.auth.signInWith(Facebook) } ``` @@ -121,7 +121,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-figma.mdx b/apps/docs/pages/guides/auth/social-login/auth-figma.mdx index fe9de319ffb..2ae59d16ae5 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-figma.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-figma.mdx @@ -70,11 +70,11 @@ async function signInWithFigma() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Figma` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Figma` as the `Provider`: ```kotlin suspend fun signInWithFigma() { - supabase.gotrue.loginWith(Figma) + supabase.auth.signInWith(Figma) } ``` @@ -105,7 +105,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-github.mdx b/apps/docs/pages/guides/auth/social-login/auth-github.mdx index 140c06abd4a..67d591a917f 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-github.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-github.mdx @@ -81,11 +81,11 @@ async function signInWithGithub() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Github` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Github` as the `Provider`: ```kotlin suspend fun signInWithGithub() { - supabase.gotrue.loginWith(Github) + supabase.auth.signInWith(Github) } ``` @@ -116,7 +116,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx b/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx index d20d8ab1895..839666427bf 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-gitlab.mdx @@ -67,11 +67,11 @@ async function signInWithGitLab() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Gitlab` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Gitlab` as the `Provider`: ```kotlin suspend fun signInWithGitLab() { - supabase.gotrue.loginWith(Gitlab) + supabase.auth.signInWith(Gitlab) } ``` @@ -102,7 +102,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-google.mdx b/apps/docs/pages/guides/auth/social-login/auth-google.mdx index 095de776f87..6eea2b0291e 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-google.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-google.mdx @@ -307,7 +307,7 @@ Before you can use Sign in with Google, you need to obtain a [Google Cloud Platf When the user provides consent, Google issues an identity token (commonly abbreviated as ID token) that is then sent to your project's Supabase Auth server. When valid, a new user session is started by issuing an access and refresh token from Supabase Auth. - When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap automatically and on other platforms it uses `gotrue.loginWith(Google)`. + When using [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform/), you can use the [compose-auth](https://supabase.com/docs/reference/kotlin/installing) plugin. On Android it uses Google OneTap (or Credential Manager on Android 14+) automatically and on other platforms it uses `auth.signInWith(Google)`. Alternatively, you can have a look at [Google One Tap](https://developers.google.com/identity/one-tap/android/overview), just login with the IdToken after you receive it. **Initialize the Supabase Client** diff --git a/apps/docs/pages/guides/auth/social-login/auth-kakao.mdx b/apps/docs/pages/guides/auth/social-login/auth-kakao.mdx index f4fabf24822..81088ca30cd 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-kakao.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-kakao.mdx @@ -92,11 +92,11 @@ async function signInWithKakao() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Kakao` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Kakao` as the `Provider`: ```kotlin suspend fun signInWithKakao() { - supabase.gotrue.loginWith(Kakao) + supabase.auth.signInWith(Kakao) } ``` @@ -127,7 +127,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-keycloak.mdx b/apps/docs/pages/guides/auth/social-login/auth-keycloak.mdx index 4c05ec6f1c7..f65574df644 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-keycloak.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-keycloak.mdx @@ -86,11 +86,11 @@ async function signInWithKeycloak() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Keycloak` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Keycloak` as the `Provider`: ```kotlin suspend fun signInWithKeycloak() { - supabase.gotrue.loginWith(Keycloak) { + supabase.auth.signInWith(Keycloak) { scopes.add("openid") } } @@ -123,7 +123,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx b/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx index 932b5b12296..e90289ec3d5 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-linkedin.mdx @@ -78,11 +78,11 @@ async function signInWithLinkedIn() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `LinkedIn` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `LinkedIn` as the `Provider`: ```kotlin suspend fun signInWithKaLinkedIn() { - supabase.gotrue.loginWith(LinkedIn) + supabase.auth.signInWith(LinkedIn) } ``` @@ -113,7 +113,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-notion.mdx b/apps/docs/pages/guides/auth/social-login/auth-notion.mdx index 94c95bbe19b..e810d6881f7 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-notion.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-notion.mdx @@ -70,11 +70,11 @@ async function signInWithNotion() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Notion` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Notion` as the `Provider`: ```kotlin suspend fun signInWithNotion() { - supabase.gotrue.loginWith(Notion) + supabase.auth.signInWith(Notion) } ``` @@ -105,7 +105,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-slack.mdx b/apps/docs/pages/guides/auth/social-login/auth-slack.mdx index 8af70ee2779..fcb7b2b01aa 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-slack.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-slack.mdx @@ -88,11 +88,11 @@ async function signInWithSlack() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Slack` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Slack` as the `Provider`: ```kotlin suspend fun signInWithSlack() { - supabase.gotrue.loginWith(Slack) + supabase.auth.signInWith(Slack) } ``` @@ -123,7 +123,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx b/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx index 9ffe50ab4f3..bf37db3c82e 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-spotify.mdx @@ -74,11 +74,11 @@ async function signInWithSpotify() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Spotify` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Spotify` as the `Provider`: ```kotlin suspend fun signInWithSpotify() { - supabase.gotrue.loginWith(Spotify) + supabase.auth.signInWith(Spotify) } ``` @@ -109,7 +109,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx b/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx index a5a0daf4d10..80ffddcd638 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-twitch.mdx @@ -85,11 +85,11 @@ async function signInWithTwitch() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Twitch` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Twitch` as the `Provider`: ```kotlin suspend fun signInWithTwitch() { - supabase.gotrue.loginWith(Twitch) + supabase.auth.signInWith(Twitch) } ``` @@ -120,7 +120,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx b/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx index b4a60a54e4e..0536884319f 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-twitter.mdx @@ -76,11 +76,11 @@ async function signInWithTwitter() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Twitter` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Twitter` as the `Provider`: ```kotlin suspend fun signInWithTwitter() { - supabase.gotrue.loginWith(Twitter) + supabase.auth.signInWith(Twitter) } ``` @@ -111,7 +111,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-workos.mdx b/apps/docs/pages/guides/auth/social-login/auth-workos.mdx index 4199c97b2df..b01daa20b53 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-workos.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-workos.mdx @@ -83,11 +83,11 @@ Refer to the [WorkOS Documentation](https://workos.com/docs/reference/sso/author -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `WorkOS` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `WorkOS` as the `Provider`: ```kotlin suspend fun signInWithWorkOS() { - supabase.gotrue.loginWith(WorkOS) { + supabase.auth.signInWith(WorkOS) { queryParams["connection"] = "" queryParams["organization"] = "" queryParams["workos_provider"] = "" @@ -124,7 +124,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx b/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx index 2599249edb4..682af543830 100644 --- a/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx +++ b/apps/docs/pages/guides/auth/social-login/auth-zoom.mdx @@ -83,11 +83,11 @@ async function signInWithZoom() { -When your user signs in, call [loginWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Zoom` as the `Provider`: +When your user signs in, call [signInWith(Provider)](/docs/reference/kotlin/auth-signinwithoauth) with `Zoom` as the `Provider`: ```kotlin suspend fun signInWithZoom() { - supabase.gotrue.loginWith(Zoom) + supabase.auth.signInWith(Zoom) } ``` @@ -118,7 +118,7 @@ When your user signs out, call [logout()](/docs/reference/kotlin/auth-signout) t ```kotlin suspend fun signOut() { - supabase.gotrue.logout() + supabase.auth.signOut() } ``` diff --git a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx index 0ab938d9375..2e0fccce324 100644 --- a/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx +++ b/apps/docs/pages/guides/auth/sso/auth-sso-saml.mdx @@ -208,10 +208,12 @@ Calling [`signInWithSSO`](/docs/reference/javascript/auth-signinwithsso) starts ```kotlin -supabase.gotrue.loginWith(SSO.withDomain("company.com")) +supabase.auth.signInWith(SSO) { + domain = "company.com" +} ``` -Calling [`loginWith(SSO)`](/docs/reference/kotlin/auth-signinwithsso) starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. +Calling [`signInWith(SSO)`](/docs/reference/kotlin/auth-signinwithsso) starts the sign-in process using the identity provider registered for the `company.com` domain name. It is not required that identity providers be assigned one or multiple domain names, in which case you can use the provider's unique ID instead. diff --git a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx index 4b1991f3d12..a972580a7d4 100644 --- a/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx +++ b/apps/docs/pages/guides/getting-started/quickstarts/kotlin.mdx @@ -21,22 +21,17 @@ export const meta = { - ```sql SQL_EDITOR - -- Create products table - create table - public.products ( - _id bigint generated by default as identity not null, - productid text not null, - name text null, - description text null, - price real null, - image text null, - category text null, - nutrition text null, - constraint products_pkey primary key (productid), - ) tablespace pg_default; - - ```` + ```sql SQL_EDITOR + -- Create the table + CREATE TABLE countries ( + id SERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL + ); + -- Insert some sample data into the table + INSERT INTO countries (name) VALUES ('United States'); + INSERT INTO countries (name) VALUES ('Canada'); + INSERT INTO countries (name) VALUES ('Mexico'); + ``` @@ -53,15 +48,28 @@ export const meta = { - - Import Supabase and all required dependencies. Replace the version placeholders `$supabase_version` and `$ktor_version` with the respective latest versions. + + Open `build.gradle.kts` (app) file and add the serialization plug, Ktor client, and Supabase client. + + Replace the version placeholders `$kotlin_version` with the Kotlin version of the project, and `$supabase_version` and `$ktor_version` with the respective latest versions. + + The latest supabase-kt version can be found [here](https://github.com/supabase-community/supabase-kt/releases) and Ktor version can be found [here](https://ktor.io/docs/welcome.html). ```kotlin - implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version" - implementation "io.ktor:ktor-client-android:$ktor_version" + plugins { + ... + kotlin("plugin.serialization") version "$kotlin_version" + } + ... + dependencies { + ... + implementation(platform("io.github.jan-tennert.supabase:bom:$supabase_version")) + implementation("io.github.jan-tennert.supabase:postgrest-kt") + implementation("io.ktor:ktor-client-android:$ktor_version") + } ``` @@ -70,17 +78,15 @@ export const meta = { - - Open the `build.gradle` (app), add the serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same as the Kotlin version in your app. + + Add the following line to the `AndroidManifest.xml` file under the `manifest` tag and outside the `application` tag. - ```kotlin - plugins { - ... - id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlin_version' - ... - } + ```xml + ... + + ... ``` @@ -90,18 +96,25 @@ export const meta = { - You can create a Supabase client whenever you need to perform an API call. That being said, it is recommended to use a dependency injection library like [Hilt](https://developer.android.com/training/dependency-injection/hilt-android#kts). + You can create a Supabase client whenever you need to perform an API call. + + For the sake of simplicity, we will create a client in the `MainActivity.kt` file at the top just below the imports. + + Replace the `supabaseUrl` and `supabaseKey` with your own found in [your dashboard](https://supabase.com/dashboard/project/_/settings/api). ```kotlin - val client = createSupabaseClient( + import ... + + val supabase = createSupabaseClient( supabaseUrl = "https://xyzcompany.supabase.co", - supabaseKey = "public-anon-key" + supabaseKey = "your_public_anon_key" ) { install(Postgrest) } + ... ``` @@ -110,29 +123,18 @@ export const meta = { - + + Create a serializable data class to represent the data from the database. + + Add the following below the `createSupabaseClient` function in the `MainActivity.kt` file. ```kotlin @Serializable - data class ProductDto( - @SerialName("productid") - val productId: String, - @SerialName("name") + data class Country( + val id: Int, val name: String, - @SerialName("description") - val description: String, - @SerialName("price") - val price: Double, - @SerialName("image") - val image: String, - @SerialName("category") - val category: String, - @SerialName("nutrition") - val nutrition: String, - @SerialName("_id") - val _id: Int, ) ``` @@ -142,48 +144,52 @@ export const meta = { - - This kind of object will be consumed by the view. - - - - ```kotlin - data class Product( - val productId: String, - val name: String, - val description: String, - val price: Double, - val image: String, - val category: String, - val nutrition: String, - val _id: Int, - ) - ``` - - - - - - - - Create a repository to interact with the data source. + Use `LaunchedEffect` to fetch data from the database and display it in a `LazyColumn`. + + Replace the default `MainActivity` class with the following code. + + Note that we are making a network request from our UI code. In production, you should probably use a `ViewModel` to separate the UI and data fetching logic. ```kotlin - interface ProductRepository { - fun getProducts(): List + class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + SupabaseTutorialTheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + CountriesList() + } + } + } + } } - class ProductRepositoryImpl @Inject constructor( - private val postgrest: Postgrest, - ) : ProductRepository { - override suspend fun getProducts(): List { - val result = client.postgrest["products"] - .select().decodeList() - // Handle result data for next step - return result + @Composable + fun CountriesList() { + var countries by remember { mutableStateOf>(listOf()) } + LaunchedEffect(Unit) { + withContext(Dispatchers.IO) { + countries = supabase.from("countries") + .select().decodeList() + } + } + LazyColumn { + items( + countries, + key = { country -> country.id }, + ) { country -> + Text( + country.name, + modifier = Modifier.padding(8.dp), + ) + } } } ``` @@ -191,105 +197,9 @@ export const meta = { - - - Use [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) for dependency injection. - - - ```kotlin - InstallIn(SingletonComponent::class) - @Module - abstract class RepositoryModule { - @Binds - abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository - } - ``` - - - - - - - Add the `@Inject` annotation to use the repository in a ViewModel. - - - ```kotlin - class ProductListViewModel @Inject constructor( - private val productRepository: ProductRepository - ) : ViewModel() { - - private val _productList = MutableStateFlow?>(listOf()) - val productList: Flow?> = _productList - - init { - getProducts() - } - - fun getProducts() { - viewModelScope.launch { - val products = productRepository.getProducts() - _productList.emit(products?.map { it -> it.asDomainModel() }) - } - } - - private fun ProductDto.asDomainModel(): Product { - return Product( - productId = this.productId, - name = this.name, - price = this.price, - image = this.image, - description = this.description, - category = this.category, - nutrition = this.nutrition, - _id = this._id - ) - } - ``` - - - - - - - - - ```kotlin - @Composable - fun ProductListScreen( - modifier: Modifier = Modifier, - navController: NavController, - viewModel: ProductListViewModel = hiltViewModel(), - ) { - val productList = viewModel.productList.collectAsState(initial = listOf()).value - if (!productList.isNullOrEmpty()) { - LazyColumn( - modifier = modifier.padding(24.dp), - contentPadding = PaddingValues(5.dp) - ) { - items(productList) { item -> - ProductListItem( - product = item, - modifier = modifier, - onClick = { - navController.navigate( - ProductDetailsDestination.createRouteWithParam( - item.id - ) - ) - }, - ) - } - } - } - } - ``` - - - - - + - + Run the app on an emulator or a physical device by clicking the `Run app` button in Android Studio. diff --git a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx index 06d6242b950..a3260bc2bb3 100644 --- a/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx +++ b/apps/docs/pages/guides/getting-started/tutorials/with-kotlin.mdx @@ -127,11 +127,12 @@ Open the `AndroidManifest.xml` file, update name property of Application tag: ``` -Add annotation for the `MainActivity`: +Create the `MainActivity`: ```kotlin @AndroidEntryPoint class MainActivity : ComponentActivity() { + //This will come later } ``` @@ -152,7 +153,7 @@ object SupabaseModule { supabaseKey = BuildConfig.SUPABASE_ANON_KEY ) { install(Postgrest) - install(GoTrue) { + install(Auth) { flowType = FlowType.PKCE scheme = "app" host = "supabase.com" @@ -169,8 +170,8 @@ object SupabaseModule { @Provides @Singleton - fun provideSupabaseGoTrue(client: SupabaseClient): GoTrue { - return client.gotrue + fun provideSupabaseAuth(client: SupabaseClient): Auth { + return client.auth } @@ -246,7 +247,7 @@ class ProductRepositoryImpl @Inject constructor( name = product.name, price = product.price, ) - postgrest["products"].insert(productDto) + postgrest.from("products").insert(productDto) true } true @@ -257,7 +258,7 @@ class ProductRepositoryImpl @Inject constructor( override suspend fun getProducts(): List? { return withContext(Dispatchers.IO) { - val result = postgrest["products"] + val result = postgrest.from("products") .select().decodeList() result } @@ -266,7 +267,7 @@ class ProductRepositoryImpl @Inject constructor( override suspend fun getProduct(id: String): ProductDto { return withContext(Dispatchers.IO) { - postgrest["products"].select { + postgrest.from("products").select { eq("id", id) }.decodeSingle() } @@ -274,7 +275,7 @@ class ProductRepositoryImpl @Inject constructor( override suspend fun deleteProduct(id: String) { return withContext(Dispatchers.IO) { - postgrest["products"].delete { + postgrest.from("products").delete { eq("id", id) } } @@ -290,12 +291,12 @@ class ProductRepositoryImpl @Inject constructor( withContext(Dispatchers.IO) { if (imageFile.isNotEmpty()) { val imageUrl = - storage["Product%20Image"].upload( + storage.from("Product%20Image").upload( path = "$imageName.png", data = imageFile, upsert = true ) - postgrest["products"].update({ + postgrest.from("products").update({ set("name", name) set("price", price) set("image", buildImageUrl(imageFileName = imageUrl)) @@ -303,7 +304,7 @@ class ProductRepositoryImpl @Inject constructor( eq("id", id) } } else { - postgrest["products"].update({ + postgrest.from("products").update({ set("name", name) set("price", price) }) { @@ -332,11 +333,11 @@ interface AuthenticationRepository { ```kotlin class AuthenticationRepositoryImpl @Inject constructor( - private val goTrue: GoTrue + private val auth: Auth ) : AuthenticationRepository { override suspend fun signIn(email: String, password: String): Boolean { return try { - goTrue.loginWith(Email) { + auth.signInWith(Email) { this.email = email this.password = password } @@ -348,7 +349,7 @@ class AuthenticationRepositoryImpl @Inject constructor( override suspend fun signUp(email: String, password: String): Boolean { return try { - goTrue.signUpWith(Email) { + auth.signUpWith(Email) { this.email = email this.password = password } @@ -360,7 +361,7 @@ class AuthenticationRepositoryImpl @Inject constructor( override suspend fun signInWithGoogle(): Boolean { return try { - goTrue.loginWith(Google) + auth.signInWith(Google) true } catch (e: Exception) { false @@ -371,6 +372,49 @@ class AuthenticationRepositoryImpl @Inject constructor( ### Implement screens +To navigate screens, use the AndroidX navigation library. For routes, implement a `Destination` interface: + +```kotlin + +interface Destination { + val route: String + val title: String +} + + +object ProductListDestination : Destination { + override val route = "product_list" + override val title = "Product List" +} + +object ProductDetailsDestination : Destination { + override val route = "product_details" + override val title = "Product Details" + const val productId = "product_id" + val arguments = listOf(navArgument(name = productId) { + type = NavType.StringType + }) + fun createRouteWithParam(productId: String) = "$route/${productId}" +} + +object AddProductDestination : Destination { + override val route = "add_product" + override val title = "Add Product" +} + +object AuthenticationDestination: Destination { + override val route = "authentication" + override val title = "Authentication" +} + +object SignUpDestination: Destination { + override val route = "signup" + override val title = "Sign Up" +} +``` + +This will help later for navigating between screens. + Create a `ProductListViewModel`: ```kotlin @@ -1029,7 +1073,7 @@ class SignInViewModel @Inject constructor( _password.value = password } - fun onLogin() { + fun onSignIn() { viewModelScope.launch { authenticationRepository.signIn( email = _email.value, @@ -1162,9 +1206,77 @@ fun SignInScreen( } ``` +### Implement the `MainActivity` + +In the `MainActivity` you created earlier, show your newly created screens: + +```kotlin +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + @Inject + lateinit var supabaseClient: SupabaseClient + + @OptIn(ExperimentalMaterial3Api::class) + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + ManageProductsTheme { + // A surface container using the 'background' color from the theme + val navController = rememberNavController() + val currentBackStack by navController.currentBackStackEntryAsState() + val currentDestination = currentBackStack?.destination + Scaffold { innerPadding -> + NavHost( + navController, + startDestination = ProductListDestination.route, + Modifier.padding(innerPadding) + ) { + composable(ProductListDestination.route) { + ProductListScreen( + navController = navController + ) + } + + composable(AuthenticationDestination.route) { + SignInScreen( + navController = navController + ) + } + + composable(SignUpDestination.route) { + SignUpScreen( + navController = navController + ) + } + + composable(AddProductDestination.route) { + AddProductScreen( + navController = navController + ) + } + + composable( + route = "${ProductDetailsDestination.route}/{${ProductDetailsDestination.productId}}", + arguments = ProductDetailsDestination.arguments + ) { navBackStackEntry -> + val productId = + navBackStackEntry.arguments?.getString(ProductDetailsDestination.productId) + ProductDetailsScreen( + productId = productId, + navController = navController, + ) + } + } + } + } + } + } +} +``` + ### Create the success screen -Implement a "Sign In" success screen. Create new "Empty Activity" and in `AndroidManifest.xml`, add a deep link, make sure `scheme` and `host` are the same as the ones set in the GoTrue instance. +To handle OAuth and OTP signins, create a new activity to handle the deeplink you set in `AndroidManifest.xml`: ```xml @@ -1209,6 +1321,8 @@ Implement a "Sign In" success screen. Create new "Empty Activity" and in `Androi ``` +Then create the `DeepLinkHandlerActivity`: + ```kotlin @AndroidEntryPoint class DeepLinkHandlerActivity : ComponentActivity() { diff --git a/apps/docs/pages/guides/realtime/broadcast.mdx b/apps/docs/pages/guides/realtime/broadcast.mdx index f1707ad7249..bc8d8f1fa20 100644 --- a/apps/docs/pages/guides/realtime/broadcast.mdx +++ b/apps/docs/pages/guides/realtime/broadcast.mdx @@ -24,46 +24,46 @@ Go to your Supabase project's [API Settings](https://supabase.com/dashboard/proj defaultActiveId="js" queryGroup="language" > - + -```js -import { createClient } from '@supabase/supabase-js' + ```js + import { createClient } from '@supabase/supabase-js' -const SUPABASE_URL = 'https://.supabase.co' -const SUPABASE_KEY = '' + const SUPABASE_URL = 'https://.supabase.co' + const SUPABASE_KEY = '' -const client = createClient(SUPABASE_URL, SUPABASE_KEY) -``` + const client = createClient(SUPABASE_URL, SUPABASE_KEY) + ``` - - + + -```dart -import 'package:supabase_flutter/supabase_flutter.dart'; + ```dart + import 'package:supabase_flutter/supabase_flutter.dart'; -void main() async { - Supabase.initialize( - url: 'https://.supabase.co', - anonKey: '', - ); - runApp(MyApp()); -} + void main() async { + Supabase.initialize( + url: 'https://.supabase.co', + anonKey: '', + ); + runApp(MyApp()); + } -final supabase = Supabase.instance.client; -``` + final supabase = Supabase.instance.client; + ``` - - + + -```kotlin -val supabaseUrl = "https://.supabase.co" -val supabaseKey = "" -val client = createSupabaseClient(supabaseUrl, supabaseKey) { - install(Realtime) -} -``` + ```kotlin + val supabaseUrl = "https://.supabase.co" + val supabaseKey = "" + val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { + install(Realtime) + } + ``` - + ### Listening to Broadcast messages @@ -77,62 +77,61 @@ You can provide a callback for the `broadcast` channel to receive message. In th defaultActiveId="js" queryGroup="language" > - + -{/* prettier-ignore */} -```js -// Join a room/topic. Can be anything except for 'realtime'. -const channelA = clientA.channel('room-1') + {/* prettier-ignore */} + ```js + // Join a room/topic. Can be anything except for 'realtime'. + const channelA = clientA.channel('room-1') -// Simple function to log any messages we receive -function messageReceived(payload) { - console.log(payload) -} - -// Subscribe to the Channel -channelA - .on( - 'broadcast', - { event: 'test' }, - (payload) => messageReceived(payload) - ) - .subscribe() -``` - - - - -```dart -// Simple function to log any messages we receive -void messageReceived(payload) { - print(payload); -} - -// Subscribe to the Channel -channelA - .onBroadcast( - event: 'test', callback: (payload) => messageReceived(payload)) - .subscribe(); -``` - - - - -```kotlin -val channelA = clientA.realtime.createChannel("room-1") - -//Listen for broadcast messages -val broadcastFlow: Flow = channelA.broadcastFlow("test") - .onEach { - println(it) + // Simple function to log any messages we receive + function messageReceived(payload) { + console.log(payload) } - .launchIn(yourCoroutineScope) //you can also use .collect { } here -clientA.realtime.connect() -channelA.join() -``` + // Subscribe to the Channel + channelA + .on( + 'broadcast', + { event: 'test' }, + (payload) => messageReceived(payload) + ) + .subscribe() + ``` - + + + + ```dart + // Simple function to log any messages we receive + void messageReceived(payload) { + print(payload); + } + + // Subscribe to the Channel + channelA + .onBroadcast( + event: 'test', callback: (payload) => messageReceived(payload)) + .subscribe(); + ``` + + + + + ```kotlin + val channelA = clientA.channel("room-1") + + //Listen for broadcast messages + val broadcastFlow: Flow = channelA.broadcastFlow("test") + .onEach { + println(it) + } + .launchIn(yourCoroutineScope) //you can also use .collect { } here + + channelA.subscribe() + ``` + + ### Sending Broadcast messages @@ -144,70 +143,68 @@ channelA.join() defaultActiveId="js" queryGroup="language" > - + -We can send Broadcast messages using `channelB.send()`. Let's set up another client to send messages. + We can send Broadcast messages using `channelB.send()`. Let's set up another client to send messages. -{/* prettier-ignore */} -```js -// Join a room/topic. Can be anything except for 'realtime'. -const channelB = clientA.channel('room-1') + {/* prettier-ignore */} + ```js + // Join a room/topic. Can be anything except for 'realtime'. + const channelB = clientA.channel('room-1') -channelB.subscribe((status) => { - // Wait for successful connection - if (status !== 'SUBSCRIBED') { - return null - } + channelB.subscribe((status) => { + // Wait for successful connection + if (status !== 'SUBSCRIBED') { + return null + } - // Send a message once the client is subscribed - channelB.send({ - type: 'broadcast', - event: 'test', - payload: { message: 'hello, world' }, - }) -}) -``` + // Send a message once the client is subscribed + channelB.send({ + type: 'broadcast', + event: 'test', + payload: { message: 'hello, world' }, + }) + }) + ``` - - + + -```dart -// Join a room/topic. Can be anything except for 'realtime'. -final channelB = supabase.channel('room-1'); + ```dart + // Join a room/topic. Can be anything except for 'realtime'. + final channelB = supabase.channel('room-1'); -channelB.subscribe((status, error) { - // Wait for successful connection - if (status != RealtimeSubscribeStatus.subscribed) { - return; - } + channelB.subscribe((status, error) { + // Wait for successful connection + if (status != RealtimeSubscribeStatus.subscribed) { + return; + } - // Send a message once the client is subscribed - channelB.sendBroadcastMessage( - event: 'test', - payload: {'message': 'hello, world'}, - ); -}); -``` + // Send a message once the client is subscribed + channelB.sendBroadcastMessage( + event: 'test', + payload: {'message': 'hello, world'}, + ); + }); + ``` - - + + -We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages. + We can send Broadcast messages using `channelB.broadcast()`. Let's set up another client to send messages. -```kotlin -val channelB = clientA.realtime.createChannel("room-1") + ```kotlin + val channelB = clientA.channel("room-1") -clientA.realtime.connect() + channelB.subscribe(blockUntilSubscribed = true) //You can also use the channelA.status flow instead, but this parameter will block the coroutine until the status is joined. -channelB.join(blockUntilJoined = true) //You can also use the channelA.status flow instead, but this parameter will block the coroutine until the status is joined. + channelB.broadcast( + event = "test", + payload = YourMessage(message = "hello, world!") + ) + ``` -channelB.broadcast( - event = "test", - payload = YourMessage(message = "hello, world!") -) -``` - - + Before sending messages we need to ensure the client is connected, which we have done within the `subscribe()` callback. @@ -225,90 +222,89 @@ You can pass configuration options while initializing the Supabase Client. defaultActiveId="js" queryGroup="language" > - + -By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `self` parameter to `true`. + By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `self` parameter to `true`. -{/* prettier-ignore */} -```js -const myChannel = supabase.channel('room-2', { - config: { - broadcast: { self: true }, - }, -}) + {/* prettier-ignore */} + ```js + const myChannel = supabase.channel('room-2', { + config: { + broadcast: { self: true }, + }, + }) -myChannel.on( - 'broadcast', - { event: 'test-my-messages' }, - (payload) => console.log(payload) -) - -myChannel.subscribe((status) => { - if (status !== 'SUBSCRIBED') { return } - channelC.send({ - type: 'broadcast', - event: 'test-my-messages', - payload: { message: 'talking to myself' }, - }) -}) -``` - - - - -```dart -final myChannel = supabase.channel( - 'room-2', - opts: const RealtimeChannelConfig( - self: true, - ), -); - -myChannel.onBroadcast( - event: 'test-my-messages', - callback: (payload) => print(payload), -); - -myChannel.subscribe((status, error) { - if (status != RealtimeSubscribeStatus.subscribed) return; - // channelC.send({ - myChannel.sendBroadcastMessage( - event: 'test-my-messages', - payload: {'message': 'talking to myself'}, - ); -}); -``` - - - - -By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `receiveOwnBroadcasts` parameter to `true`. - -```kotlin -val myChannel = supabase.realtime.createChannel("room-2") { - broadcast { - receiveOwnBroadcasts = true - } -} - -val broadcastFlow: Flow = myChannel.broadcastFlow("test-my-messages") - .onEach { - println(it) - } - .launchIn(yourCoroutineScope) - -clientA.realtime.connect() -myChannel.join(blockUntilJoined = true) //You can also use the myChannel.status flow instead, but this parameter will block the coroutine until the status is joined. - -myChannel.broadcast( - event = "test-my-messages", - payload = YourMessage( - message = "talking to myself" + myChannel.on( + 'broadcast', + { event: 'test-my-messages' }, + (payload) => console.log(payload) ) -) -``` - + myChannel.subscribe((status) => { + if (status !== 'SUBSCRIBED') { return } + channelC.send({ + type: 'broadcast', + event: 'test-my-messages', + payload: { message: 'talking to myself' }, + }) + }) + ``` + + + + + ```dart + final myChannel = supabase.channel( + 'room-2', + opts: const RealtimeChannelConfig( + self: true, + ), + ); + + myChannel.onBroadcast( + event: 'test-my-messages', + callback: (payload) => print(payload), + ); + + myChannel.subscribe((status, error) { + if (status != RealtimeSubscribeStatus.subscribed) return; + // channelC.send({ + myChannel.sendBroadcastMessage( + event: 'test-my-messages', + payload: {'message': 'talking to myself'}, + ); + }); + ``` + + + + + By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `receiveOwnBroadcasts` parameter to `true`. + + ```kotlin + val myChannel = supabase.channel("room-2") { + broadcast { + receiveOwnBroadcasts = true + } + } + + val broadcastFlow: Flow = myChannel.broadcastFlow("test-my-messages") + .onEach { + println(it) + } + .launchIn(yourCoroutineScope) + + myChannel.subscribe(blockUntilSubscribed = true) //You can also use the myChannel.status flow instead, but this parameter will block the coroutine until the status is joined. + + myChannel.broadcast( + event = "test-my-messages", + payload = YourMessage( + message = "talking to myself" + ) + ) + ``` + + ### Acknowledge messages @@ -320,73 +316,72 @@ myChannel.broadcast( defaultActiveId="js" queryGroup="language" > - + -You can confirm that Realtime received your message by setting Broadcast's `ack` config to `true`. + You can confirm that Realtime received your message by setting Broadcast's `ack` config to `true`. -{/* prettier-ignore */} -```js -const myChannel = clientD.channel('room-3', { - config: { - broadcast: { ack: true }, - }, -}) + {/* prettier-ignore */} + ```js + const myChannel = clientD.channel('room-3', { + config: { + broadcast: { ack: true }, + }, + }) -myChannel.subscribe(async (status) => { - if (status !== 'SUBSCRIBED') { return } + myChannel.subscribe(async (status) => { + if (status !== 'SUBSCRIBED') { return } - const serverResponse = await myChannel.send({ - type: 'broadcast', - event: 'acknowledge', - payload: {}, - }) + const serverResponse = await myChannel.send({ + type: 'broadcast', + event: 'acknowledge', + payload: {}, + }) - console.log('serverResponse', serverResponse) -}) -``` + console.log('serverResponse', serverResponse) + }) + ``` - - + + -```dart -final myChannel = supabase.channel('room-3',opts: const RealtimeChannelConfig( - ack: true, -), + ```dart + final myChannel = supabase.channel('room-3',opts: const RealtimeChannelConfig( + ack: true, + ), -); + ); -myChannel.subscribe( (status, error) async { - if (status != RealtimeSubscribeStatus.subscribed) return; + myChannel.subscribe( (status, error) async { + if (status != RealtimeSubscribeStatus.subscribed) return; - final serverResponse = await myChannel.sendBroadcastMessage( + final serverResponse = await myChannel.sendBroadcastMessage( - event: 'acknowledge', - payload: {}, - ); + event: 'acknowledge', + payload: {}, + ); - print('serverResponse: $serverResponse'); -}); -``` + print('serverResponse: $serverResponse'); + }); + ``` - - + + -By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `acknowledgeBroadcasts` parameter to `true`. + By default, broadcast messages are only sent to other clients. You can broadcast messages back to the sender by setting Broadcast's `acknowledgeBroadcasts` parameter to `true`. -```kotlin - val myChannel = supabase.realtime.createChannel("room-2") { - broadcast { - acknowledgeBroadcasts = true - } - } + ```kotlin + val myChannel = supabase.channel("room-2") { + broadcast { + acknowledgeBroadcasts = true + } + } - supabase.realtime.connect() - myChannel.join(blockUntilJoined = true) //You can also use the myChannel.status flow instead, but this parameter will block the coroutine until the status is joined. + myChannel.subscribe(blockUntilSubscribed = true) //You can also use the myChannel.status flow instead, but this parameter will block the coroutine until the status is joined. - myChannel.broadcast(event = "acknowledge", buildJsonObject { }) -``` + myChannel.broadcast(event = "acknowledge", buildJsonObject { }) + ``` - + Use this to guarantee that the server has received the message before resolving `channelD.send`'s promise. If the `ack` config is not set to `true` when creating the channel, the promise returned by `channelD.send` will resolve immediately. @@ -408,42 +403,58 @@ You can also send a Broadcast message by making an HTTP request to Realtime serv defaultActiveId="js" queryGroup="language" > - -```js -const channel = client.channel('test-channel') + + ```js + const channel = client.channel('test-channel') -// No need to subscribe to channel + // No need to subscribe to channel -channel -.send({ -type: 'broadcast', -event: 'test', -payload: { message: 'Hi' }, -}) -.then((resp) => console.log(resp)) + channel + .send({ + type: 'broadcast', + event: 'test', + payload: { message: 'Hi' }, + }) + .then((resp) => console.log(resp)) -// Remember to clean up the channel + // Remember to clean up the channel -client.removeChannel(channel) + client.removeChannel(channel) -```` + ``` - - -```dart -// No need to subscribe to channel + + + ```dart + // No need to subscribe to channel -final channel = supabase.channel('test-channel'); -final res = await channel.sendBroadcastMessage( - event: "test", - payload: { - 'message': 'Hi', - }, -); -print(res); -```` + final channel = supabase.channel('test-channel'); + final res = await channel.sendBroadcastMessage( + event: "test", + payload: { + 'message': 'Hi', + }, + ); + print(res); + ``` - + + + ```kotlin + val myChannel = supabase.channel("room-2") { + broadcast { + acknowledgeBroadcasts = true + } + } + + // No need to subscribe to channel + + myChannel.broadcast(event = "test", buildJsonObject { + put("message", "Hi") + }) + ``` + + export const Page = ({ children }) => diff --git a/apps/docs/pages/guides/realtime/postgres-changes.mdx b/apps/docs/pages/guides/realtime/postgres-changes.mdx index 8d4d11e0dab..f99b82420b6 100644 --- a/apps/docs/pages/guides/realtime/postgres-changes.mdx +++ b/apps/docs/pages/guides/realtime/postgres-changes.mdx @@ -244,7 +244,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("schema-db-changes") +val myChannel = supabase.channel("schema-db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") @@ -258,8 +258,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -313,7 +312,7 @@ final changes = supabase Use `PostgresAction.Insert` as type to listen only to database `INSERT`s: ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") @@ -323,8 +322,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -378,7 +376,7 @@ supabase Use `PostgresAction.Update` as type to listen only to database `UPDATE`s: ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") @@ -388,8 +386,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -443,7 +440,7 @@ supabase Use `PostgresAction.Delete` as type to listen only to database `DELETE`s: ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") @@ -453,8 +450,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -508,7 +504,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "todos" @@ -520,8 +516,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -589,15 +584,14 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val messageChanges = myChannel.postgresChangeFlow(schema = "public") { table = "messages" } val userChanges = myChannel.postgresChangeFlow(schema = "public") { table = "users" } -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -655,7 +649,7 @@ const changes = client ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "todos" @@ -668,8 +662,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -731,7 +724,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "messages" @@ -744,8 +737,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -879,7 +871,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "profiles" @@ -892,8 +884,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -953,7 +944,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "profiles" @@ -966,8 +957,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -1027,7 +1017,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" @@ -1040,8 +1030,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -1101,7 +1090,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" @@ -1114,8 +1103,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -1175,7 +1163,7 @@ supabase ```kotlin -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" @@ -1188,8 +1176,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` @@ -1303,7 +1290,7 @@ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { jwtToken = "your-custom-jwt" } } -val myChannel = supabase.realtime.createChannel("db-changes") +val myChannel = supabase.channel("db-changes") val changes = myChannel.postgresChangeFlow(schema = "public") { table = "products" @@ -1316,8 +1303,7 @@ changes } .launchIn(yourCoroutineScope) -supabase.realtime.connect() -myChannel.join() +myChannel.subscribe() ``` diff --git a/apps/docs/pages/guides/realtime/presence.mdx b/apps/docs/pages/guides/realtime/presence.mdx index dbf8751a0dc..a59bd5afdda 100644 --- a/apps/docs/pages/guides/realtime/presence.mdx +++ b/apps/docs/pages/guides/realtime/presence.mdx @@ -58,7 +58,7 @@ final supabase = Supabase.instance.client; ```kotlin val supabaseUrl = "https://.supabase.co" val supabaseKey = "" -val client = createSupabaseClient(supabaseUrl, supabaseKey) { +val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { install(Realtime) } ``` @@ -120,7 +120,7 @@ roomOne.onPresenceSync((_) { Listen to the presence change flow, emitting new a new `PresenceAction` whenever someone joins or leaves: ```kotlin -val roomOne = supabase.realtime.createChannel("room_01") +val roomOne = supabase.channel("room_01") val presenceFlow: Flow = roomOne.presenceChangeFlow() presenceFlow .onEach { @@ -128,8 +128,8 @@ presenceFlow println(it.leaves) //You can also use it.decodeLeavesAs() } .launchIn(yourCoroutineScope) //You can also use .collect { } here -supabase.realtime.connect() -roomOne.join() + +roomOne.subscribe() ``` @@ -188,15 +188,14 @@ roomOne.subscribe((status, error) async { ```kotlin -val roomOne = supabase.realtime.createChannel("room_01") +val roomOne = supabase.channel("room_01") val userStatus = UserStatus( //Your custom class user = "user-1", onlineAt = Clock.System.now().toEpochMilliseconds() ) -supabase.realtime.connect() -roomOne.join(blockUntilJoined = true) //You can also use the roomOne.status flow instead, but this parameter will block the coroutine until the status is joined. +roomOne.subscribe(blockUntilSubscribed = true) //You can also use the roomOne.status flow instead, but this parameter will block the coroutine until the status is joined. roomOne.track(userStatus) ``` @@ -299,7 +298,7 @@ final channelC = supabase.channel( ```kotlin -val channelC = supabase.realtime.createChannel("test") { +val channelC = supabase.channel("test") { presence { key = "userId-123" } diff --git a/apps/docs/pages/guides/storage/serving/image-transformations.mdx b/apps/docs/pages/guides/storage/serving/image-transformations.mdx index dbdaec500cc..8ad51116cd9 100644 --- a/apps/docs/pages/guides/storage/serving/image-transformations.mdx +++ b/apps/docs/pages/guides/storage/serving/image-transformations.mdx @@ -43,7 +43,7 @@ supabase.storage.from('bucket').getPublicUrl('image.jpg', { ```kotlin -val url = supabase.storage["bucket"].publicRenderUrl("image.jpg") { +val url = supabase.storage.from("bucket").publicRenderUrl("image.jpg") { size(width = 500, height = 600) } ``` @@ -81,7 +81,7 @@ supabase.storage.from('bucket').createSignedUrl('image.jpg', 60000, { ```kotlin -val url = supabase.storage["bucket"].createSignedUrl("image.jpg", 60.seconds) { +val url = supabase.storage.from("bucket").createSignedUrl("image.jpg", 60.seconds) { size(200, 200) } ``` @@ -117,13 +117,13 @@ supabase.storage.from('bucket').download('image.jpg', { ```kotlin -val data = supabase.storage["bucket"].downloadAuthenticated("image.jpg") { +val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") { size(800, 300) } //Or on JVM stream directly to a file val file = File("image.jpg") -supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) { +supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) { size(800, 300) } ``` @@ -170,14 +170,14 @@ await storage.from('bucket').download('image.jpeg', { ```kotlin -val data = supabase.storage["bucket"].downloadAuthenticated("image.jpg") { +val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") { size(200, 200) format = "origin" } //Or on JVM stream directly to a file val file = File("image.jpg") -supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) { +supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) { size(200, 200) format = "origin" } @@ -309,14 +309,14 @@ supabase.storage.from('bucket').download('image.jpg', { ```kotlin -val data = supabase.storage["bucket"].downloadAuthenticated("image.jpg") { +val data = supabase.storage.from("bucket").downloadAuthenticated("image.jpg") { size(800, 300) resize = ImageTransformation.Resize.CONTAIN } //Or on JVM stream directly to a file val file = File("image.jpg") -supabase.storage["bucket"].downloadAuthenticatedTo("image.jpg", file) { +supabase.storage.from("bucket").downloadAuthenticatedTo("image.jpg", file) { size(800, 300) resize = ImageTransformation.Resize.CONTAIN } diff --git a/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx b/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx index 4d875bde02d..f10d6371b2c 100644 --- a/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx +++ b/apps/docs/pages/guides/storage/uploads/resumable-uploads.mdx @@ -88,7 +88,7 @@ Supabase Storage implements the [TUS protocol](https://tus.io/) to enable resuma ```kotlin suspend fun uploadFile(file: File) { - val upload: ResumableUpload = supabase.storage["bucket_name"] + val upload: ResumableUpload = supabase.storage.from("bucket_name") .resumable.createOrContinueUpload("file_path", file) upload.stateFlow .onEach { @@ -100,7 +100,7 @@ Supabase Storage implements the [TUS protocol](https://tus.io/) to enable resuma // On other platforms you might have to give the bytes directly and specify a source if you want to continue it later: suspend fun uploadData(bytes: ByteArray) { - val upload: ResumableUpload = supabase.storage["bucket_name"] + val upload: ResumableUpload = supabase.storage.from("bucket_name") .resumable.createOrContinueUpload(bytes, "source", "file_path") upload.stateFlow diff --git a/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx b/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx index 64061f54891..dbafac6e7cd 100644 --- a/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx +++ b/apps/docs/pages/guides/storage/uploads/standard-uploads.mdx @@ -55,12 +55,12 @@ val supabase = createSupabaseClient(supabaseUrl, supabaseKey) { } suspend fun uploadData(bytes: ByteArray) { - supabase.storage["bucket_name"].upload("file_path", bytes) + supabase.storage.from("bucket_name").upload("file_path", bytes) } //Or on JVM/Android: (This will stream the data from the file to supabase) suspend fun uploadFile(file: File) { - supabase.storage["bucket_name"].upload("file_path", file) + supabase.storage.from("bucket_name").upload("file_path", file) } ``` diff --git a/apps/docs/pages/reference/kotlin/[...slug].tsx b/apps/docs/pages/reference/kotlin/[...slug].tsx index 96e0008668f..26678b12787 100644 --- a/apps/docs/pages/reference/kotlin/[...slug].tsx +++ b/apps/docs/pages/reference/kotlin/[...slug].tsx @@ -1,5 +1,5 @@ import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' -import spec from '~/../../spec/supabase_kt_v1.yml' assert { type: 'yml' } +import spec from '~/../../spec/supabase_kt_v2.yml' assert { type: 'yml' } import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' diff --git a/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx b/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx index 412c43cc990..0d76f33cf09 100644 --- a/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/kotlin/crawlers/[...slug].tsx @@ -1,6 +1,6 @@ import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json' import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json' -import spec from '~/../../spec/supabase_kt_v1.yml' assert { type: 'yml' } +import spec from '~/../../spec/supabase_kt_v2.yml' assert { type: 'yml' } import RefSectionHandler from '~/components/reference/RefSectionHandler' import { flattenSections } from '~/lib/helpers' import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' @@ -14,7 +14,7 @@ const libraryPath = '/kotlin' export default function KotlinReference(props) { const router = useRouter() const slug = router.query.slug[0] - const filteredSection = sections.filter((section) => section.id === slug) + const filteredSection = sections.filter((section) => section.slug === slug) const pageTitle = filteredSection[0]?.title ? `${filteredSection[0]?.title} | Supabase` diff --git a/apps/docs/pages/reference/kotlin/v0/[...slug].tsx b/apps/docs/pages/reference/kotlin/v1/[...slug].tsx similarity index 79% rename from apps/docs/pages/reference/kotlin/v0/[...slug].tsx rename to apps/docs/pages/reference/kotlin/v1/[...slug].tsx index 5c39eba69bf..f72ca756c27 100644 --- a/apps/docs/pages/reference/kotlin/v0/[...slug].tsx +++ b/apps/docs/pages/reference/kotlin/v1/[...slug].tsx @@ -6,10 +6,18 @@ import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths' import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps' const sections = flattenSections(clientLibsCommonSections) -const libraryPath = '/kotlin/v0' +const libraryPath = '/kotlin/v1' export default function KotlinReference(props) { - return + return ( + + ) } export async function getStaticProps() { diff --git a/apps/docs/pages/reference/kotlin/v0/crawlers/[...slug].tsx b/apps/docs/pages/reference/kotlin/v1/crawlers/[...slug].tsx similarity index 97% rename from apps/docs/pages/reference/kotlin/v0/crawlers/[...slug].tsx rename to apps/docs/pages/reference/kotlin/v1/crawlers/[...slug].tsx index 7aa638d5fe2..115cba80b87 100644 --- a/apps/docs/pages/reference/kotlin/v0/crawlers/[...slug].tsx +++ b/apps/docs/pages/reference/kotlin/v1/crawlers/[...slug].tsx @@ -9,7 +9,7 @@ import { useRouter } from 'next/router' import RefSEO from '~/components/reference/RefSEO' const sections = flattenSections(clientLibsCommonSections) -const libraryPath = '/kotlin/v0' +const libraryPath = '/kotlin/v1' export default function KotlinReference(props) { const router = useRouter() diff --git a/spec/common-client-libs-sections.json b/spec/common-client-libs-sections.json index 930ef1e9fad..1a050538068 100644 --- a/spec/common-client-libs-sections.json +++ b/spec/common-client-libs-sections.json @@ -33,7 +33,8 @@ "reference_python_v2", "reference_csharp_v0", "reference_swift_v1", - "reference_kotlin_v0" + "reference_kotlin_v1", + "reference_kotlin_v2" ] }, { @@ -47,7 +48,8 @@ "reference_python_v2", "reference_csharp_v0", "reference_swift_v1", - "reference_kotlin_v0" + "reference_kotlin_v1", + "reference_kotlin_v2" ] }, { @@ -963,7 +965,8 @@ "reference_python_v2", "reference_csharp_v0", "reference_swift_v1", - "reference_kotlin_v0" + "reference_kotlin_v1", + "reference_kotlin_v2" ], "items": [ { diff --git a/spec/supabase_kt_v1.yml b/spec/supabase_kt_v1.yml index 331ffeadce0..352ed41cccc 100644 --- a/spec/supabase_kt_v1.yml +++ b/spec/supabase_kt_v1.yml @@ -2098,15 +2098,15 @@ functions: Creates a new user. - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://supabase.com/dashboard/project/_/auth/providers). - **Confirm email** determines if users need to confirm their email address after signing up. - - If **Confirm email** is enabled, the return value is null and you will be logged in instead. - - If **Confirm email** is disabled, the return value is the user and you won't be logged in automatically. + - If **Confirm email** is enabled, the return value is the user and you won't be logged in automatically. + - If **Confirm email** is disabled, the return value is null and you will be logged in instead. - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://supabase.com/dashboard/project/_/auth/url-configuration). - To learn how to handle OTP links & OAuth refer to [initializing](/docs/reference/kotlin/initializing) - If signUpWith() is called for an existing confirmed user: - If **Confirm email** is enabled in [your project](https://supabase.com/dashboard/project/_/auth/providers), an obfuscated/fake user object is returned. - If **Confirm email** is disabled, the error message, `User already registered` is returned. examples: - - id: sign-up-phone + - id: sign-up-email name: Sign up with email isSpotlight: true code: | diff --git a/spec/supabase_kt_v2.yml b/spec/supabase_kt_v2.yml new file mode 100644 index 00000000000..bb1cbbbe30a --- /dev/null +++ b/spec/supabase_kt_v2.yml @@ -0,0 +1,4091 @@ +openref: 0.1 + +info: + id: reference/supabase-kotlin + title: Supabase Kotlin Client + description: | + + Supabase Kotlin. + + specUrl: https://github.com/supabase/supabase/edit/master/spec/supabase_kt_v2.yml + slugPrefix: '/' + libraries: + - name: 'Kotlin' + id: 'kt' + version: '0.0.1' + +functions: + - id: initializing + title: 'Initializing' + description: | + ### Create Supabase Client + + Independently of which Supabase module you are using, you will need to initialize the main client first and install the module. + + To create a new client, you can use the `createSupabaseClient` function. + + When installing a module, you can pass a block to configure it. + + ### OAuth and OTP link verification + + [supabase-kt](https://github.com/supabase-community/supabase-kt) provides several platform implementations for OAuth and OTP link verification. \ + **On JVM**, it uses a HTTP Callback Server to receive the session data from a successful OAuth login. + + *Note: OTP link verification such as sign ups are not supported on JVM. You may have to send a verification token rather than a url in your email. To send the token, rather than a redirect url, change `{{ .ConfirmationURL }}` in your sign up email to `{{ .Token }}`* + + **On Android, iOS & MacOS**, OAuth and OTP verification use deeplinks. Refer to the guide below on how to setup deeplinks. Alternatively you can use Native Google Auth or a WebView for OAuth. Refer to the [demo](https://github.com/supabase-community/supabase-kt/tree/master/demos/android-login) to learn more. + **On JS**, it uses the website origin as the callback url. Session importing gets handled automatically. + **Windows, tvOS, watchOS & Linux** currently have no default implementation. Feel free to create a PR. + + You always make your own implementation and use `auth.parseSessionFromFragment(fragment)` or `auth.parseSessionFromUrl(url)` to let [supabase-kt](https://github.com/supabase-community/supabase-kt) handle the parsing after receiving a callback. + Then you can simply use `auth.importSession(session)`. + + ### Configure deeplink callbacks for Authentication + + Deeplinks are supported on Android, iOS and MacOS. + 1. **Set up a deeplink** \ + On Android, set up a [deeplink](https://developer.android.com/training/app-links/deep-linking) in your Android manifest. \ + On iOS and MacOS, set up a [url scheme](https://developer.apple.com/documentation/xcode/defining-a-custom-url-scheme-for-your-app). + 2. **Add your deeplink to the [redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration)** \ + **Pattern**: scheme://host + 3. **Configure the Auth plugin** + Set the `host` and `scheme` in the Auth config: + ```kotlin + install(Auth) { + host = "deeplink host" // this can be anything, eg. your package name or app/company url (not your Supabase url) + scheme = "deeplink scheme" + + // On Android only, you can set OAuth and SSO logins to open in a custom tab, rather than an external browser: + defaultExternalAuthAction = ExternalAuthAction.CUSTOM_TABS //defaults to EXTERNAL_BROWSER + } + ``` + 4. **Call platform specific function on startup** \ + On Android: `supabase.handleDeeplinks(intent)` \ + On iOS/MacOS: `supabase.handleDeeplinks(url)` + + Then you can log in using OAuth: + ```kotlin + supabase.auth.signInWith(Google) + ``` + Or open OTP links directly in your app. + + ### PKCE Authentication flow + supabase-kt supports the [PKCE authentication flow](https://supabase.com/blog/supabase-auth-sso-pkce). + To use it, change the `flowType` in the Auth configuration: + ```kotlin + install(Auth) { + flowType = FlowType.PKCE + } + ``` + That's it! If you already implemented deeplinks to handle OTPs and OAuth you don't have to change anything! + examples: + - id: initialize-client + name: Initialize Client + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(Auth) + install(Postgrest) + //install other modules + } + ``` + - id: configure-auth + name: Configure Auth module + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(Auth) { + alwaysAutoRefresh = false // default: true + autoLoadFromStorage = false // default: true + //and more... + } + } + ``` + description: | + **Common:** + + `alwaysAutoRefresh` - whether the Auth plugin should always auto refresh expired sessions automatically. Default: `true` + + `autoLoadFromStorage` - whether the Auth plugin should automatically load the session from the session manager. Default: `true` + + `autoSaveToStorage` - whether the Auth plugin should automatically save the session to the session manager. Default: `true` + + `flowType` - Which authentication flow to use. Currently available: FlowType.PKCE and FlowType.IMPLICIT. Default: `FlowType.IMPLICIT` + + `codeVerifierCache` - Interface for saving and loading codes for the PKCE authentication flow. Default: `SettingsCodeVerifierCache` + + `customUrl` - Custom url for the Auth API. Can be safely ignored when using Supabase. Default: `null` + + `jwtToken` - Plugin specific JWT Token. Can be ignored when using the Auth plugin. Default: `null` + + `retryDelay` - Duration after which the Auth plugin should retry a failed session refresh. Default: `10.seconds` + + `sessionManager` - Interface for saving and loading the user session. Default: `SettingsSessionManager` + + **Android & iOS:** + + `scheme` - The scheme for the redirect url, when using deep linking. Default: `supabase` + + `host` - The host for the redirect url, when using deep linking. Default: `login` + + **Android:** + + `enableLifecycleCallbacks` - Whether to stop auto-refresh on focus loss, and resume it on focus again. Default: `true` + + **Desktop:** + + `httpPort`: The port the web server is running on, when logging in with OAuth. Default: `0` (random port). + + `timeout`: The timeout for the web server, when logging in with OAuth. Default: `1.minutes`. + + `htmlTitle`: The title of the redirect page, when logging in with OAuth. Default: `"Supabase Auth"`. + + `htmlText`: The text of the redirect page, when logging in with OAuth. Default: `"Logged in. You may continue in your app."`. + + `htmlIconUrl`: The icon of the redirect page, when logging in with OAuth. Default: `"https://supabase.com/brand-assets/supabase-logo-icon.png"`. + - id: configure-postgrest + name: Configure PostgREST module + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(Postgrest) { + defaultSchema = "schema" // default: "public" + propertyConversionMethod = PropertyConversionMethod.SERIAL_NAME // default: PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE + } + } + ``` + description: | + `propertyConversionMethod` - The method to use to convert the property names to the column names when applying filters and using the update method. Default: `PropertyConversionMethod.CAMEL_CASE_TO_SNAKE_CASE` + + `defaultSchema` - The default schema to use for database requests. Default: `public` + + `customUrl` - Custom url for the PostgREST API. Can be safely ignored when using Supabase. Default: `null` + + `jwtToken` - Plugin specific JWT Token. Can be ignored when using the Auth plugin. Default: `null` + - id: configure-storage + name: Configure Storage module + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(Storage) { + transferTimeout = 90.seconds // Default: 120 seconds + } + } + ``` + description: | + `transferTimeout` - the timeout for uploading and downloading files. Default: `120.seconds` + + `resumable.cache` - Interface for storing resumable upload urls. Default: `SettingsResumableCache` + + `resumable.defaultChunkSize` - The default chunk size for resumable uploads. Supabase currently only supports a chunk size of 6MB, so be careful when changing this value. Default: `6MB` + + `resumable.retryTimeout` - the timeout for retrying resumable uploads when uploading a chunk fails. Default: `5.seconds` + + `resumable.onlyUpdateStateAfterChunk` - whether the upload state should only be updated after a chunk was uploaded successfully or also when the chunk is currently being uploaded. Default: `false` + + `customUrl` - Custom url for the Storage API. Can be safely ignored when using Supabase. Default: `null` + + `jwtToken` - Plugin specific JWT Token. Can be ignored when using the Auth plugin. Default: `null` + - id: configure-realtime + name: Configure Realtime module + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(Realtime) { + reconnectDelay = 5.seconds // Default: 7 seconds + } + } + ``` + description: | + `reconnectDelay` - The delay between reconnect attempts. Default: `7.seconds` + + `heartbeatInterval` - The interval between heartbeat messages. Default: `15.seconds` + + `disconnectOnSessionLoss` - Whether to disconnect from the websocket when the session is lost. Default: `true` + + `secure` - Whether to use wss or ws. Defaults to [SupabaseClient.useHTTPS] when null + + `websocketConfig` - Custom Ktor websocket config + + `customUrl` - Custom url for the Realtime websocket. Can be safely ignored when using Supabase. Default: `null` + + `jwtToken` - Plugin specific JWT Token. Can be ignored when using the Auth plugin. Default: `null` + - id: configure-functions + name: Configure Functions plugin + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(Functions) { + //no custom settings + } + } + ``` + description: | + `customUrl` - Custom url for the Functions API. Can be safely ignored when using Supabase. Default: `null` + + `jwtToken` - Plugin specific JWT Token. Can be ignored when using the Auth plugin. Default: `null` + - id: configure-graphql + name: Configure GraphQL plugin + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://xyzcompany.supabase.co", + supabaseKey = "public-anon-key" + ) { + install(GraphQL) { + apolloConfiguration { + //custom configuration + } + } + } + ``` + description: | + `apolloConfiguration` - Custom configuration for the ApolloClient + + `customUrl` - Custom url for the GraphQL API. Can be safely ignored when using Supabase. Default: `null` + + `jwtToken` - Plugin specific JWT Token. Can be ignored when using the Auth plugin. Default: `null` + + **You can access the created ApolloClient via `supabase.graphql.apolloClient`, which automatically adds the required headers depending on your session.** + + - id: select + title: 'Fetch data: select()' + notes: | + Perform a SELECT query on the table or view. + - When calling a `decode` method, you have to provide a [serializable class](/docs/reference/kotlin/installing#serialization) as the type parameter. + - You can provide a `Columns` object to select specific columns. + - You can provide a [filter](/docs/reference/kotlin/using-filters) block to filter the results + examples: + - id: getting-your-data + name: Getting your data + isSpotlight: true + code: | + ```kotlin + val city = supabase.from("cities").select().decodeSingle() + ``` + - id: selecting-specific-columns + name: Selecting specific columns + description: You can select specific fields from your tables. + code: | + ```kotlin + val city = supabase.from("cities").select(columns = Columns.list("id, name")).decodeSingle() + ``` + - id: query-foreign-tables + name: Query foreign tables + description: If your database has foreign key relationships, you can query related tables too. + code: | + ```kotlin + val columns = Columns.raw(""" + id, + name, + cities ( + id, + name + ) + """.trimIndent()) + val country = supabase.from("countries") + .select( + columns = columns + ) + .decodeSingle() + ``` + note: | + What about join tables + If your tables are **NOT** directly related, but instead are joined by a _join table_, + you can still use the `select()` method to query the related data. The PostgREST engine detects the relationship automatically. + For more details, [follow the link](https://postgrest.org/en/latest/api.html#embedding-through-join-tables). + - id: query-the-same-foreign-table-multiple-times + name: Query the same foreign table multiple times + description: | + Sometimes you will need to query the same foreign table twice. + In this case, you can use the name of the joined column to identify + which join you intend to use. For convenience, you can also give an + alias for each column. For example, if we had a shop of products, + and we wanted to get the supplier and the purchaser at the same time + (both in the users) table + code: | + ```kotlin + val columns = Columns.raw(""" + content, + from: sender_id(name), + to: receiver_id(name) + """.trimIndent()) + val message = supabase.from("messages") + .select(columns = columns) + .decodeSingle() + ``` + - id: querying-with-count-option + name: Querying with count option + description: | + You can get the number of rows by using the count option. + Allowed values for count option are [Count.EXACT](https://postgrest.org/en/stable/api.html#exact-count), [Count.PLANNED](https://postgrest.org/en/stable/api.html#planned-count) and [Count.ESTIMATED](https://postgrest.org/en/stable/api.html#estimated-count). + code: | + ```kotlin + val count = supabase.from("countries") + .select(head = true) { + count(Count.EXACT) + } + .count()!! + ``` + - id: querying-json-data + name: Querying JSON data + description: | + If you have data inside of a JSONB column, you can apply select + and query filters to the data values. Postgres offers a + [number of operators](https://www.postgresql.org/docs/current/functions-json.html) + for querying JSON data. Also see + [PostgREST docs](http://postgrest.org/en/v7.0.0/api.html#json-columns) for more details. + code: | + ```kotlin + val columns = Columns.raw(""" + id, name + address->city + """.trimIndent()) + val user = supabase.from("users") + .select(columns = columns) + .decodeSingle() + ``` + + - id: insert + title: 'Create data: insert()' + $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.insert' + notes: | + - When calling an `insert` method, you have to provide a [serializable value](/docs/reference/kotlin/installing#serialization). + - By default, every time you run `insert()`, the client library will make a `select` to return the full record. + This is convenient, but it can also cause problems if your policies are not configured to allow the `select` operation. + examples: + - id: create-a-record + name: Create a record + isSpotlight: true + code: | + ```kotlin + val city = City(name = "The Shire", countryId = 554) + supabase.from("cities").insert(city) + ``` + - id: create-a-record-and-return + name: Create a record and return it + isSpotlight: true + code: | + ```kotlin + val city = City(name = "The Shire", countryId = 554) + val result = supabase.from("cities").insert(city) { + select() + }.decodeSingle() + ``` + - id: bulk-create + name: Bulk create + description: | + When running a bulk create, the operation is handled in a single transaction. If any of the inserts fail, all other operations are + rolled back. + code: | + ```kotlin + val theShire = City(name = "The Shire", countryId = 554) + val rohan = City(name = "Rohan", countryId = 554) + supabase.from("cities").insert(listOf(theShire, rohan)) + ``` + + - id: update + title: 'Modify data: update()' + notes: | + - `update()` should always be combined with a [filter](/docs/reference/kotlin/using-filters) block to avoid updating all records. + - When calling `insert` or `update`, you have to provide a [serializable value](/docs/reference/kotlin/installing#serialization) in the function parameter. + examples: + - id: updating-your-data + name: Updating your data + isSpotlight: true + code: | + ```kotlin + supabase.from("countries").update( + { + Country::name setTo "Australia" + //or + set("name", "Australia") + } + ) { + filter { + Country::id eq 1 + //or + eq("id", 1) + } + } + ``` + - id: update-a-record-and-return-it + name: Update a record and return it + code: | + ```kotlin + val newCountry = supabase.from("countries").update( + { + Country::name setTo "Australia" + //or + set("name", "Australia") + } + ) { + select() + filter { + Country::id eq 1 + //or + eq("id", 1) + } + }.decodeSingle() + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Australia'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "name": "Australia" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + - id: updating-json-data + name: Updating JSON data + description: | + Postgres offers a + [number of operators](https://www.postgresql.org/docs/current/functions-json.html) + for working with JSON data. Right now it is only possible to update an entire JSON document, + but we are [working on ideas](https://github.com/PostgREST/postgrest/issues/465) for updating individual keys. + code: | + ```kotlin + val address = Address(street = "Melrose Place", postcode = 90210) + supabase.from("users").update( + { + User::address setTo address + } + ) { + filter { + eq("address->postcode", 90210) + } + } + ``` + + - id: upsert + title: 'Upsert data: upsert()' + $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.upsert' + notes: | + - Primary keys should be included in the data payload in order for an update to work correctly. + - Primary keys must be natural, not surrogate. There are however, [workarounds](https://github.com/PostgREST/postgrest/issues/1118) for surrogate primary keys. + - If you need to insert new data and update existing data at the same time, use [Postgres triggers](https://github.com/supabase/postgrest-js/issues/173#issuecomment-825124550). + - When calling `insert` or `update`, you have to provide a [serializable value](/docs/reference/kotlin/installing#serialization) in the function parameter. + examples: + - id: upsert-your-data + name: Upsert your data + isSpotlight: true + code: | + ```kotlin + val toUpsert = Message(id = 3, message = "foo", username = "supabot") + supabase.from("messages").upsert(toUpsert) + ``` + - id: upserting-into-tables-with-constraints + name: Upserting into tables with constraints + description: | + Running the following will cause Supabase to upsert data into the `users` table. + If the username 'supabot' already exists, the `onConflict` argument tells Supabase to overwrite that row + based on the column passed into `onConflict`. + isSpotlight: true + code: | + ```kotlin + let toUpsert = User(username = "supabot") + supabase.from("users").upsert(toUpsert, onConflict = "username") + ``` + - id: upsert-return-row-count + name: Return the exact number of rows + isSpotlight: true + code: | + ```kotlin + let toUpsert = User(username = "supabot") + val count = supabase.from("users").upsert(toUpsert, onConflict = "username") { + count(Count.EXACT) + }.count() + ``` + + - id: delete + title: 'Delete data: delete()' + $ref: '@supabase/postgrest-js."lib/PostgrestQueryBuilder".PostgrestQueryBuilder.delete' + notes: | + - `delete()` should always be combined with a [filter](/docs/reference/kotlin/using-filters) block to target the item(s) you wish to delete. + - If you use `delete()` with filters and you have + [RLS](/docs/learn/auth-deep-dive/auth-row-level-security) enabled, only + rows visible through `SELECT` policies are deleted. Note that by default + no rows are visible, so you need at least one `SELECT`/`ALL` policy that + makes the rows visible. + examples: + - id: delete-records + name: Delete records + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::id eq 666 + //or + eq("id", 666) + } + } + ``` + - id: fetch-delete-records + name: Fetch deleted records + code: | + ```kotlin + val deletedCity = supabase.from("cities").delete { + select() + filter { + City::id eq 666 + //or + eq("id", 666) + } + }.decodeSingle() + ``` + - id: rpc + title: 'Stored Procedures: rpc()' + description: | + You can call stored procedures as a "Remote Procedure Call". + + That's a fancy way of saying that you can put some logic into your database then call it from anywhere. + It's especially useful when the logic rarely changes - like password resets and updates. + + - When calling `rpc` with parameters, you have to provide a [serializable value](/docs/reference/kotlin/installing#serialization) in the function parameter. + examples: + - id: call-a-stored-procedure + name: Call a stored procedure + isSpotlight: true + description: This is an example invoking a stored procedure. + code: | + ```kotlin + supabase.postgrest.rpc("hello_world") + ``` + - id: with-parameters + name: With Parameters + code: | + ```kotlin + val rpcParams = City(name = "The Shire") + supabase.postgrest.rpc("echo_city", rpcParams) + ``` + - id: using-filters + title: Using Filters + description: | + Filters allow you to only return rows that match certain conditions. + + Filters can be used on `select()`, `update()`, and `delete()` queries. + + You can use two different types for applying filters: + ```kotlin + eq("country_id", 1) + ``` + And using a class property: + ```kotlin + City::countryId eq 1 + ``` + + As you can see on the property syntax: + the name of the `countryId` gets converted to `country_id`. + + By default, this is done by converting camel case to snake case, but you can customize this by changing the `propertyConversionMethod` in the Postgrest Config + + If a database function returns a table response, you can also apply filters. + examples: + - id: applying-filters + name: Applying a filter block + description: | + Filters can be applied on any of these functions: `select()`, `update()`, `upsert()`, + `delete()`, and `rpc()` + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name", "country_id")) { + filter { + City::name eq "The Shire" + //or + eq("name", "The Shire") + } + } + ``` + - id: multiple-filters + name: Multiple filters on one column + description: | + Filters can be applied on any of these functions: `select()`, `update()`, `upsert()`, + `delete()`, and `rpc()` + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name, country_id")) { + filter { + and { //when both are true + City::population gt 40000 + City::population lt 700000 + } + or { //when either one of the filters are true + City::name eq "London" + City::name eq "Berlin" + } + } + } + ``` + - id: filter-by-value-within-json-column + name: Filter by values within a JSON column + description: | + Filters can be built up one step at a time and then executed. For example: + data: + sql: | + ```sql + create table + users ( + id int8 primary key, + name text, + address jsonb + ); + + insert into + users (id, name, address) + values + (1, 'Michael', '{ "postcode": 90210 }'), + (2, 'Jane', null); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "name": "Michael", + "address": { + "postcode": 90210 + } + } + ], + "status": 200, + "statusText": "OK" + } + ``` + code: | + ```kotlin + supabase.from("users").select { + filter { + eq("address->postcode", 90210) + } + } + ``` + - id: filter-foreign-tables + name: Filter Foreign Tables + code: | + ```kotlin + val columns = Columns.raw(""" + name, + cities!inner ( + name + ) + """.trimIndent()) + supabase.from("countries").select( + columns = columns + ) { + filter { + eq("cities.name", "Bali") + } + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'Germany'), + (2, 'Indonesia'); + insert into + cities (id, country_id, name) + values + (1, 2, 'Bali'), + (2, 1, 'Munich'); + ``` + response: | + ```json + { + "data": [ + { + "name": "Indonesia", + "cities": [ + { + "name": "Bali" + } + ] + } + ], + "status": 200, + "statusText": "OK" + } + ``` + - id: or + title: or() + description: | + Finds all rows satisfying at least one of the filters. + notes: | + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("countries").select(columns = Columns.list("name")) { + filter { + or { + Country::id eq 2 + Country::name eq "Algeria" + //or + eq("id", 2) + eq("name", "Algeria") + } + } + } + ``` + - id: use-or-with-and + name: Use `or` with `and` + code: | + ```kotlin + supabase.from("countries").select(columns = Columns.list("name")) { + filter { + or { + Country::id gt 3 + and { + Country::id eq 1 + Country::name eq "Afghanistan" + } + } + } + } + ``` + + - id: not + title: filterNot() + description: | + Finds all rows that don't satisfy the filter. + notes: | + - `.filterNot()` expects you to use the raw [PostgREST syntax](https://postgrest.org/en/stable/api.html#horizontal-filtering-rows) for the filter names and values. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("countries").select { + filter { + filterNot("name", FilterOperation.IS, "") + } + } + ``` + + - id: eq + title: eq() + description: | + Finds all rows whose value on the stated `column` exactly matches the specified `value`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name", "country_id")) { + filter { + City::name eq "The Shire" + //or + eq("name", "The Shire") + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::name eq "San Francisco" + //or + eq("name", "San Francisco") + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::name eq "Mordor" + //or + eq("name", "Mordor") + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("function") { + filter { + City::name eq "Mordor" + //or + eq("name", "Mordor") + } + } + ``` + + - id: neq + title: neq() + description: | + Finds all rows whose value on the stated `column` doesn't match the specified `value`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name", "country_id")) { + filter { + City::name neq "The Shire" + //or + neq("name", "The Shire") + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::name neq "The Shire" + //or + neq("name", "The Shire") + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::name neq "The Shire" + //or + neq("name", "The Shire") + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.rpc("echo_all_cities") { + filter { + neq("address->postcode", 90210) + } + } + ``` + + - id: gt + title: gt() + description: | + Finds all rows whose value on the stated `column` is greater than the specified `value`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::countryId gt 300 + //or + gt("country_id", 300) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::countryId gt 300 + //or + gt("country_id", 300) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::countryId gt 300 + //or + gt("country_id", 300) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::countryId gt 300 + //or + gt("country_id", 300) + } + } + ``` + + - id: gte + title: gte() + description: | + Finds all rows whose value on the stated `column` is greater than or equal to the specified `value`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::countryId gte 300 + //or + gte("country_id", 300) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::countryId gte 300 + //or + gte("country_id", 300) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::countryId gte 300 + //or + gte("country_id", 300) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::countryId gte 300 + //or + gte("country_id", 300) + } + } + ``` + + - id: lt + title: lt() + description: | + Finds all rows whose value on the stated `column` is less than the specified `value`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::countryId lt 300 + //or + lt("country_id", 300) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::countryId lt 300 + //or + lt("country_id", 300) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::countryId lt 300 + //or + lt("country_id", 300) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::countryId lt 300 + //or + lt("country_id", 300) + } + } + ``` + + - id: lte + title: lte() + description: | + Finds all rows whose value on the stated `column` is less than or equal to the specified `value`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::countryId lte 300 + //or + lte("country_id", 300) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::countryId lte 300 + //or + lte("country_id", 300) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::countryId lte 300 + //or + lte("country_id", 300) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::countryId lte 300 + //or + lte("country_id", 300) + } + } + ``` + + - id: like + title: like() + description: | + Finds all rows whose value in the stated `column` matches the supplied `pattern` (case sensitive). + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::name like "%la%" + //or + like("name", "%la%") + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::name like "%la%" + //or + like("name", "%la%") + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::name like "%la%" + //or + like("name", "%la%") + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::name like "%la%" + //or + like("name", "%la%") + } + } + ``` + + - id: ilike + title: ilike() + description: | + Finds all rows whose value in the stated `column` matches the supplied `pattern` (case insensitive). + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::name ilike "%la%" + //or + ilike("name", "%la%") + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::name ilike "%la%" + //or + ilike("name", "%la%") + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::name ilike "%la%" + //or + ilike("name", "%la%") + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::name ilike "%la%" + //or + ilike("name", "%la%") + } + } + ``` + + - id: is + title: is_() + description: | + A check for exact equality (null, true, false), finds all rows whose value on the stated `column` exactly match the specified `value`. + + `is_` and `in_` filter methods are suffixed with `_` to avoid collisions with reserved keywords. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::name isExact null + //or + exact("name", null) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::name isExact null + //or + exact("name", null) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::name isExact null + //or + exact("name", null) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::name isExact null + //or + exact("name", null) + } + } + ``` + + - id: in + title: in_() + description: | + Finds all rows whose value on the stated `column` is found on the specified `values`. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::name isIn listOf("Rio de Janeiro", "San Francisco") + //or + isIn("name", listOf("Rio de Janeiro", "San Francisco")) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::name isIn listOf("Rio de Janeiro", "San Francisco") + //or + isIn("name", listOf("Rio de Janeiro", "San Francisco")) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::name isIn listOf("Rio de Janeiro", "San Francisco") + //or + isIn("name", listOf("Rio de Janeiro", "San Francisco")) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::name isIn listOf("Rio de Janeiro", "San Francisco") + //or + isIn("name", listOf("Rio de Janeiro", "San Francisco")) + } + } + ``` + + - id: contains + title: contains() + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("cities").select(columns = Columns.list("name")) { + filter { + City::mainExports contains listOf("oil") + //or + contains("main_exports", listOf("oil")) + } + } + ``` + - id: with-update + name: With `update()` + code: | + ```kotlin + val toUpdate = City(name = "Mordor") + supabase.from("cities").update(toUpdate) { + filter { + City::mainExports contains listOf("oil") + //or + contains("main_exports", listOf("oil")) + } + } + ``` + - id: with-delete + name: With `delete()` + code: | + ```kotlin + supabase.from("cities").delete { + filter { + City::mainExports contains listOf("oil") + //or + contains("main_exports", listOf("oil")) + } + } + ``` + - id: with-rpc + name: With `rpc()` + code: | + ```kotlin + supabase.postgrest.rpc("echo_all_cities") { + filter { + City::mainExports contains listOf("oil") + //or + contains("main_exports", listOf("oil")) + } + } + ``` + + - id: range-lt + title: rangeLt() + description: | + Only relevant for range columns. Match only rows where every element in column is less than any element in range. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("reservations").select { + filter { + Reservation::during rangeLt ("2000-01-02 08:30" to "2000-01-02 09:30") + //or + rangeLt("during", "2000-01-02 08:30" to "2000-01-02 09:30") + } + } + ``` + data: + sql: | + ```sql + create table + reservations ( + id int8 primary key, + room_name text, + during tsrange + ); + + insert into + reservations (id, room_name, during) + values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "room_name": "Emerald", + "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + description: | + Postgres supports a number of [range + types](https://www.postgresql.org/docs/current/rangetypes.html). You + can filter on range columns using the string representation of range + values. + hideCodeBlock: true + + - id: range-gt + title: rangeGt() + description: | + Only relevant for range columns. Match only rows where every element in column is greater than any element in range. + + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("reservations").select { + filter { + Reservation::during rangeGt ("2000-01-02 08:30" to "2000-01-02 09:30") + //or + rangeGt("during", "2000-01-02 08:30" to "2000-01-02 09:30") + } + } + ``` + data: + sql: | + ```sql + create table + reservations ( + id int8 primary key, + room_name text, + during tsrange + ); + + insert into + reservations (id, room_name, during) + values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + ``` + response: | + ```json + { + "data": [ + { + "id": 2, + "room_name": "Topaz", + "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + description: | + Postgres supports a number of [range + types](https://www.postgresql.org/docs/current/rangetypes.html). You + can filter on range columns using the string representation of range + values. + hideCodeBlock: true + + - id: range-gte + title: rangeGte() + description: | + Only relevant for range columns. Match only rows where every element in column is either contained in range or greater than any element in range. + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("reservations").select { + filter { + Reservation::during rangeGte ("2000-01-02 08:30" to "2000-01-02 09:30") + //or + rangeGte("during", "2000-01-02 08:30" to "2000-01-02 09:30") + } + } + ``` + data: + sql: | + ```sql + create table + reservations ( + id int8 primary key, + room_name text, + during tsrange + ); + + insert into + reservations (id, room_name, during) + values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + ``` + response: | + ```json + { + "data": [ + { + "id": 2, + "room_name": "Topaz", + "during": "[\"2000-01-02 09:00:00\",\"2000-01-02 10:00:00\")" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + description: | + Postgres supports a number of [range + types](https://www.postgresql.org/docs/current/rangetypes.html). You + can filter on range columns using the string representation of range + values. + hideCodeBlock: true + + - id: range-lte + title: rangeLte() + description: | + Only relevant for range columns. Match only rows where every element in column is either contained in range or less than any element in range. + + $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.rangeLte' + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("reservations").select { + filter { + Reservation::during rangeLte ("2000-01-02 08:30" to "2000-01-02 09:30") + //or + rangeLte("during", "2000-01-02 08:30" to "2000-01-02 09:30") + } + } + ``` + data: + sql: | + ```sql + create table + reservations ( + id int8 primary key, + room_name text, + during tsrange + ); + + insert into + reservations (id, room_name, during) + values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "room_name": "Emerald", + "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + description: | + Postgres supports a number of [range + types](https://www.postgresql.org/docs/current/rangetypes.html). You + can filter on range columns using the string representation of range + values. + hideCodeBlock: true + + - id: range-adjacent + title: rangeAdjacent() + description: | + Only relevant for range columns. Match only rows where column is mutually exclusive to range and there can be no element between the two ranges. + + examples: + - id: with-select + name: With `select()` + isSpotlight: true + code: | + ```kotlin + supabase.from("reservations").select { + filter { + Reservation::during adjacent ("2000-01-02 08:30" to "2000-01-02 09:30") + //or + adjacent("during", "2000-01-02 08:30" to "2000-01-02 09:30") + } + } + ``` + data: + sql: | + ```sql + create table + reservations ( + id int8 primary key, + room_name text, + during tsrange + ); + + insert into + reservations (id, room_name, during) + values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "room_name": "Emerald", + "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + + hideCodeBlock: true + + - id: overlaps + title: overlaps() + $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.overlaps' + description: | + Only relevant for array and range columns. Match only rows where column and value have an element in common. + + examples: + - id: on-array-columns + name: On array columns + code: | + ```kotlin + supabase.from("issues").select(columns = Columns.list("title")) { + filter { + Issue::tags overlaps listOf("is:closed", "severity:high") + //or + overlaps("tags", listOf("is:closed", "severity:high")) + } + } + ``` + data: + sql: | + ```sql + create table + issues ( + id int8 primary key, + title text, + tags text[] + ); + + insert into + issues (id, title, tags) + values + (1, 'Cache invalidation is not working', array['is:open', 'severity:high', 'priority:low']), + (2, 'Use better names', array['is:open', 'severity:low', 'priority:medium']); + ``` + response: | + ```json + { + "data": [ + { + "title": "Cache invalidation is not working" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + isSpotlight: true + - id: on-range-columns + name: On range columns + code: | + ```kotlin + supabase.from("issues").select(columns = Columns.list("title")) { + filter { + Issue::during overlaps listOf("2000-01-01 12:45", "2000-01-01 13:15") + //or + overlaps("during", listOf("2000-01-01 12:45", "2000-01-01 13:15")) + } + } + ``` + data: + sql: | + ```sql + create table + reservations ( + id int8 primary key, + room_name text, + during tsrange + ); + + insert into + reservations (id, room_name, during) + values + (1, 'Emerald', '[2000-01-01 13:00, 2000-01-01 15:00)'), + (2, 'Topaz', '[2000-01-02 09:00, 2000-01-02 10:00)'); + ``` + response: | + ```json + { + "data": [ + { + "id": 1, + "room_name": "Emerald", + "during": "[\"2000-01-01 13:00:00\",\"2000-01-01 15:00:00\")" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + description: | + Postgres supports a number of [range + types](https://www.postgresql.org/docs/current/rangetypes.html). You + can filter on range columns using the string representation of range + values. + hideCodeBlock: true + + - id: text-search + title: textSearch() + description: | + Only relevant for text and tsvector columns. Match only rows where `column` matches the query string in `query`. + + For more information, see [Postgres full text search](https://supabase.com/docs/guides/database/full-text-search). + examples: + - id: text-search + name: Text search + code: | + ```kotlin + supabase.from("quotes").select(columns = Columns.list("catchphrase")) { + filter { + textSearch(column = "catchphrase", query = "'fat' & 'cat'", config = "english", type = TextSearchType.YOUR_TYPE) + } + } + ``` + - id: basic-normalization + name: Basic normalization + description: Uses PostgreSQL's `plainto_tsquery` function. + code: | + ```kotlin + supabase.from("quotes").select(columns = Columns.list("catchphrase")) { + filter { + textSearch(column = "catchphrase", query = "'fat' & 'cat'", config = "english", type = TextSearchType.PLAINTO) + } + } + ``` + - id: full-normalization + name: Full normalization + description: Uses PostgreSQL's `phraseto_tsquery` function. + code: | + ```kotlin + supabase.from("quotes").select(columns = Columns.list("catchphrase")) { + filter { + textSearch(column = "catchphrase", query = "'fat' & 'cat'", config = "english", type = TextSearchType.PHRASETO) + } + } + ``` + - id: web-search + name: Websearch + description: | + Uses PostgreSQL's `websearch_to_tsquery` function. + This function will never raise syntax errors, which makes it possible to use raw user-supplied input for search, and can be used + with advanced operators. + + - `unquoted text`: text not inside quote marks will be converted to terms separated by & operators, as if processed by plainto_tsquery. + - `"quoted text"`: text inside quote marks will be converted to terms separated by <-> operators, as if processed by phraseto_tsquery. + - `OR`: the word “or” will be converted to the | operator. + - `-`: a dash will be converted to the ! operator. + + code: | + ```kotlin + supabase.from("quotes").select(columns = Columns.list("catchphrase")) { + filter { + textSearch(column = "catchphrase", query = "'fat' & 'cat'", config = "english", type = TextSearchType.WEBSEARCH) + } + } + ``` + + - id: filter + title: filter() + $ref: '@supabase/postgrest-js.PostgrestFilterBuilder.filter' + notes: | + filter() expects you to use the raw PostgREST syntax for the filter values. + examples: + - id: with-select + name: With `select()` + code: | + ```kotlin + supabase.from("countries").select { + filter { + filter(column = "name", operator = FilterOperator.IN, value = "('Algeria', 'Japan')") + } + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ```json + { + "data": [ + { + "id": 3, + "name": "Algeria" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + isSpotlight: true + - id: on-a-foreign-table + name: On a foreign table + code: | + ```kotlin + val columns = Columns.raw(""" + name, + cities!inner ( + name + ) + """.trimIndent()) + supabase.from("countries").select( + columns = columns + ) { + filter { + filter(column = "cities.name", operator = FilterOperator.EQ, value = "Bali") + } + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'Germany'), + (2, 'Indonesia'); + insert into + cities (id, country_id, name) + values + (1, 2, 'Bali'), + (2, 1, 'Munich'); + ``` + response: | + ```json + { + "data": [ + { + "name": "Indonesia", + "cities": [ + { + "name": "Bali" + } + ] + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + - id: order + title: order() + description: | + Order the query result by column. + $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.order' + examples: + - id: with-select + name: With `select()` + code: | + ```kotlin + supabase.from("countries").select(columns = Columns.list("id", "name")) { + order(column = "id", order = Order.ASCENDING) + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ```json + { + "data": [ + { + "id": 3, + "name": "Algeria" + }, + { + "id": 2, + "name": "Albania" + }, + { + "id": 1, + "name": "Afghanistan" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + isSpotlight: true + - id: on-a-foreign-table + name: On a foreign table + code: | + ```kotlin + val columns = Columns.raw(""" + name, + cities ( + name + ) + """.trimIndent()) + supabase.from("countries").select( + columns = columns + ) { + order(column = "id", order = Order.ASCENDING, foreignTable = "cities") + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'United States'), + (2, 'Vanuatu'); + insert into + cities (id, country_id, name) + values + (1, 1, 'Atlanta'), + (2, 1, 'New York City'); + ``` + response: | + ```json + { + "data": [ + { + "name": "United States", + "cities": [ + { + "name": "New York City" + }, + { + "name": "Atlanta" + } + ] + }, + { + "name": "Vanuatu", + "cities": [] + } + ], + "status": 200, + "statusText": "OK" + } + ``` + description: | + Ordering on foreign tables doesn't affect the ordering of + the parent table. + hideCodeBlock: true + + - id: limit + title: limit() + description: | + Limit the query result by count. + $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.limit' + examples: + - id: with-select + name: With `select()` + code: | + ```kotlin + supabase.from("countries").select { + limit(count = 1) + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ```json + { + "data": [ + { + "name": "Afghanistan" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + isSpotlight: true + - id: on-a-foreign-table + name: On a foreign table + code: | + ```kotlin + val columns = Columns.raw(""" + name, + cities ( + name + ) + """) + supabase.from("countries").select( + columns = columns + ) { + limit(count = 1, foreignTable = "cities") + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + create table + cities ( + id int8 primary key, + country_id int8 not null references countries, + name text + ); + + insert into + countries (id, name) + values + (1, 'United States'); + insert into + cities (id, country_id, name) + values + (1, 1, 'Atlanta'), + (2, 1, 'New York City'); + ``` + response: | + ```json + { + "data": [ + { + "name": "United States", + "cities": [ + { + "name": "Atlanta" + } + ] + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + - id: range + title: range() + description: | + Limit the query result by from and to inclusively. + examples: + - id: with-select + name: With `select()` + code: | + ```kotlin + supabase.from("countries").select { + range(1L..5L) + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ```json + { + "data": [ + { + "name": "Afghanistan" + }, + { + "name": "Albania" + } + ], + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + isSpotlight: true + - id: single + title: single() + $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.single' + examples: + - id: with-select + name: With `select()` + code: | + ```kotlin + val result = supabase.from("countries").select(Columns.list("name")) { + limit(1) + single() + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ```json + { + "data": { + "name": "Afghanistan" + }, + "status": 200, + "statusText": "OK" + } + ``` + hideCodeBlock: true + isSpotlight: true + + - id: csv + $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.csv' + title: csv() + examples: + - id: return-data-as-csv + name: Return data as CSV + code: | + ```kotlin + val (csvData, _) = supabase.from("countries").select { + explain() + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ```json + { + "data": "id,name\n1,Afghanistan\n2,Albania\n3,Algeria", + "status": 200, + "statusText": "OK" + } + ``` + description: | + By default, the data is returned in JSON format, but can also be returned as Comma Separated Values. + hideCodeBlock: true + isSpotlight: true + + - id: explain + $ref: '@supabase/postgrest-js.PostgrestTransformBuilder.explain' + title: Using Explain + description: | + For debugging slow queries, you can get the [Postgres `EXPLAIN` execution plan](https://www.postgresql.org/docs/current/sql-explain.html) of a query + using the `explain()` method. This works on any query, even for `rpc()` or writes. + + Explain is not enabled by default as it can reveal sensitive information about your database. + It's best to only enable this for testing environments but if you wish to enable it for production you can provide additional protection by using a `pre-request` function. + + Follow the [Performance Debugging Guide](/docs/guides/api/rest/debugging-performance) to enable the functionality on your project. + examples: + - id: get-execution-plan + name: Get the execution plan + code: | + ```kotlin + val result = supabase.from("countries").select { + explain() + } + ``` + data: + sql: | + ```sql + create table + countries (id int8 primary key, name text); + + insert into + countries (id, name) + values + (1, 'Afghanistan'), + (2, 'Albania'), + (3, 'Algeria'); + ``` + response: | + ``` + Aggregate (cost=33.34..33.36 rows=1 width=112) + -> Limit (cost=0.00..18.33 rows=1000 width=40) + -> Seq Scan on countries (cost=0.00..22.00 rows=1200 width=40) + ``` + description: | + By default, the data is returned in TEXT format, but can also be returned as JSON by using the `format` parameter. + hideCodeBlock: true + isSpotlight: true + - id: auth-api + title: 'Overview' + notes: | + - The auth methods can be accessed via the Supabase Auth client. + examples: + - id: create-auth-client + name: Create auth client + isSpotlight: true + code: | + ```kotlin + val supabase = createSupabaseClient(supabaseURL = "https://xyzcompany.supabase.co'", supabaseKey = "public-anon-key") { ... } + val auth = supabase.auth + ``` + - id: sign-up + title: 'signUp()' + $ref: '@supabase/gotrue-js.GoTrueClient.signUp' + notes: | + Creates a new user. + - By default, the user needs to verify their email address before logging in. To turn this off, disable **Confirm email** in [your project](https://supabase.com/dashboard/project/_/auth/providers). + - **Confirm email** determines if users need to confirm their email address after signing up. + - If **Confirm email** is enabled, the return value is the user and you won't be logged in automatically. + - If **Confirm email** is disabled, the return value is null and you will be logged in instead. + - When the user confirms their email address, they are redirected to the [`SITE_URL`](https://supabase.com/docs/reference/auth/config#site_url) by default. You can modify your `SITE_URL` or add additional redirect URLs in [your project](https://supabase.com/dashboard/project/_/auth/url-configuration). + - To learn how to handle OTP links & OAuth refer to [initializing](/docs/reference/kotlin/initializing) + - If signUpWith() is called for an existing confirmed user: + - If **Confirm email** is enabled in [your project](https://supabase.com/dashboard/project/_/auth/providers), an obfuscated/fake user object is returned. + - If **Confirm email** is disabled, the error message, `User already registered` is returned. + examples: + - id: sign-up-email + name: Sign up with email + isSpotlight: true + code: | + ```kotlin + val user = supabase.auth.signUpWith(Email) { + email = "example@email.com" + password = "example-password" + } + ``` + - id: sign-up-phone + name: Sign up with a phone number + isSpotlight: true + code: | + ```kotlin + val user = supabase.auth.signUpWith(Phone) { + phone = "+4912345679" + password = "example-password" + } + ``` + - id: sign-up-with-additional-user-metadata + name: Sign up with additional user metadata + isSpotlight: false + code: | + ```kotlin + val user = supabase.auth.signUpWith(Email) { + email = "example@email.com" + password = "example-password" + data = buildJsonObject { + put("first_name", "John") + put("age", 24) + } + } + ``` + - id: sign-up-with-redirect + name: Sign up with a redirect URL + description: | + - See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. + code: | + ```kotlin + val user = supabase.auth.signUpWith(Email, redirectUrl = "https://example.com") { + email = "example@email.com" + password = "example-password" + } + ``` + - id: sign-in-with-password + title: 'signInWith()' + $ref: '@supabase/gotrue-js.GoTrueClient.signInWithPassword' + notes: | + Logs in an existing user. + - Requires either an email and password or a phone number and password. + examples: + - id: sign-in-with-email-and-password + name: Sign in with email and password + isSpotlight: true + code: | + ```kotlin + supabase.auth.signInWith(Email) { + email = "example@email.com" + password = "example-password" + } + ``` + - id: sign-in-with-phone-and-password + name: Sign in with phone and password + isSpotlight: false + code: | + ```kotlin + supabase.auth.signInWith(Phone) { + phone = "+4912345679" + password = "example-password" + } + ``` + - id: sign-in-with-id-token + name: Sign in with id token + isSpotlight: false + code: | + ```kotlin + supabase.auth.signInWith(IDToken) { + idToken = "token" + provider = Google //Also supported: Apple, Azure and Facebook + //optional: + nonce = "nonce" + data = buildJsonObject { + //... + } + } + ``` + + - id: sign-in-with-otp + title: 'signInWith(OTP)' + $ref: '@supabase/gotrue-js.GoTrueClient.signInWithOtp' + notes: | + Sends a OTP to the user's email or phone number. + - Requires either an email or phone number. + - This method is used for passwordless sign-ins where a OTP is sent to the user's email or phone number. + - If the user doesn't exist, `signInWith(OTP)` will signup the user instead. To restrict this behavior, you can set `createUser` to `false`. + - If you're using an email, you can configure whether you want the user to receive a magiclink or a OTP. + - If you're using phone, you can configure whether you want the user to receive a OTP. + - The magic link's destination URL is determined by the [`SITE_URL`](/docs/reference/auth/config#site_url). + - See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. + - To learn how to handle OTP links & OAuth refer to [initializing](/docs/reference/kotlin/initializing) + - Magic links and OTPs share the same implementation. To send users a one-time code instead of a magic link, [modify the magic link email template](https://supabase.com/dashboard/project/_/auth/templates) to include `{{ .Token }}` instead of `{{ .ConfirmationURL }}`. + examples: + - id: sign-in-with-email + name: Sign in with email + isSpotlight: true + description: The user will be sent an email which contains either a magiclink or a OTP or both. By default, a given user can only request a OTP once every 60 seconds. + code: | + ```kotlin + supabase.auth.signInWith(OTP) { + email = "example@email.com" + } + ``` + - id: sign-in-with-sms-otp + name: Sign in with SMS OTP + isSpotlight: false + description: The user will be sent a SMS which contains a OTP. By default, a given user can only request a OTP once every 60 seconds. + code: | + ```kotlin + supabase.auth.signInWith(OTP) { + phone = "+4912345679" + } + ``` + - id: sign-in-with-oauth + title: 'signInWith(OAuthProvider)' + $ref: '@supabase/gotrue-js.GoTrueClient.signInWithOAuth' + notes: | + - This method is used for signing in using a third-party provider. + - Supabase supports many different [third-party providers](https://supabase.com/docs/guides/auth#providers). + - To learn how to handle OTP links & OAuth refer to [initializing](/docs/reference/kotlin/initializing) + examples: + - id: sign-in-using-a-third-party-provider + name: Sign in using a third-party provider + isSpotlight: true + code: | + ```kotlin + supabase.auth.signInWith(Github) + ``` + - id: sign-in-using-a-third-party-provider with scopes + name: Sign in using a third-party provider with scopes + isSpotlight: true + code: | + ```kotlin + supabase.auth.signInWith(Github) { + scopes.add("email") + } + ``` + + - id: sign-in-using-a-third-party-provider-with-redirect + name: Create a custom url + isSpotlight: false + description: | + - When the third-party provider successfully authenticates the user, the provider redirects the user to the URL specified in the `redirectUrl` parameter. This parameter defaults to the [`SITE_URL`](/docs/reference/auth/config#site_url). It does not redirect the user immediately after invoking this method. + - See [redirect URLs and wildcards](/docs/guides/auth/overview#redirect-urls-and-wildcards) to add additional redirect URLs to your project. + - oAuthUrl() provides the URL which needs to be opened in a browser. + - The redirectTo URL needs to be setup correctly in your project under Authentication -> URL Configuration -> Redirect URLs. + - To see how you can use a custom in-app browser on Android, check our [demo](https://github.com/supabase-community/supabase-kt/tree/development/demos/android-login) on GitHub. + code: | + ```kotlin + val url = supabase.auth.oAuthUrl(Github, redirectUrl = "https://example.com") + ``` + - id: sign-in-with-scopes + name: Create a custom url with scopes + isSpotlight: false + description: | + If you need additional data from an OAuth provider, you can include a space-separated list of scopes in your request to get back an OAuth provider token. + You may also need to specify the scopes in the provider's OAuth app settings, depending on the provider. The list of scopes will be documented by the third-party provider you are using and specifying scopes will enable you to use the OAuth provider token to call additional APIs supported by the third-party provider to get more information. + code: | + ```kotlin + val url = supabase.auth.oAuthUrl(Github, redirectUrl = "https://example.com") { + scopes.add("email") + } + ``` + - id: sign-in-with-sso + title: 'signInWithSSO()' + $ref: '@supabase/gotrue-js.GoTrueClient.signInWithSSO' + notes: | + - Before you can call this method you need to [establish a connection](/docs/guides/auth/sso/auth-sso-saml#managing-saml-20-connections) to an identity provider. Use the [CLI commands](/docs/reference/cli/supabase-sso) to do this. + - If you've associated an email domain to the identity provider, you can change the `domain` property in the `signInWith(SSO)` method to start a sign-in flow. + - In case you need to use a different way to start the authentication flow with an identity provider, you can change the `providerId` property. For example: + - Mapping specific user email addresses with an identity provider. + - Using different hints to identity the identity provider to be used by the user, like a company-specific page, IP address or other tracking information. + - To learn how to handle OTP links & OAuth refer to [initializing](/docs/reference/kotlin/initializing) + + examples: + - id: sign-in-with-domain + name: Sign in with email domain + isSpotlight: true + code: | + ```kotlin + // You can extract the user's email domain and use it to trigger the + // authentication flow with the correct identity provider. + + supabase.auth.signInWith(SSO) { + domain = "company.com" + } + + //the url was opened automatically, if you don't want that, provide a custom redirect url + ``` + - id: sign-in-with-provider-uuid + name: Sign in with provider UUID + isSpotlight: true + code: | + ```kotlin + // Useful when you need to map a user's sign in request according + // to different rules that can't use email domains. + + supabase.auth.signInWith(SSO){ + providerId = "21648a9d-8d5a-4555-a9d1-d6375dc14e92" + } + + //the url was opened automatically, if you don't want that, provide a custom redirect url + ``` + - id: sign-out + title: 'signOut()' + $ref: '@supabase/gotrue-js.GoTrueClient.signOut' + notes: | + Logs out the current user. + - In order to use the `signOut()` method, the user needs to be signed in first. + examples: + - id: sign-out + name: Sign out + isSpotlight: true + code: | + ```kotlin + supabase.auth.signOut() + ``` + - id: sign-out-all-sessions + name: Sign out all sessions + isSpotlight: true + code: | + ```kotlin + supabase.auth.signOut(SignOutScope.GLOBAL) + ``` + - id: sign-out-others + name: Sign out all sessions except the current + isSpotlight: true + code: | + ```kotlin + supabase.auth.signOut(SignOutScope.OTHERS) + ``` + - id: verify-otp + title: 'Verify OTPs' + $ref: '@supabase/gotrue-js.GoTrueClient.verifyOtp' + notes: | + Log in a user given a User supplied OTP received via mobile. + examples: + - id: verify-email-otp(otp) + name: Verify an Email OTP + isSpotlight: true + code: | + ```kotlin + supabase.auth.verifyEmailOtp(type = OtpType.Email.INVITE, email = "example@email.com", token = "token") + ``` + description: | + Available types are: + - `OtpType.Email.MAGIC_LINK` + - `OtpType.Email.SIGNUP` + - `OtpType.Email.INVITE` + - `OtpType.Email.RECOVERY` + - `OtpType.Email.EMAIL_CHANGE` + - id: verify-phone-otp(otp) + name: Verify an Phone OTP + isSpotlight: false + code: | + ```kotlin + supabase.auth.verifyPhoneOtp(type = OtpType.Phone.SMS, phone = "+491234567", token = "token") + ``` + description: | + Available types are: + - `OtpType.Phone.SMS` + - `OtpType.Phone.PHONE_CHANGE` + - id: send-password-reauthentication + title: 'reauthenticate()' + $ref: '@supabase/gotrue-js.GoTrueClient.reauthenticate' + notes: | + - This method is used together with `modifyUser()` when a user's password needs to be updated. + - This method will send a nonce to the user's email. If the user doesn't have a confirmed email address, the method will send the nonce to the user's confirmed phone number instead. + examples: + - id: send-reauthentication-nonce + name: Send reauthentication nonce + description: Sends a reauthentication nonce to the user's email or phone number. + isSpotlight: true + code: | + ```kotlin + supabase.auth.reauthenticate() + ``` + - id: resend-email-or-phone-otps + title: 'resend()' + $ref: '@supabase/gotrue-js.GoTrueClient.resend' + notes: | + - Resends a signup confirmation, email change or phone change email to the user. + - Passwordless sign-ins can be resent by calling the `signInWith(OTP)` method again. + - Password recovery emails can be resent by calling the `resetPasswordForEmail()` method again. + - This method will only resend an email or phone OTP to the user if there was an initial signup, email change or phone change request being made. + examples: + - id: resend-email-signup-confirmation + name: Resend an email signup confirmation + description: Resends the email signup confirmation to the user + isSpotlight: true + code: | + ```kotlin + supabase.auth.resendEmail(OtpType.Email.SIGNUP, "example@email.com") + ``` + - id: resend-phone-signup-confirmation + name: Resend a phone signup confirmation + description: Resends the phone signup confirmation email to the user + code: | + ```kotlin + supabase.auth.resendPhone(OtpType.Phone.SMS, "1234567890") + ``` + - id: resend-email-change-email + name: Resend email change email + description: Resends the email change email to the user + code: | + ```kotlin + supabase.auth.resendEmail(OtpType.Email.EMAIL_CHANGE, "example@email.com") + ``` + - id: resend-phone-change + name: Resend phone change OTP + description: Resends the phone change OTP to the user + code: | + ```kotlin + supabase.auth.resendPhone(OtpType.Phone.PHONE_CHANGE, "1234567890") + ``` + - id: get-session + title: 'Get current session' + $ref: '@supabase/gotrue-js.GoTrueClient.getSession' + notes: | + Returns the current session, or `null` if there is none. + examples: + - id: get-the-session-data + name: Get the session data + isSpotlight: true + code: | + ```kotlin + val session = supabase.auth.currentSessionOrNull() + ``` + - id: get-user + title: 'getUser()' + $ref: '@supabase/gotrue-js.GoTrueClient.getUser' + description: | + - This method gets the user object from the current session. + - Fetches the user object from the database instead of local session. + - Should be used only when you require the most current user data. For faster results, `getCurrentSessionOrNull()?.user` is recommended. + examples: + - id: get-the-logged-in-user-with-the-current-existing-session + name: Get the logged in user with the current session + isSpotlight: true + code: | + ```kotlin + val user = supabase.auth.retrieveUserForCurrentSession(updateSession = true) + ``` + description: | + `updateSession` updates the local session with the new user + - id: get-different-user + name: Get a user based on their access token + isSpotlight: true + code: | + ```kotlin + val user = supabase.auth.retrieveUser("JWT") + ``` + - id: update-user + title: 'modifyUser()' + $ref: '@supabase/gotrue-js.GoTrueClient.updateUser' + notes: | + Modifies the user data. + - In order to use the `modifyUser()` method, the user needs to be signed in first. + - By default, email updates sends a confirmation link to both the user's current and new email. + To only send a confirmation link to the user's new email, disable **Secure email change** in your project's [email auth provider settings](https://supabase.com/dashboard/project/_/auth/providers). + examples: + - id: update-the-email-for-an-authenticated-user + name: Update the email for an authenticated user + description: Sends a "Confirm Email Change" email to the new email address. + isSpotlight: false + code: | + ```kotlin + val user = supabase.auth.modifyUser { + email = "newEmail@email.com" + } + ``` + - id: update-the-password-for-an-authenticated-user + name: Update the password for an authenticated user + isSpotlight: false + code: | + ```kotlin + val user = supabase.auth.modifyUser { + password = "secretPassword" + } + ``` + - id: update-the-users-metadata + name: Update the user's metadata + isSpotlight: true + code: | + ```kotlin + val user = supabase.auth.modifyUser { + data { + put("name", "John") + } + } + ``` + - id: set-session + title: 'importSession()' + $ref: '@supabase/gotrue-js.GoTrueClient.setSession' + notes: | + Changes the local session. + - `importSession()` takes in a UserSession. + - [Refresh token rotation](/docs/reference/auth/config#refresh_token_rotation_enabled) is enabled by default on all projects to guard against replay attacks. + - You can configure the [`REFRESH_TOKEN_REUSE_INTERVAL`](https://supabase.com/docs/reference/auth/config#refresh_token_reuse_interval) which provides a short window in which the same refresh token can be used multiple times in the event of concurrency or offline issues. + examples: + - id: refresh-the-session + name: Set local session + description: Sets the local session from refresh_token and returns current session or an error if the refresh_token is invalid. + isSpotlight: true + code: | + ```kotlin + supabase.auth.importSession(UserSession(accessToken = "token", refreshToken = "refresh", expiresIn = 2000, tokenType = "Bearer", user = null)) + ``` + - id: refresh-session + title: 'refreshSession()' + $ref: '@supabase/gotrue-js.GoTrueClient.refreshSession' + notes: | + This method will refresh the session whether the current one is expired or not. + + - This is done automatically, but can be disabled in the Auth config. + examples: + - id: refresh-current-session + name: Refresh current session + isSpotlight: true + code: | + ```kotlin + val session = supabase.auth.refreshCurrentSession() + ``` + - id: refresh-session-using-the-current-session + name: Refresh session using the refresh token + isSpotlight: true + code: | + ```kotlin + val session = supabase.auth.refreshSession(refreshToken = "refreshToken") + ``` + - id: on-auth-state-change + title: 'sessionStatus' + $ref: '@supabase/gotrue-js.GoTrueClient.onAuthStateChange' + notes: | + Listen to session changes. + examples: + - id: listen-to-auth-changes + name: Listen to auth changes + isSpotlight: true + code: | + ```kotlin + supabase.auth.sessionStatus.collect { + when(it) { + is SessionStatus.Authenticated -> println(it.session.user) + SessionStatus.LoadingFromStorage -> println("Loading from storage") + SessionStatus.NetworkError -> println("Network error") + SessionStatus.NotAuthenticated -> println("Not authenticated") + } + } + ``` + description: | + Types of statuses: + - `NotAuthenticated`, + - `LoadingFromStorage`, + - `NetworkError`, + - `Authenticated(session)` + - id: reset-password-for-email + title: 'Send a password reset request' + notes: | + Sends a password reset request to the given email address. + - The password reset flow consist of 2 broad steps: (i) Allow the user to login via the password reset link; (ii) Update the user's password. + - The `resetPasswordForEmail()` only sends a password reset link to the user's email. + To update the user's password, see [`modifyUser()`](/docs/reference/kotlin/auth-updateuser). + - The user gets redirected back to your app, assuming you setup [OTP handling](/docs/reference/kotlin/initializing) + - After the user has been redirected successfully, prompt them for a new password and call `modifyUser()`: + ```kotlin + supabase.auth.modifyUser { + password = "1234567" + } + ``` + examples: + - id: send-password-reset-email + name: Send password reset email + isSpotlight: true + code: | + ```kotlin + supabase.auth.resetPasswordForEmail(email = "example@email.com") + ``` + - id: exchange-code-for-session + title: 'exchangeCodeForSession()' + $ref: '@supabase/gotrue-js.GoTrueClient.exchangeCodeForSession' + notes: | + - Used when `flowType` is set to `FlowType.PKCE` in the Auth configuration. + examples: + - id: exchange-auth-code + name: Exchange Auth Code + isSpotlight: true + code: | + ```kotlin + supabase.auth.exchangeCodeForSession("34e770dd-9ff9-416c-87fa-43b31d7ef225") + ``` + - id: auth-mfa-api + title: 'Overview' + notes: | + This section contains methods commonly used for Multi-Factor Authentication (MFA) and are invoked behind the `supabase.auth.mfa` namespace. + + Currently, we only support time-based one-time password (TOTP) as the 2nd factor. We don't support recovery codes but we allow users to enroll more than 1 TOTP factor, with an upper limit of 10. + + Having a 2nd TOTP factor for recovery frees the user of the burden of having to store their recovery codes somewhere. It also reduces the attack surface since multiple recovery codes are usually generated compared to just having 1 backup TOTP factor. + - id: mfa-enroll + title: 'Enroll a factor' + $ref: '@supabase/gotrue-js.GoTrueMFAApi.enroll' + notes: | + Enrolls a new factor. + - Currently, `totp` is the only supported `factorType`. The returned `id` should be used to create a challenge. + - To create a challenge, see [`mfa.createChallenge()`](/docs/reference/kotlin/auth-mfa-challenge). + - To verify a challenge, see [`mfa.verifyChallenge()`](/docs/reference/kotlin/auth-mfa-verify). + - To create and verify a challenge in a single step, see [`mfa.createChallengeAndVerify()`](/docs/reference/kotlin/auth-mfa-challengeandverify). + examples: + - id: enroll-totp-factor + name: Enroll a time-based, one-time password (TOTP) factor + isSpotlight: true + code: | + ```kotlin + val factor = supabase.auth.mfa.enroll(factorType = FactorType.TOTP) + + // Use the id to create a challenge. + // The challenge can be verified by entering the code generated from the authenticator app. + // The code will be generated upon scanning the qr_code or entering the secret into the authenticator app. + val (id, type, qrCode) = factor.data //qrCode is a svg as a string + val (factorId, factorType, _) = factor + ``` + - id: get-local-verified-factors + name: Check the local user for verified factors + isSpotlight: true + code: | + ```kotlin + val verifiedFactors = supabase.auth.mfa.verifiedFactors + ``` + - id: retrieve-verified-factors + name: Retrieve verified factors + isSpotlight: true + code: | + ```kotlin + val verifiedFactors = supabase.auth.mfa.retrieveFactorsForCurrentUser() + ``` + - id: mfa-challenge + title: 'mfa.challenge()' + $ref: '@supabase/gotrue-js.GoTrueMFAApi.challenge' + notes: | + Creates a challenge for a factor. + - An [enrolled factor](/docs/reference/kotlin/auth-mfa-enroll) is required before creating a challenge. + - To verify a challenge, see [`mfa.verifyChallenge()`](/docs/reference/kotlin/auth-mfa-verify). + examples: + - id: create-mfa-challenge + name: Create a challenge for a factor + isSpotlight: true + code: | + ```kotlin + val challenge = supabase.auth.mfa.createChallenge(factorId = "34e770dd-9ff9-416c-87fa-43b31d7ef225") + ``` + - id: mfa-verify + title: 'mfa.verify()' + $ref: '@supabase/gotrue-js.GoTrueMFAApi.verify' + notes: | + Verifies a challenge for a factor. + - To verify a challenge, please [create a challenge](/docs/reference/kotlin/auth-mfa-challenge) first. + examples: + - id: verify-challenge + name: Verify a challenge for a factor + isSpotlight: true + code: | + ```kotlin + supabase.auth.mfa.verifyChallenge( + factorId = "34e770dd-9ff9-416c-87fa-43b31d7ef225", + challengeId = "4034ae6f-a8ce-4fb5-8ee5-69a5863a7c15", + code = "123456", + saveSession = true // this is set to true by default, but you can set it to false if you want to handle the session yourself + ) + ``` + - id: mfa-challenge-and-verify + title: 'mfa.challengeAndVerify()' + $ref: '@supabase/gotrue-js.GoTrueMFAApi.challengeAndVerify' + notes: | + Creates and verifies a challenge for a factor. + - An [enrolled factor](/docs/reference/kotlin/auth-mfa-enroll) is required before invoking `createChallengeAndVerify()`. + - Executes [`mfa.createChallenge()`](/docs/reference/kotlin/auth-mfa-challenge) and [`mfa.verifyChallenge()`](/docs/reference/kotlin/auth-mfa-verify) in a single step. + examples: + - id: challenge-and-verify + name: Create and verify a challenge for a factor + isSpotlight: true + code: | + ```kotlin + supabase.auth.mfa.createChallengeAndVerify( + factorId = "34e770dd-9ff9-416c-87fa-43b31d7ef225", + code = "123456", + saveSession = true // this is set to true by default, but you can set it to false if you want to handle the session yourself + ) + ``` + - id: mfa-unenroll + title: 'mfa.unenroll()' + $ref: '@supabase/gotrue-js.GoTrueMFAApi.unenroll' + notes: | + Unenroll removes a MFA factor. A user has to have an `AAL2` authentication level in order to unenroll a verified factor. + examples: + - id: unenroll-a-factor + name: Unenroll a factor + isSpotlight: true + code: | + ```kotlin + supabase.auth.mfa.unenroll(factorId = "34e770dd-9ff9-416c-87fa-43b31d7ef225") + ``` + - id: mfa-get-authenticator-assurance-level + title: 'mfa.getAuthenticatorAssuranceLevel()' + $ref: '@supabase/gotrue-js.GoTrueMFAApi.getAuthenticatorAssuranceLevel' + notes: | + - Authenticator Assurance Level (AAL) is the measure of the strength of an authentication mechanism. + - In Supabase, having an AAL of `aal1` refers to having the 1st factor of authentication such as an email and password or OAuth sign-in while `aal2` refers to the 2nd factor of authentication such as a time-based, one-time-password (TOTP). + - If the user has a verified factor, the `next` field will return `AuthenticatorAssuranceLevel.AAL2`, else, it will return `AuthenticatorAssuranceLevel.AAL1`. + examples: + - id: get-aal + name: Get the AAL details of the current session + isSpotlight: true + code: | + ```kotlin + val (current, next) = supabase.auth.mfa.getAuthenticatorAssuranceLevel() + ``` + - id: aal-enabled + name: Check whether the user has at least one verified factor + isSpotlight: true + code: | + ```kotlin + val enabled = supabase.auth.mfa.isMfaEnabled + //flow variant, automatically emitting new values on session changes + val enabledFlow = supabase.auth.mfa.isMfaEnabledFlow + ``` + - id: aal-enabled-for-current-session + name: Check whether the user is logged in using AAL2 + isSpotlight: true + code: | + ```kotlin + val loggedInUsingMfa = supabase.auth.mfa.loggedInUsingMfa + //flow variant, automatically emitting new values on session changes + val loggedInUsingMfaFlow = supabase.auth.mfa.loggedInUsingMfaFlow + ``` + - id: admin-api + title: 'Overview' + notes: | + - Any method under the `supabase.auth.admin` namespace requires a `service_role` key. + - These methods are considered admin methods and should be called on a trusted server. Never expose your `service_role` key in the browser. + examples: + - id: create-auth-admin-client + name: Create server-side auth client + isSpotlight: true + code: | + ```kotlin + val supabase = createSupabaseClient( + supabaseUrl = "https://id.supabase.co", + supabaseKey = "supabaseKey" + ) { + install(Auth) { + autoLoadFromStorage = false + alwaysAutoRefresh = false + } + // install other plugins (these will use the service role key) + } + supabase.auth.importAuthToken("service_role") + + // Access auth admin api + val adminGoTrueClient = supabase.auth.admin + ``` + + - id: get-user-by-id + title: 'getUserById()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.getUserById' + notes: | + Fetches the user object from the database based on the user's id. + - The `retrieveUserById()` method requires the user's id which maps to the `auth.users.id` column. + examples: + - id: fetch-the-user-object-using-the-access-token-jwt + name: Fetch the user object using the access_token jwt + isSpotlight: true + code: | + ```kotlin + val user = supabase.auth.admin.retrieveUserById(uid = "f2a0b0a0-6b1a-4b7a-8f1a-4b7a6b1a8f1a") + ``` + + - id: list-users + title: 'listUsers()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.listUsers' + notes: | + Retrieves a list of users. + - Defaults to return 50 users per page. + examples: + - id: get-a-full-list-of-users + name: Get a page of users + isSpotlight: true + code: | + ```kotlin + val users = supabase.auth.admin.retrieveUsers() + ``` + - id: get-paginated-list-of-users + name: Paginated list of users + isSpotlight: false + code: | + ```kotlin + val users = supabase.auth.admin.retrieveUsers( + page = 1, + perPage = 100 + ) + ``` + - id: create-user + title: 'createUser()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.createUser' + notes: | + Creates a new user. + - To confirm the user's email address or phone number, set `autoConfirm` to true. Both arguments default to false. + examples: + - id: create-a-new-user-with-email-custom-user-metadata + name: Create user with email + isSpotlight: true + code: | + ```kotlin + val userWithEmail = supabase.auth.admin.createUserWithEmail { + email = "example@email.com" + password = "secretpassword" + userMetadata { + put("name", "John") + } + } + ``` + - id: create-a-new-user-with-phone-custom-user-metadata + name: Create user with phone + isSpotlight: true + code: | + ```kotlin + val userWithPhone = supabase.auth.admin.createUserWithPhone { + phone = "+49123456789" + password = "secretpassword" + userMetadata { + put("name", "John") + } + } + ``` + - id: auto-confirm-the-users-email + name: Auto-confirm the user's email + code: | + ```kotlin + val userWithEmail = supabase.auth.admin.createUserWithEmail { + email = "example@email.com" + password = "secretpassword" + autoConfirm = true + } + ``` + - id: auto-confirm-the-users-phone-number + name: Auto-confirm the user's phone number + code: | + ```kotlin + val userWithPhone = supabase.auth.admin.createUserWithPhone { + phone = "+49123456789" + password = "secretpassword" + autoConfirm = true + } + ``` + - id: delete-user + title: 'deleteUser()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.deleteUser' + notes: | + Deletes a user from the database. + - The `deleteUser()` method requires the user's ID, which maps to the `auth.users.id` column. + examples: + - id: removes-a-user + name: Removes a user + isSpotlight: true + code: | + ```kotlin + supabase.auth.admin.deleteUser(uid = "uid") + ``` + + - id: invite-user-by-email + title: 'inviteUserByEmail()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.inviteUserByEmail' + notes: | + Sends an invite link to the user's email address. + examples: + - id: invite-a-user + name: Invite a user + isSpotlight: true + code: | + ```kotlin + supabase.auth.admin.inviteUserByEmail( + email = "example@email.com", + //optional: + redirectTo = "https://example.com/redirect", + data = buildJsonObject { + put("custom", "value") + } + ) + ``` + + - id: generate-link + title: 'generateLink()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.generateLink' + notes: | + Generates email links and OTPs to be sent via a custom email provider. + examples: + - id: generate-a-signup-link + name: Generate a signup link + isSpotlight: true + code: | + ```kotlin + val (url, user) = supabase.auth.admin.generateLinkFor(LinkType.Signup) { + email = "example@email.com" + password = "secretpassword" + } + ``` + - id: generate-an-invite-link + name: Generate an invite link + isSpotlight: false + code: | + ```kotlin + val (url, user) = supabase.auth.admin.generateLinkFor(LinkType.Invite) { + email = "example@email.com" + } + ``` + - id: generate-a-magic-link + name: Generate a magic link + isSpotlight: false + code: | + ```kotlin + val (url, user) = supabase.auth.admin.generateLinkFor(LinkType.MagicLink) { + email = "example@email.com" + } + ``` + - id: generate-a-recovery-link + name: Generate a recovery link + isSpotlight: false + code: | + ```kotlin + val (url, user) = supabase.auth.admin.generateLinkFor(LinkType.Recovery) { + email = "example@email.com" + } + ``` + - id: generate-links-to-change-current-email-address + name: Generate links to change current email address + isSpotlight: false + code: | + ```kotlin + // generate an email change link to be sent to the current email address + val (url, user) = supabase.auth.admin.generateLinkFor(LinkType.EmailChangeCurrent) { + email = "example@email.com" + newEmail = "newEmail@email.com" + } + + // generate an email change link to be sent to the new email address + val (url, user) = supabase.auth.admin.generateLinkFor(LinkType.EmailChangeNew) { + email = "example@email.com" + newEmail = "newEmail@email.com" + } + ``` + + - id: update-user-by-id + title: 'updateUserById()' + $ref: '@supabase/gotrue-js.GoTrueAdminApi.updateUserById' + notes: | + Updates the user data. + examples: + - id: updates-a-users-email + name: Updates a user's email + isSpotlight: false + code: | + ```kotlin + supabase.auth.admin.updateUserById(uid = "id") { + email = "example@email.com" + } + ``` + - id: updates-a-users-password + name: Updates a user's password + isSpotlight: false + code: | + ```js + supabase.auth.admin.updateUserById(uid = "id") { + password = "password" + } + ``` + - id: updates-a-users-metadata + name: Updates a user's metadata + isSpotlight: true + code: | + ```kotlin + supabase.auth.admin.updateUserById(uid = "id") { + userMetadata = buildJsonObject { + put("key", "value") + } + } + ``` + - id: updates-a-users-app-metadata + name: Updates a user's app_metadata + isSpotlight: false + code: | + ```kotlin + supabase.auth.admin.updateUserById(uid = "id") { + appMetadata = buildJsonObject { + put("key", "value") + } + } + ``` + - id: confirms-a-users-email-address + name: Confirms a user's email address + isSpotlight: false + code: | + ```kotlin + supabase.auth.admin.updateUserById(uid = "id") { + emailConfirm = true + } + ``` + - id: confirms-a-users-phone-number + name: Confirms a user's phone number + isSpotlight: false + code: | + ```kotlin + supabase.auth.admin.updateUserById(uid = "id") { + phoneConfirm = true + } + ``` + - id: mfa-list-factors + title: 'mfa.listFactors()' + notes: | + Lists all factors associated to a user. + $ref: '@supabase/gotrue-js.GoTrueAdminMFAApi.listFactors' + examples: + - id: list-factors + name: List all factors for a user + isSpotlight: true + code: | + ```kotlin + const factors = supabase.auth.admin.retrieveFactors(uid = "id") + ``` + - id: mfa-delete-factor + title: 'mfa.deleteFactor()' + $ref: '@supabase/gotrue-js.GoTrueAdminMFAApi.deleteFactor' + notes: | + Deletes a factor on a user. This will log the user out of all active sessions if the deleted factor was verified. + examples: + - id: delete-factor + name: Delete a factor for a user + isSpotlight: true + code: | + ```kotlin + supabase.auth.admin.deleteFactor(uid = "id", factorId = "factor_id") + ``` + - id: invoke + title: 'invoke()' + description: | + Invokes a Supabase Function. See the [guide](/docs/guides/functions) for details on writing Functions. + - When invoking a function with parameters, you have to provide a [serializable value](/docs/reference/kotlin/installing#serialization) in the function parameter. + notes: | + - Requires an Authorization header. + examples: + - id: basic-invocation + name: Basic invocation + isSpotlight: true + code: | + ```kotlin + supabase.functions.invoke("function_name") + ``` + - id: basic-invocation-with-body + name: Basic invocation with body + isSpotlight: true + code: | + ```kotlin + supabase.functions.invoke( + function = "function_name", + body = buildJsonObject { + put("foo", "bar") + }, + headers = Headers.build { + append(HttpHeaders.ContentType, "application/json") + } + ) + ``` + - id: reuse-function + name: Reuse function by saving it to a variable + isSpotlight: true + code: | + ```kotlin + val function = supabase.functions.buildEdgeFunction( + function = "function", + headers = Headers.build { + /*Default headers*/ + //when you are sending a body you may want to add this header: + append(HttpHeaders.ContentType, "application/json") + } + ) + //invoke it: + function() + //invoke it with a body: + function(body) + //invoke it with custom request options: + function(body) { + header("Header", "Value") + parameter("Key", "Value") //url parameter + } + ``` + - id: subscribe + description: | + Subscribe to realtime changes in your database. + title: 'on().subscribe()' + notes: | + - Realtime is disabled by default for new Projects for better database performance and security. You can turn it on by [managing replication](/docs/guides/database/api#managing-realtime). + - If you want to receive the "previous" data for updates and deletes, you will need to set `REPLICA IDENTITY` to `FULL`, like this: `ALTER TABLE your_table REPLICA IDENTITY FULL;` + - When using a method with a generic type like `track`, `broadcast` or `broadcastFlow`, you have to provide a [serializable class](/docs/reference/kotlin/installing#serialization) as the type parameter. + examples: + - id: liste-to-broadcasts + name: Listen to broadcasts + code: | + ```kotlin + @Serializable + data class Message(val content: String, val sender: String) + + val channel = supabase.channel("channelId") { + //optional config + } + + val broadcastFlow = channel.broadcastFlow(event = "message") + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + broadcastFlow.collect { //it: Message + println(it) + } + + channel.subscribe(blockUntilSubscribed = true) + + channel.broadcast(event = "message", Message("I joined!", "John")) + ``` + - id: listen-to-presence-updates + name: Listen to presence updates + code: | + ```kotlin + @Serializable + data class PresenceState(val username: String) + + val connectedUsers = mutableSetOf() + val channel = supabase.channel("channelId") { + //optional config + } + + val presenceChangeFlow = channel.presenceChangeFlow() + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + presenceChangeFlow.collect { + connectedUsers += it.decodeJoinsAs() + connectedUsers -= it.decodeLeavesAs() + } + + channel.subscribe(blockUntilSubscribed = true) + //send own state + channel.track(PresenceState(username = "John")) + - id: listen-to-all-database-changes + name: Listen to all database changes + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + val changeFlow = channel.postgresChangeFlow(schema = "public") + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + 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() + ``` + - id: listen-to-a-specific-table + name: Listen to a specific table + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + val changeFlow = channel.postgresChangeFlow(schema = "public") { + table = "users" + } + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + 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() + ``` + - id: listen-to-inserts + name: Listen to inserts + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + val changeFlow = channel.postgresChangeFlow(schema = "public") { + table = "users" + } + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + changeFlow.collect { + println(it.record) + } + + channel.subscribe() + ``` + - id: listen-to-updates + name: Listen to updates + description: | + By default, Supabase will send only the updated record. If you want to receive the previous values as well you can + enable full replication for the table you are listening too: + + ```sql + alter table "your_table" replica identity full; + ``` + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + val changeFlow = channel.postgresChangeFlow(schema = "public") { + table = "users" + } + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + changeFlow.collect { + println(it.record) + println(it.oldRecord) + } + + channel.subscribe() + ``` + - id: listen-to-deletes + name: Listen to deletes + description: | + By default, Supabase does not send deleted records. If you want to receive the deleted record you can + enable full replication for the table you are listening too: + + ```sql + alter table "your_table" replica identity full; + ``` + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + val changeFlow = channel.postgresChangeFlow(schema = "public") { + table = "users" + } + + //in a new coroutine (or use Flow.onEach().launchIn(scope)): + changeFlow.collect { + println(it.oldRecord) + } + + channel.subscribe() + ``` + - id: listening-to-row-level-changes + name: Listen to row level changes + description: You can listen to individual rows using the format `{table}:{col}=eq.{val}` - where `{col}` is the column name, and `{val}` is the value which you want to match. + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + val changeFlow = channel.postgresChangeFlow(schema = "public") { + table = "users" + filter = "id=eq.1" + } + + //in a new coroutine: + changeFlow.collect { + println(it.oldRecord) + } + + channel.subscribe() + ``` + + - id: remove-channel + description: | + Unsubscribes and removes Realtime channel from Realtime client. + title: 'removeChannel()' + notes: | + - Removing a channel is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. + - Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. + examples: + - id: removes-a-channel + name: Remove a channel + isSpotlight: true + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + //... + supabase.realtime.removeChannel(channel) + ``` + - id: unsubscribe-channel + name: Unsubscribe from a channel + isSpotlight: true + code: | + ```kotlin + val channel = supabase.channel("channelId") { + //optional config + } + //... + channel.unsubscribe() + ``` + - id: remove-all-channels + title: removeAllChannels() + $ref: '@supabase/supabase-js.index.SupabaseClient.removeAllChannels' + notes: | + Unsubscribes and removes all Realtime channels from Realtime client. + - Removing channels is a great way to maintain the performance of your project's Realtime service as well as your database if you're listening to Postgres changes. Supabase will automatically handle cleanup 30 seconds after a client is disconnected, but unused channels may cause degradation as more clients are simultaneously subscribed. + examples: + - id: remove-all-channels + name: Remove all channels + isSpotlight: true + code: | + ```kotlin + supabase.realtime.removeAllChannels() + ``` + - id: get-channels + title: getChannels() + $ref: '@supabase/supabase-js.index.SupabaseClient.getChannels' + notes: | + Returns all Realtime channels. + examples: + - id: get-all-channels + name: Get all channels + isSpotlight: true + code: | + ```kotlin + val channels = supabase.realtime.subscriptions.entries + ``` + - id: list-buckets + title: listBuckets() + $ref: '@supabase/storage-js.packages/StorageBucketApi.default.listBuckets' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: `select` + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: list-buckets + name: List buckets + isSpotlight: true + code: | + ```kotlin + val buckets = supabase.storage.retrieveBuckets() + ``` + + - id: get-bucket + title: getBucket() + $ref: '@supabase/storage-js.packages/StorageBucketApi.default.getBucket' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: `select` + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: get-bucket + name: Get bucket + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.retrieveBucketById(bucketId = "avatars") + ``` + + - id: create-bucket + title: createBucket() + $ref: '@supabase/storage-js.packages/StorageBucketApi.default.createBucket' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: `insert` + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: create-bucket + name: Create bucket + isSpotlight: true + code: | + ```kotlin + supabase.storage.createBucket(name = "icons", id = "icons") { + public = true + fileSizeLimit = 5.megabytes + } + ``` + - id: update-bucket + title: updateBucket() + $ref: '@supabase/storage-js.packages/StorageBucketApi.default.updateBucket' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: `select` and `update` + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: update-bucket + name: Update bucket + isSpotlight: true + code: | + ```kotlin + supabase.storage.updateBucket("cards") { + public = false + fileSizeLimit = 20.megabytes + allowedMimeTypes(ContentType.Image.PNG, ContentType.Image.JPEG) + } + ``` + + - id: empty-bucket + title: emptyBucket() + $ref: '@supabase/storage-js.packages/StorageBucketApi.default.emptyBucket' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: `select` + - `objects` table permissions: `select` and `delete` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: empty-bucket + name: Empty bucket + isSpotlight: true + code: | + ```kotlin + supabase.storage.emptyBucket(bucketId = "icons") + ``` + - id: delete-bucket + title: deleteBucket() + $ref: '@supabase/storage-js.packages/StorageBucketApi.default.deleteBucket' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: `select` and `delete` + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: delete-bucket + name: Delete bucket + isSpotlight: true + code: | + ```kotlin + supabase.storage.deleteBucket(bucketId = "icons") + ``` + + - id: from-upload + title: from.upload() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.upload' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `insert` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + - Resumable uploads use a `Disk` cache by default to store the upload urls. You can customize that in the Auth config by changing the `resumable.cache` property. + examples: + - id: upload-file + name: Upload file + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + bucket.upload("myIcon.png", byteArray, upsert = false) + //on JVM you can use java.io.File + bucket.upload("myIcon.png", file, upsert = false) + ``` + - id: upload-file-with-progress + name: Upload file with progress + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + bucket.uploadAsFlow("test.png", byteArrayOf()).collect { + when(it) { + is UploadStatus.Progress -> println("Progress: ${it.totalBytesSend.toFloat() / it.contentLength * 100}%") + is UploadStatus.Success -> println("Success") + } + } + ``` + - id: create-resumable-upload + name: Create resumable upload + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + //JVM/Android: + val upload = bucket.resumable.createOrContinueUpload("icon.png", File("icon.png")) + //Other platforms: + val upload = bucket.resumable.createOrContinueUpload(data = byteArray, source = "this is for continuing previous uploads later", path = "icon.png") + val upload = bucket.resumable.createOrContinueUpload( //Probably better to write an extension function + channel = { offset -> /* create ByteReadChannel and seek to offset */ }, + source = "this is for continuing previous uploads later", + size = dataSize, + path = "icon.png" + ) + ``` + - id: start-resumable-upload + name: Start and resumable upload + isSpotlight: true + code: | + ```kotlin + upload.startOrResumeUploading() + ``` + - id: pause-resumable-upload + name: Pause resumable upload + code: | + ```kotlin + upload.pause() + ``` + - id: cancel-resumable-upload + name: Cancel resumable upload + code: | + ```kotlin + upload.cancel() + ``` + description: | + This will also remove the upload url from the cache + - id: listen-to-upload-state + name: Listen to the resumable upload state + code: | + ```kotlin + upload.stateFlow.collect { + println("Progress: ${it.progress * 100}%") + println("Paused: ${it.paused}") + println("Is done: ${it.isDone}") + } + ``` + - id: continue-previous-upload + name: Continue previous uploads + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + + //only on JVM/Android: + bucket.resumable.continuePreviousFileUploads() + .map { it.await() } //await all uploads. This just makes sure the uploads have an update-to-date url. You can also do this in parallel + .forEach { upload -> + upload.startOrResumeUploading() + } + + //on other platforms you may have to continue uploads from the source (Probably better to write an extension function): + bucket.resumable.continuePreviousUploads { source, offset -> + //create ByteReadChannel from source and seek to offset + } + .map { it.await() } //await all uploads. This just makes sure the uploads have an update-to-date url. You can also do this in parallel + .forEach { upload -> + upload.startOrResumeUploading() + } + ``` + + - id: from-update + title: from.update() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.update' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `update` and `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: update-file + name: Update file + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + bucket.update("myIcon.png", byteArray, upsert = false) + //on JVM you can use java.io.File + bucket.update("myIcon.png", file, upsert = false) + ``` + + - id: from-move + title: from.move() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.move' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `update` and `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: move-file + name: Move file + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + bucket.move("icon1.png", "icon2.png") + ``` + - id: from-copy + title: from.copy() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.copy' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `insert` and `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: copy-file + name: Copy file + isSpotlight: true + code: | + ```kotlin + supabase.storage.from("test").copy(from = "avatar.png", to = "avatar2.png") + ``` + - id: from-create-signed-url + title: from.createSignedUrl() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.createSignedUrl' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: create-signed-url + name: Create Signed URL + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + val url = bucket.createSignedUrl(path = "icon.png", expiresIn = 3.minutes) + ``` + - id: create-signed-url-with-transformation + name: Create Signed URL with transformation + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + val url = bucket.createSignedUrl(path = "icon.png", expiresIn = 3.minutes) { + size(100, 100) + fill() + quality = 80 + } + ``` + - id: from-create-signed-urls + title: from.createSignedUrls() + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: create-signed-urls + name: Create Signed URLs + isSpotlight: true + code: | + ```kotlin + val urls = supabase.storage.from("avatars").createSignedUrls(20.minutes, "avata1.jpg", "avatar2.jpg") + ``` + - id: from-create-signed-upload-url + title: from.createSignedUploadUrl() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.createSignedUploadUrl' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `insert` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: create-signed-upload-url + name: Create Signed Upload URL + isSpotlight: true + code: | + ```kotlin + val url = supabase.storage.from("avatars").createSignedUploadUrl("avatar.png") + ``` + - id: from-upload-to-signed-url + title: from.uploadToSignedUrl() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.uploadToSignedUrl' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: upload-to-signed-url + name: Upload to a signed URL + isSpotlight: true + code: | + ```kotlin + supabase.storage.from("avatars").uploadToSignedUrl(path = "avatar.jpg", token = "token-from-createSignedUploadUrl", data = bytes) + //or on JVM: + supabase.storage.from("avatars").uploadToSignedUrl(path = "avatar.jpg", token = "token-from-createSignedUploadUrl", file = File("avatar.jpg")) + ``` + - id: from-get-public-url + title: from.getPublicUrl() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.getPublicUrl' + notes: | + - The bucket needs to be set to public, either via [updateBucket()](/docs/reference/kotlin/storage-updatebucket) or by going to Storage on [supabase.com/dashboard](https://supabase.com/dashboard), clicking the overflow menu on a bucket and choosing "Make public" + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: none + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: returns-the-url-for-an-asset-in-a-public-bucket + name: Returns the URL for an asset in a public bucket + isSpotlight: true + code: | + ```kotlin + val url = supabase.storage.from("public-bucket").publicUrl("folder/avatar1.png") + ``` + - id: transform-asset-in-public-bucket + name: Returns the URL for an asset in a public bucket with transformations + isSpotlight: true + code: | + ```kotlin + val url = supabase.storage.from("public-bucket").publicRenderUrl("folder/avatar1.png") { + size(100, 100) + } + ``` + + - id: from-download + title: from.download() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.download' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: download-file-authenticated + name: Download file from non-public bucket + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + val bytes = bucket.downloadAuthenticated("test.png") + //or on JVM: + bucket.downloadAuthenticatedTo("test.png", File("test.png")) + ``` + - id: download-file-public + name: Download file from public bucket + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + val bytes = bucket.downloadPublic("test.png") + //or on JVM: + bucket.downloadPublicTo("test.png", File("test.png")) + ``` + - id: download-with-transformation + name: Download file with transformation + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + val bytes = bucket.downloadPublic("test.png") { + size(100, 100) + fill() + quality = 100 + } + //or on JVM: + bucket.downloadPublicTo("test.png", File("test.png")) { + size(100, 100) + fill() + quality = 100 + } + ``` + - id: download-with-progress + name: Download file with progress + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + bucket.downloadAuthenticatedAsFlow("icon.png").collect { + when(it) { + is DownloadStatus.ByteData -> println("Downloaded ${it.data.size} bytes") + is DownloadStatus.Progress -> println("Downloaded ${it.totalBytesReceived.toFloat() / it.contentLength * 100}%") + DownloadStatus.Success -> println("Downloaded successfully") + } + } + //or on JVM: + bucket.downloadAuthenticatedToAsFlow("icon.png", File("icon.png")).collect { + when(it) { + is DownloadStatus.Progress -> println("Downloaded ${it.totalBytesReceived.toFloat() / it.contentLength * 100}%") + DownloadStatus.Success -> println("Downloaded successfully") + else -> {} //The ByteData status will never occur as we are writing directly to a file + } + } + ``` + - id: from-remove + title: from.remove() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.remove' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `delete` and `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: delete-file + name: Delete file + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + bucket.delete("test.png", "test2.png") + ``` + + - id: from-list + title: from.list() + $ref: '@supabase/storage-js.packages/StorageFileApi.default.list' + notes: | + - RLS policy permissions required: + - `buckets` table permissions: none + - `objects` table permissions: `select` + - Refer to the [Storage guide](/docs/guides/storage/security/access-control) on how access control works + examples: + - id: list-files-in-a-bucket + name: List files in a bucket + isSpotlight: true + code: | + ```kotlin + val bucket = supabase.storage.from("avatars") + val files = bucket.list() + ```