Last Week

Supabase Postgres Database is investigated. First table is in. Data classes set up for database. App can now read and write the database.

What does it mean in English?

An app needs to store information. It could store it on device. But in many cases it’s more helpful to store it in the cloud (which is just somebody else’s computer that is always connected to the internet) such that the functionality of the app is not dependent upon one device.

There are many options to store information in the cloud. I am using Supabase’s Postges Database Service. It is a database (think of it as a bunch of linked Excel spreadsheets) that is hosted in the cloud and managed by somebody else. I pay a fee for the privilege to use their service.

Tools used:

Nerdy Details

How did I get my app to talk with the database?

First we create a new table that will have the following columns.

NameTypeDefault
idint8N/A
created_attimestampnow()
userIdtextNULL
userNametestNULL

Then we create a data class that corresponds to this table. Note that Supabase requires it to be serializable for the database to accept. Additionally, the data class does not contain the id or created_at fields because they are auto-generated when the row entry is inserted.

@Serializable
data class HostDetail(
    val userId: String = "",
    val userName: String = "",
)

In our implementation of the repository we add a new function for inserting user row entry. Here TABLE_HOST is just the name for the table that we just created. The argument passed hostDetail is an object of the data class we just created.

override suspend fun insertHostDetail(hostDetail: HostDetail): Result<Unit> = makeApiCall(dispatcher) {
    supabaseClient.from(TABLE_HOST)
        .insert(hostDetail)
}

Recall that makeApiCall() is just a wrapper helper function for exception handling.

suspend fun <T> makeApiCall(
    dispatcher: CoroutineDispatcher,
    call: suspend () -> T
): Result<T> = runCatching {
    withContext(dispatcher) {
        call.invoke()
    }
}

Now we are ready to use it in our ViewModel.

Before we create a row entry in the user table, we will need a unique identifier for the user (note that this is different than the primary key in the user table). In our case we will use the auto-generated UUID that Supabase Auth sent us when the user logged in on the login page. This value is passed to us as userId when the ViewModel is instantiated.

When the page is loaded, we will first attempt to retrieve the user row in the database. Only when we can’t find the entry do we handle the error and create a new one. The error thrown for this particular case is NoSuchElementException.

private suspend fun getHost() {
    hostRepository.retrieveHostDetail(viewState.value.hostDetail.userId)
        .onSuccess { hostDetail ->
            // ...
        }.onFailure { error ->
            when (error) {
                is NoSuchElementException -> handleUserNotFoundError()
            }
        }
}

In our function to handle when a user row is not found, we insert this missing row.

private suspend fun handleUserNotFoundError() {
    val hostDetail = viewState.value.hostDetail
    hostRepository.insertHostDetail(hostDetail)
        .onSuccess {
        	// ...
        }
        .onFailure { error ->
        	// ...
        }
}

It is important to note that I have not implemented any security measures for database access. I will do this at a later date.

Next Week

  • Flush out the UI on host menu list page