Skip to main content

Overview

Divvy is built as a modern Android application using Kotlin and Jetpack Compose with a clean MVVM architecture. The app simplifies group expense splitting by bringing it to the moment of payment through bank integration and receipt scanning.

Technology Stack

Frontend

  • Kotlin - Primary language
  • Jetpack Compose - Modern UI toolkit
  • Material 3 - Design system
  • Navigation Compose - Type-safe navigation

Backend & Data

  • Supabase - Database and authentication
  • Postgrest - Database queries
  • GoTrue - Auth provider
  • Plaid - Bank integration (planned)

Dependency Injection

  • Hilt - DI framework
  • Dagger - Compile-time DI

Camera & Media

  • CameraX - Receipt scanning
  • Coil - Image loading

Project Structure

The Divvy codebase follows a feature-based organization within the MVVM pattern:
app/src/main/java/com/example/divvy/
├── backend/          # Repositories and data access layer
├── ui/               # UI layer organized by feature
│   ├── analytics/
│   ├── assignitems/
│   ├── auth/
│   ├── expenses/
│   ├── friends/
│   ├── groupdetail/
│   ├── groups/
│   ├── home/
│   ├── ledger/
│   ├── profile/
│   ├── scanreceipt/
│   ├── splitexpense/
│   ├── splitpercentage/
│   ├── navigation/   # Navigation graph and routes
│   └── theme/        # Material 3 theming
├── models/           # Domain models
├── components/       # Reusable UI components
└── di/               # Dependency injection modules
Each feature in the ui/ directory typically contains ViewModels/ and Views/ subdirectories, maintaining clear separation between business logic and presentation.

Data Flow Architecture

Divvy implements a unidirectional data flow pattern:
1

User Interaction

User interacts with Compose UI components (buttons, text fields, etc.)
2

ViewModel Processing

UI events are handled by ViewModels which contain business logic
3

Repository Layer

ViewModels call repository methods for data operations
4

Backend Communication

Repositories interact with Supabase through the SupabaseClient
5

State Updates

Data flows back through StateFlow/Flow emissions
6

UI Recomposition

Compose UI automatically recomposes when state changes

Dependency Injection Setup

Divvy uses Hilt for dependency injection with two main modules:

NetworkModule

Provides the Supabase client configuration:
app/src/main/java/com/example/divvy/di/NetworkModule.kt
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

    @Provides
    @Singleton
    fun provideSupabaseClient(): SupabaseClient {
        val client = createSupabaseClient(
            supabaseUrl = BuildConfig.SUPABASE_URL,
            supabaseKey = BuildConfig.SUPABASE_ANON_KEY
        ) {
            defaultSerializer = KotlinXSerializer(Json { ignoreUnknownKeys = true })
            install(Auth) {
                scheme = "com.example.divvy"
                host = "auth"
                defaultExternalAuthAction = ExternalAuthAction.CUSTOM_TABS
            }
            install(Postgrest)
        }
        SupabaseClientProvider.setClient(client)
        return client
    }
}

AppModule

Binds repository interfaces to their Supabase implementations:
app/src/main/java/com/example/divvy/di/AppModule.kt
@Module
@InstallIn(SingletonComponent::class)
abstract class AppModule {
    @Binds @Singleton abstract fun bindAuthRepository(
        impl: SupabaseAuthRepository
    ): AuthRepository
    
    @Binds @Singleton abstract fun bindGroupRepository(
        impl: SupabaseGroupRepository
    ): GroupRepository
    
    @Binds @Singleton abstract fun bindMemberRepository(
        impl: SupabaseMemberRepository
    ): MemberRepository
    
    @Binds @Singleton abstract fun bindBalanceRepository(
        impl: SupabaseBalanceRepository
    ): BalanceRepository
    
    @Binds @Singleton abstract fun bindExpensesRepository(
        impl: SupabaseExpensesRepository
    ): ExpensesRepository
    
    @Binds @Singleton abstract fun bindProfilesRepository(
        impl: SupabaseProfilesRepository
    ): ProfilesRepository
    
    @Binds @Singleton abstract fun bindActivityRepository(
        impl: SupabaseActivityRepository
    ): ActivityRepository
}
All repositories are provided as singletons to ensure consistent state across the app and efficient resource usage.

Build Configuration

Key build configuration from app/build.gradle.kts:
android {
    namespace = "com.example.divvy"
    compileSdk = 36

    defaultConfig {
        applicationId = "com.example.divvy"
        minSdk = 26
        targetSdk = 36
        versionCode = 1
        versionName = "1.0"
    }
    
    buildFeatures {
        compose = true
        buildConfig = true
    }
}

Environment Configuration

Divvy uses local.properties for environment-specific configuration:
SUPABASE_URL=https://xxxx.supabase.co
SUPABASE_ANON_KEY=eyJ...
Never commit local.properties to version control. These values are injected at build time using BuildConfig.

Authentication Flow

Supabase Auth is configured with custom URL scheme for OAuth redirects:
  • Redirect URL: com.example.divvy://auth
  • Auth Method: Google OAuth via Custom Tabs
  • Session Management: Handled by Supabase GoTrue SDK
The redirect URL must be configured in Supabase Auth settings to enable Google sign-in.

Next Steps

MVVM Pattern

Learn how ViewModels and Views work together

Navigation

Explore the navigation architecture