> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/vivekbw/divvy/llms.txt
> Use this file to discover all available pages before exploring further.

# GroupRepository

> Repository for managing groups, members, and group metadata in Divvy

## Overview

`GroupRepository` handles all group-related data operations including creating groups, fetching group lists, updating group details, and deleting groups.

**Interface**: `backend/GroupRepository.kt:12`\
**Implementation**: `backend/SupabaseGroupRepository.kt:45`

## Interface Definition

```kotlin theme={null}
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()
}
```

## Implementation

### SupabaseGroupRepository

```kotlin theme={null}
@Singleton
class SupabaseGroupRepository @Inject constructor(
    private val supabaseClient: SupabaseClient
) : GroupRepository {
    private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
    private val _groups = MutableStateFlow<DataResult<List<Group>>>(DataResult.Loading)
    
    init {
        scope.launch { refreshGroups() }
    }
}
```

The repository maintains an internal state flow that's initialized to `Loading` and automatically refreshes on creation.

## Methods

### listGroups

Returns a reactive Flow of all groups for the current user.

```kotlin theme={null}
fun listGroups(): Flow<DataResult<List<Group>>>
```

**Returns**: `Flow<DataResult<List<Group>>>` - Stream of group list with loading/error states

**Example Usage**:

```kotlin theme={null}
@HiltViewModel
class GroupsViewModel @Inject constructor(
    private val groupRepository: GroupRepository
) : ViewModel() {
    val groups = groupRepository.listGroups()
        .stateIn(viewModelScope, SharingStarted.Lazily, DataResult.Loading)
}
```

**Implementation**:

```kotlin theme={null}
override fun listGroups(): Flow<DataResult<List<Group>>> = _groups
```

***

### getGroup

Returns a Flow for a single group by ID.

```kotlin theme={null}
fun getGroup(groupId: String): Flow<Group>
```

<ParamField path="groupId" type="String" required>
  The unique identifier of the group to retrieve
</ParamField>

**Returns**: `Flow<Group>` - Stream of the requested group (returns placeholder if not found)

**Example Usage**:

```kotlin theme={null}
val group = groupRepository.getGroup("group-123")
    .collectAsState(initial = Group(id = "group-123", name = "Loading..."))
```

**Implementation**:

```kotlin theme={null}
override fun getGroup(groupId: String): Flow<Group> =
    _groups.map { result ->
        when (result) {
            is DataResult.Success -> result.data.find { it.id == groupId }
                ?: Group(id = groupId, name = "Group")
            else -> Group(id = groupId, name = "Group")
        }
    }
```

***

### createGroup

Creates a new group with the current user as owner.

```kotlin theme={null}
suspend fun createGroup(name: String, icon: GroupIcon): Group
```

<ParamField path="name" type="String" required>
  The display name for the new group
</ParamField>

<ParamField path="icon" type="GroupIcon" required>
  The icon to display for the group (e.g., `GroupIcon.Home`, `GroupIcon.Flight`)
</ParamField>

**Returns**: `Group` - The newly created group object

**Throws**: Exception if Supabase call fails

**Example Usage**:

```kotlin theme={null}
viewModelScope.launch {
    try {
        val group = groupRepository.createGroup(
            name = "Weekend Trip",
            icon = GroupIcon.Flight
        )
        println("Created group: ${group.id}")
    } catch (e: Exception) {
        showError("Failed to create group: ${e.message}")
    }
}
```

**Supabase Implementation**:

```kotlin theme={null}
override suspend fun createGroup(name: String, icon: GroupIcon): Group {
    val params = buildJsonObject {
        put("p_name", name)
        put("p_icon", icon.name)
    }
    val row = supabaseClient.postgrest
        .rpc("create_group_with_owner", params)
        .decodeSingle<GroupRow>()

    val group = Group(
        id = row.id,
        name = row.name,
        icon = iconFromName(row.icon),
        memberCount = 1,
        balanceCents = 0L,
        createdBy = row.createdBy
    )
    
    // Update local state
    _groups.update { result ->
        val current = (result as? DataResult.Success)?.data ?: emptyList()
        DataResult.Success(current + group)
    }
    
    return group
}
```

**RPC Function**: `create_group_with_owner` - Creates the group and adds the current user as a member

***

### updateGroup

Updates an existing group's name and icon.

```kotlin theme={null}
suspend fun updateGroup(groupId: String, name: String, icon: GroupIcon)
```

<ParamField path="groupId" type="String" required>
  The ID of the group to update
</ParamField>

<ParamField path="name" type="String" required>
  The new name for the group
