# Quickstart: Android (Jetpack Compose)
Source: https://sovaria.in/docs/quickstart-android

Build the notes app with Kotlin and Jetpack Compose: keep the session in encrypted storage, follow sign-in state with auth.changes, and list and add the signed-in user's notes.

> **Warning:** **Release pending:** the Kotlin package is not on Maven Central yet, so the install step won't resolve until it is. Ask for early access on [the early-access page](/early-access) and we'll share the package with your team. The code on this page matches the SDK as it will ship (see [Kotlin SDK](https://sovaria.in/docs/kotlin-reference)).

A Compose app (Android 8, API 26+) where people sign up, sign in, add notes and see only their own.

## Before you start

Do steps 1 to 3 of the [Quickstart](https://sovaria.in/docs/quickstart) first: create a project, copy its **project URL** and **anon key** ([Quickstart](https://sovaria.in/docs/quickstart#connect)), and create the `notes` table with its row rules ([Quickstart](https://sovaria.in/docs/quickstart#table)). Every signed-in user then sees and changes only their own notes, whatever the app code does.

## 1. Create the app and add the dependency

In Android Studio create an **Empty Activity** project (Kotlin, Compose). Add the SDK and AndroidX Security for encrypted storage, and the internet permission.

app/build.gradle.kts:
```kotlin
dependencies {
    implementation("in.sovaria:sovaria:1.0.0")
    implementation("androidx.security:security-crypto:1.1.0-alpha06")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
}
```

AndroidManifest.xml:
```text
<uses-permission android:name="android.permission.INTERNET" />
```

## 2. Keep the session in encrypted storage

KeystoreStore.kt:
```kotlin
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import `in`.sovaria.SessionStorage

class KeystoreStore(context: Context) : SessionStorage {
    private val prefs = EncryptedSharedPreferences.create(
        context, "sovaria",
        MasterKey.Builder(context).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
    )
    override suspend fun read(key: String): String? = prefs.getString(key, null)
    override suspend fun write(key: String, value: String) { prefs.edit().putString(key, value).apply() }
    override suspend fun delete(key: String) { prefs.edit().remove(key).apply() }
}
```

## 3. Connect and follow sign-in state

NotesViewModel.kt:
```kotlin
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import `in`.sovaria.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch

class NotesViewModel(app: Application) : AndroidViewModel(app) {
    val signedIn = MutableStateFlow(false)
    val notes = MutableStateFlow<List<Map<String, Any?>>>(emptyList())
    val error = MutableStateFlow("")
    private lateinit var sovaria: Sovaria

    init {
        viewModelScope.launch {
            // Restores the saved session (refreshing it if it's about to expire).
            sovaria = Sovaria.initialize(
                url = "https://<project-ref>.sovarias.in",
                anonKey = "<anon-key>",
                storage = KeystoreStore(app),
            )
            sovaria.auth.changes.collect { state ->      // first event: INITIAL_SESSION
                signedIn.value = state.session != null
                if (signedIn.value) load()
            }
        }
    }

    private fun run(block: suspend () -> Unit) = viewModelScope.launch {
        try { block() } catch (e: SovariaException) { error.value = e.message }
    }

    fun signIn(email: String, password: String, create: Boolean) = run {
        if (create) sovaria.auth.signUp(email, password) else sovaria.auth.signInWithPassword(email, password)
    }

    // Rows come back keyed by column name. The row rules add "where owner_id = auth.uid()".
    private suspend fun load() { notes.value = sovaria.db.sql("select id, body from notes order by id desc").rows }

    fun add(body: String) = run {
        sovaria.db.sql("insert into notes (body) values (\$1)", listOf(body))
        load()
    }

    fun signOut() = run { sovaria.auth.signOut() }
}
```

## 4. The screens

MainActivity.kt:
```kotlin
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel

class MainActivity : ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent { MaterialTheme { NotesScreen() } }
    }
}

@Composable
fun NotesScreen(vm: NotesViewModel = viewModel()) {
    val signedIn by vm.signedIn.collectAsState()
    val notes by vm.notes.collectAsState()
    val error by vm.error.collectAsState()
    var email by remember { mutableStateOf("") }
    var password by remember { mutableStateOf("") }
    var body by remember { mutableStateOf("") }

    Column(Modifier.padding(24.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
        if (!signedIn) {
            OutlinedTextField(email, { email = it }, label = { Text("Email") })
            OutlinedTextField(password, { password = it }, label = { Text("Password") },
                visualTransformation = PasswordVisualTransformation())
            Button({ vm.signIn(email, password, create = false) }) { Text("Sign in") }
            TextButton({ vm.signIn(email, password, create = true) }) { Text("Create account") }
        } else {
            Row {
                OutlinedTextField(body, { body = it }, Modifier.weight(1f), placeholder = { Text("Write a note") })
                Button({ vm.add(body); body = "" }) { Text("Add") }
            }
            LazyColumn(Modifier.weight(1f)) { items(notes) { Text(it["body"]?.toString() ?: "") } }
            TextButton({ vm.signOut() }) { Text("Sign out") }
        }
        if (error.isNotEmpty()) Text(error, color = MaterialTheme.colorScheme.error)
    }
}
```

In Kotlin strings `$` starts a template, so the SQL placeholder is written `\$1`.

## 5. Run it

Press **Run** with an emulator or device. Create an account, add a note, sign out and back in: the note is still there, and a second account never sees it.

## Next steps

- Check who can read and change `notes` before you ship: [Tutorial: prove your table is private before you ship](https://sovaria.in/docs/tutorial-secure-a-table).
- Push new notes to other devices as they happen: [Database change events](https://sovaria.in/docs/realtime-database-changes).
- Google sign-in via deep links, push and Play Integrity: [Android quickstart (Kotlin)](https://sovaria.in/docs/mobile-kotlin).
- Every type and method: [Kotlin SDK](https://sovaria.in/docs/kotlin-reference).
