Divvy uses the Repository Pattern to abstract data access logic from the UI and business logic layers. All repositories follow a consistent interface-based design with Supabase implementations.
Each repository is defined as a Kotlin interface that declares the contract for data operations:
interface GroupRepository { fun listGroups(): Flow<DataResult<List<Group>>> fun getGroup(groupId: String): Flow<Group> suspend fun createGroup(name: String, icon: GroupIcon): Group suspend fun updateGroup(groupId: String, name: String, icon: GroupIcon) suspend fun deleteGroup(groupId: String) suspend fun refreshGroups()}
sealed class DataResult<out T> { data class Success<T>(val data: T) : DataResult<T>() data class Error(val message: String, val cause: Throwable? = null) : DataResult<Nothing>() data object Loading : DataResult<Nothing>()}
Repositories return DataResult for operations that may fail:
val groups: Flow<DataResult<List<Group>>> = groupRepository.listGroups()groups.collect { result -> when (result) { is DataResult.Loading -> showLoadingSpinner() is DataResult.Success -> displayGroups(result.data) is DataResult.Error -> showError(result.message) }}
when (result) { is DataResult.Loading -> { /* Show loading UI */ } is DataResult.Success -> { /* Display data */ } is DataResult.Error -> { /* Show error message */ }}
Update local state optimistically, then sync with backend:
override suspend fun deleteGroup(groupId: String) { // Call backend supabaseClient.postgrest.rpc("delete_group_cascade", params) // Update local state _groups.update { result -> val current = (result as? DataResult.Success)?.data ?: return@update result DataResult.Success(current.filter { g -> g.id != groupId }) }}