</ParamField>

<ParamField path="icon" type="GroupIcon" required>
  The new icon for the group
</ParamField>

**Example Usage**:

```kotlin theme={null}
viewModelScope.launch {
    groupRepository.updateGroup(
        groupId = "group-123",
        name = "Summer Vacation",
        icon = GroupIcon.Beach
    )
}
```

**Supabase Implementation**:

```kotlin theme={null}
override suspend fun updateGroup(groupId: String, name: String, icon: GroupIcon) {
    supabaseClient.from("groups").update({
        set("name", name)
        set("icon", icon.name)
    }) {
        filter { eq("id", groupId) }
    }
    
    // Update local state
    _groups.update { result ->
        val current = (result as? DataResult.Success)?.data ?: return@update result
        DataResult.Success(current.map { g ->
            if (g.id == groupId) g.copy(name = name, icon = icon) else g
        })
    }
}
```

***

### deleteGroup

Deletes a group and all associated data (expenses, splits, members).

```kotlin theme={null}
suspend fun deleteGroup(groupId: String)
```

<ParamField path="groupId" type="String" required>
  The ID of the group to delete
</ParamField>

**Warning**: This operation is irreversible and cascades to all related data.

**Example Usage**:

```kotlin theme={null}
viewModelScope.launch {
    try {
        groupRepository.deleteGroup("group-123")
        navigateBack()
    } catch (e: Exception) {
        showError("Failed to delete group: ${e.message}")
    }
}
```

**Supabase Implementation**:

```kotlin theme={null}
override suspend fun deleteGroup(groupId: String) {
    val params = buildJsonObject { put("p_group_id", groupId) }
    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 })
    }
}
```

**RPC Function**: `delete_group_cascade` - Deletes the group and all related records

***

### refreshGroups

Forces a refresh of the groups list from Supabase.

```kotlin theme={null}
suspend fun refreshGroups()
```

**Example Usage**:

```kotlin theme={null}
viewModelScope.launch {
    groupRepository.refreshGroups()
}
```

**Supabase Implementation**:

```kotlin theme={null}
override suspend fun refreshGroups() {
    try {
        val rows = supabaseClient.postgrest
            .rpc("get_my_groups_summary")
            .decodeList<GroupSummaryRow>()

        _groups.value = DataResult.Success(rows.map { row ->
            Group(
                id = row.id,
                name = row.name,
                icon = iconFromName(row.icon),
                memberCount = row.memberCount.toInt(),
                balanceCents = row.balanceCents,
                createdBy = row.createdBy
            )
        })
    } catch (e: Exception) {
        _groups.value = DataResult.Error("Failed to load groups", e)
    }
}
```

**RPC Function**: `get_my_groups_summary` - Returns groups with member counts and balance calculations

## Data Models

### Group

Source: `models/Group.kt:7`

```kotlin theme={null}
@Serializable
data class Group(
    val id: String,
    val name: String,
    val emoji: String = "",
    val icon: GroupIcon = GroupIcon.Group,
    val memberCount: Int = 0,
    val balanceCents: Long = 0L,
    val currency: String = "USD",
    val createdBy: String = ""
) {
    val isOwed: Boolean get() = balanceCents >= 0
    val formattedBalance: String
    val balanceLabel: String
}
```

### Internal Models

```kotlin theme={null}
@Serializable
private data class GroupSummaryRow(
    val id: String = "",
    val name: String = "",
    val icon: String = "",
    @SerialName("created_by")   val createdBy: String = "",
    @SerialName("created_at")   val createdAt: String = "",
    @SerialName("member_count") val memberCount: Long = 0L,
    @SerialName("balance_cents") val balanceCents: Long = 0L
)

@Serializable
private data class GroupRow(
    val id: String = "",
    val name: String = "",
    val icon: String = "",
    @SerialName("created_by") val createdBy: String = "",
    @SerialName("created_at") val createdAt: String = ""
)
```

## Error Handling

### Error States

The repository wraps all Supabase operations in try-catch blocks and updates the state flow:

```kotlin theme={null}
try {
    val data = fetchFromSupabase()
    _groups.value = DataResult.Success(data)
} catch (e: Exception) {
    _groups.value = DataResult.Error("Failed to load groups", e)
}
```

### Common Errors

* **Network errors**: Connection timeout, no internet
* **Authentication errors**: Invalid session, expired token
* **Database errors**: Constraint violations, RLS policy failures

## See Also

* [Repository Pattern Overview](/repositories/overview)
* [ExpensesRepository](/repositories/expense-repository)
* [Group Data Model](/models/group)
