SupaCompose

A framework for building android & desktop apps with Supabase

firefox_2022-05-22_13-33-54.mp4

Features

SupaCompose currently supports:

Authentication

Login Signup Verifying (Signup, Password Reset, Invite) Logout Otp
Desktop phone, password, oauth2 phone, password, oauth2 only with token yes no
Android phone, password, oauth2 phone, password, oauth2 only with token, url authentication planned yes planned
Web planned planned planned planned planned

Example code

Authentication with Desktop

suspend fun main() {
    val client = createSupabaseClient {
        supabaseUrl = System.getenv("SUPABASE_URL")
        supabaseKey = System.getenv("SUPABASE_KEY")

        install(Auth)
    }
    application {
        Window(::exitApplication) {
            val session by client.auth.currentSession.collectAsState()
            val scope = rememberCoroutineScope()
            if (session != null) {
                Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
                    Text("Logged in as ${session?.user?.email}")
                }
            } else {
                Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
                    var email by remember { mutableStateOf("") }
                    var password by remember { mutableStateOf("") }
                    Column {
                        TextField(email, { email = it }, placeholder = { Text("Email") })
                        TextField(
                            password,
                            { password = it },
                            placeholder = { Text("Password") },
                            visualTransformation = PasswordVisualTransformation()
                        )
                        Button(onClick = {
                            scope.launch {
                                client.auth.signUpWith(Email) {
                                    this.email = email
                                    this.password = password
                                }
                            }
                        }, modifier = Modifier.align(Alignment.CenterHorizontally)) {
                            Text("Login")
                        }
                        ProviderButton(
                            icon = {
                                Icon(painterResource("discord_icon.svg"), "", modifier = Modifier.size(25.dp))
                            },
                            text = {
                                Text("Log in with Discord")
                            },
                            modifier = Modifier.align(Alignment.CenterHorizontally)
                        ) {
                            scope.launch {
                                client.auth.loginWith(Discord, onFail = {
                                    when (it) {
                                        is OAuthFail.Timeout -> {
                                            println("Timeout")
                                        }
                                        is OAuthFail.Error -> {
                                            //log error
                                        }
                                    }
                                }) {
                                    timeout = 50.seconds
                                    htmlTitle = "SupaCompose"
                                    htmlText = "Logged in. You may continue in the app."
                                }
                            }
                        }
                    }
                }

            }
        }
    }

}
Authentication with Android

When you set the deep link scheme and host in the supabase deeplink plugin and in the android manifest you have to remember to set the additional redirect url in the subabase auth settings. E.g. if you have supacompose as your scheme and login as your host set this to the additional redirect url:

img.png

MainActivity

Note: you should probably use a viewmodel for suspending functions from the SupaCompose library

class MainActivity : AppCompatActivity() {

    val supabaseClient = createSupabaseClient {

        supabaseUrl = "your supabase url"
        supabaseKey = "your supabase key"

        install(Auth)
        install(DeepLinks) {
            scheme = "supacompose"
            host = "login"
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        handleDeepLinks(supabaseClient) //if you don't call this function the library will throw an error when trying to authenticate with oauth
        setContent {
            MaterialTheme {
                val session by supabaseClient.auth.currentSession.collectAsState()
                println(session)
                val scope = rememberCoroutineScope()
                if (session != null) {
                    Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
                        Text("Logged in as ${session?.user?.email}")
                    }
                } else {
                    Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
                        var email by remember { mutableStateOf("") }
                        var password by remember { mutableStateOf("") }
                        Column {
                            TextField(email, { email = it }, placeholder = { Text("Email") })
                            TextField(
                                password,
                                { password = it },
                                placeholder = { Text("Password") },
                                visualTransformation = PasswordVisualTransformation()
                            )
                            Button(onClick = {
                                scope.launch {
                                    supabaseClient.auth.loginWith(Email) {
                                        this.email = email
                                        this.password = password
                                    }
                                }
                            }, modifier = Modifier.align(Alignment.CenterHorizontally)) {
                                Text("Login")
                            }
                            ProviderButton(
                                icon = {
                                    //  Icon(painterResource("discord_icon.svg"), "", modifier = Modifier.size(25.dp))
                                },
                                text = {
                                    Text("Log in with Discord")
                                },
                                modifier = Modifier.align(Alignment.CenterHorizontally)
                            ) {
                                scope.launch {
                                    supabaseClient.auth.loginWith(Discord, onFail = {
                                        when (it) {
                                            is AuthFail.Timeout -> {
                                                println("Timeout")
                                            }
                                            is AuthFail.Error -> {
                                                //log error
                                            }
                                        }
                                    })
                                }
                            }
                        }
                    }
                }
            }
        }
    }

}
AndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.github.jan.supacompose.android">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
            android:allowBackup="false"
            android:supportsRtl="true"
            android:theme="@style/Theme.AppCompat.Light.NoActionBar">
        <activity android:name=".MainActivity" android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
                <action android:name="android.intent.action.VIEW"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <!-- This is important for deeplinks. -->
                <data android:scheme="supacompose"
                      android:host="login"/>
            </intent-filter>
        </activity>
    </application>
</manifest>

GitHub

View Github