Skip to main content

Overview

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.

Architecture

Interface-Based Abstractions

Each repository is defined as a Kotlin interface that declares the contract for data operations:

Supabase Implementations

Each interface has a corresponding Supabase implementation:
  • GroupRepositorySupabaseGroupRepository
  • ExpensesRepositorySupabaseExpensesRepository
  • AuthRepositorySupabaseAuthRepository
  • ProfilesRepositorySupabaseProfilesRepository
Implementations are marked with @Singleton and injected via Hilt:

DataResult Wrapper

Divvy uses a DataResult sealed class to handle loading states, success, and errors uniformly across the app.

Definition

Source: backend/DataResult.kt:3

Usage Pattern

Repositories return DataResult for operations that may fail:

Error Handling

Try-Catch Pattern

Repository implementations wrap Supabase calls in try-catch blocks:

Error States

Repositories maintain internal MutableStateFlow with DataResult states:

Dependency Injection with Hilt

Repository Binding

Repositories are injected as singletons:

ViewModel Injection

ViewModels receive repository instances via constructor injection:

Reactive Data with Flow

Observing Data Changes

Repositories expose Flow<DataResult<T>> for reactive UI updates:

Data Refresh

Repositories provide explicit refresh methods:

Suspend Functions

All data mutations use Kotlin coroutines with suspend functions:
This ensures operations are non-blocking and can be safely called from coroutine scopes.

Repository List

GroupRepository

Manage groups, members, and group metadata

ExpensesRepository

Create, update, and track expenses with splits

AuthRepository

Handle user authentication and sessions

ProfilesRepository

Manage user profiles and preferences

Best Practices

1. Always Use Interfaces

Depend on interfaces, not concrete implementations:

2. Handle All DataResult States

Always handle Loading, Success, and Error states:

3. Use Coroutine Scopes

Call suspend functions from appropriate coroutine scopes:

4. Local State Updates

Update local state optimistically, then sync with backend: