Home

Framework Quickstarts

Use Supabase with Android Kotlin

Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.

1

Set up a Supabase project

Create a new project in the Supabase Dashboard.

After your project is ready, create a table in your Supabase database using the SQL Editor in the Dashboard. Use the following SQL statement to create all tables.

SQL_EDITOR

_13
-- Create products table
_13
create table
_13
public.products (
_13
_id bigint generated by default as identity not null,
_13
productid text not null,
_13
name text null,
_13
description text null,
_13
price real null,
_13
image text null,
_13
category text null,
_13
nutrition text null,
_13
constraint products_pkey primary key (productid),
_13
) tablespace pg_default;

2

Create an Android app with Android Studio

Open Android Studio > New > New Android Project.

3

Install the Supabase client library

Import Supabase and all required dependencies. Replace the version placeholders $supabase_version and $ktor_version with the respective latest versions.


_10
implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
_10
implementation "io.ktor:ktor-client-android:$ktor_version"

4

Install the serializable plugin

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.


_10
plugins {
_10
...
_10
id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlin_version'
_10
...
_10
}

5

Initialize the Supabase client

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.


_10
val client = createSupabaseClient(
_10
supabaseUrl = "https://xyzcompany.supabase.co",
_10
supabaseKey = "public-anon-key"
_10
) {
_10
install(Postgrest)
_10
}

6

Create a data transfer object


_19
@Serializable
_19
data class ProductDto(
_19
@SerialName("productid")
_19
val productId: String,
_19
@SerialName("name")
_19
val name: String,
_19
@SerialName("description")
_19
val description: String,
_19
@SerialName("price")
_19
val price: Double,
_19
@SerialName("image")
_19
val image: String,
_19
@SerialName("category")
_19
val category: String,
_19
@SerialName("nutrition")
_19
val nutrition: String,
_19
@SerialName("_id")
_19
val _id: Int,
_19
)

7

Create a domain object

This kind of object will be consumed by the view.


_10
data class Product(
_10
val productId: String,
_10
val name: String,
_10
val description: String,
_10
val price: Double,
_10
val image: String,
_10
val category: String,
_10
val nutrition: String,
_10
val _id: Int,
_10
)

8

Query data from the app

Create a repository to interact with the data source.


_14
interface ProductRepository {
_14
fun getProducts(): List<ProductDto>
_14
}
_14
_14
class ProductRepositoryImpl @Inject constructor(
_14
private val postgrest: Postgrest,
_14
) : ProductRepository {
_14
override suspend fun getProducts(): List<ProductDto> {
_14
val result = client.postgrest["products"]
_14
.select().decodeList<ProductDto>()
_14
// Handle result data for next step
_14
return result
_14
}
_14
}

9

Create a module to provide repository

Use Hilt for dependency injection.


_10
InstallIn(SingletonComponent::class)
_10
@Module
_10
abstract class RepositoryModule {
_10
@Binds
_10
abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository
_10
}

10

Get data from ViewModel inside a coroutine scope

Add the @Inject annotation to use the repository in a ViewModel.


_30
class ProductListViewModel @Inject constructor(
_30
private val productRepository: ProductRepository
_30
) : ViewModel() {
_30
_30
private val _productList = MutableStateFlow<List<Product>?>(listOf())
_30
val productList: Flow<List<Product>?> = _productList
_30
_30
init {
_30
getProducts()
_30
}
_30
_30
fun getProducts() {
_30
viewModelScope.launch {
_30
val products = productRepository.getProducts()
_30
_productList.emit(products?.map { it -> it.asDomainModel() })
_30
}
_30
}
_30
_30
private fun ProductDto.asDomainModel(): Product {
_30
return Product(
_30
productId = this.productId,
_30
name = this.name,
_30
price = this.price,
_30
image = this.image,
_30
description = this.description,
_30
category = this.category,
_30
nutrition = this.nutrition,
_30
_id = this._id
_30
)
_30
}

11

Observe data in a Composable


_28
@Composable
_28
fun ProductListScreen(
_28
modifier: Modifier = Modifier,
_28
navController: NavController,
_28
viewModel: ProductListViewModel = hiltViewModel(),
_28
) {
_28
val productList = viewModel.productList.collectAsState(initial = listOf()).value
_28
if (!productList.isNullOrEmpty()) {
_28
LazyColumn(
_28
modifier = modifier.padding(24.dp),
_28
contentPadding = PaddingValues(5.dp)
_28
) {
_28
items(productList) { item ->
_28
ProductListItem(
_28
product = item,
_28
modifier = modifier,
_28
onClick = {
_28
navController.navigate(
_28
ProductDetailsDestination.createRouteWithParam(
_28
item.id
_28
)
_28
)
_28
},
_28
)
_28
}
_28
}
_28
}
_28
}

12

Start the app