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.
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.
Create an Android app with Android Studio
Open Android Studio > New > New Android Project.
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"
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 }
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.
_10val client = createSupabaseClient(_10 supabaseUrl = "https://xyzcompany.supabase.co",_10 supabaseKey = "public-anon-key"_10 ) {_10 install(Postgrest)_10}
Create a data transfer object
_19@Serializable_19data 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)
Create a domain object
This kind of object will be consumed by the view.
_10data 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)
Query data from the app
Create a repository to interact with the data source.
_14interface ProductRepository {_14 fun getProducts(): List<ProductDto>_14}_14_14class 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}
Create a module to provide repository
Use Hilt for dependency injection.
_10InstallIn(SingletonComponent::class)_10@Module_10abstract class RepositoryModule {_10 @Binds_10 abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository_10}
Get data from ViewModel inside a coroutine scope
Add the @Inject
annotation to use the repository in a ViewModel.
_30class 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}
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 }