Phase 2: Household Creation & Invites - 6 plans in 4 waves covering SHARE-01 through SHARE-05 - Data models, repository pattern, and Supabase integration - Database schema with RLS policies for multi-tenant isolation - State management with Riverpod and business logic use cases - Complete UI components for household management - Navigation integration with authentication flow Ready for execution: /gsd:execute-phase 2
13 KiB
13 KiB
phase: 02-household-creation
plan: 03
type: execute
wave: 2
depends_on: [02-01]
files_modified:
- lib/features/household/providers/household_provider.dart
- lib/features/household/domain/usecases/create_household_usecase.dart
- lib/features/household/domain/usecases/generate_invite_code_usecase.dart
- lib/features/household/domain/usecases/join_household_usecase.dart
- lib/features/household/domain/usecases/get_user_households_usecase.dart
autonomous: true
must_haves:
truths:
- "Household state management provides reactive updates across the app"
- "Use cases encapsulate business logic for household operations"
- "Provider integrates with existing authentication state"
- "Household operations handle loading states and errors properly"
artifacts:
- path: "lib/features/household/providers/household_provider.dart"
provides: "Global household state management"
contains: "class HouseholdProvider", "Riverpod", "AsyncValue"
- path: "lib/features/household/domain/usecases/create_household_usecase.dart"
provides: "Household creation business logic"
contains: "class CreateHouseholdUseCase", "Future"
- path: "lib/features/household/domain/usecases/join_household_usecase.dart"
provides: "Household joining logic with validation"
contains: "class JoinHouseholdUseCase", "invite code validation"
- path: "lib/features/household/domain/usecases/get_user_households_usecase.dart"
provides: "User household retrieval logic"
contains: "class GetUserHouseholdsUseCase", "List"
key_links:
- from: "lib/features/household/providers/household_provider.dart"
to: "lib/features/household/domain/usecases/create_household_usecase.dart"
via: "dependency injection"
pattern: "CreateHouseholdUseCase.*createHouseholdUseCase"
- from: "lib/features/household/providers/household_provider.dart"
to: "lib/providers/auth_provider.dart"
via: "authentication state"
pattern: "ref.watch.*authProvider"
Implement household state management and business logic use cases following clean architecture patterns.
Purpose: Provide reactive household state that integrates with authentication and handles all household operations with proper error handling. Output: Complete state management layer with use cases and Riverpod provider ready for UI integration.
<execution_context>
@/.opencode/get-shit-done/workflows/execute-plan.md
@/.opencode/get-shit-done/templates/summary.md
</execution_context>
State Management References
@lib/providers/auth_provider.dart @lib/features/authentication/data/repositories/auth_repository_impl.dart
Create Household Use Cases lib/features/household/domain/usecases/create_household_usecase.dart lib/features/household/domain/usecases/generate_invite_code_usecase.dart lib/features/household/domain/usecases/join_household_usecase.dart lib/features/household/domain/usecases/get_user_households_usecase.dart Create use cases for household operations following clean architecture:1. CreateHouseholdUseCase (create_household_usecase.dart):
```dart
class CreateHouseholdUseCase {
final HouseholdRepository repository;
CreateHouseholdUseCase(this.repository);
Future<HouseholdEntity> call(String name, String userId) async {
if (name.trim().isEmpty) {
throw HouseholdValidationException('Household name cannot be empty');
}
if (name.length > 100) {
throw HouseholdValidationException('Household name too long (max 100 characters)');
}
return await repository.createHousehold(name.trim(), userId);
}
}
```
2. GenerateInviteCodeUseCase (generate_invite_code_usecase.dart):
```dart
class GenerateInviteCodeUseCase {
final HouseholdRepository repository;
GenerateInviteCodeUseCase(this.repository);
Future<InviteCodeModel> call(String householdId, String userId) async {
// Check if user is owner (will be validated in repository)
return await repository.generateInviteCode(householdId, userId);
}
}
```
3. JoinHouseholdUseCase (join_household_usecase.dart):
```dart
class JoinHouseholdUseCase {
final HouseholdRepository repository;
JoinHouseholdUseCase(this.repository);
Future<HouseholdEntity> call(String inviteCode, String userId) async {
if (inviteCode.trim().isEmpty) {
throw HouseholdValidationException('Invite code cannot be empty');
}
if (inviteCode.length != 8) {
throw HouseholdValidationException('Invite code must be 8 characters');
}
return await repository.joinHousehold(inviteCode.trim().toUpperCase(), userId);
}
}
```
4. GetUserHouseholdsUseCase (get_user_households_usecase.dart):
```dart
class GetUserHouseholdsUseCase {
final HouseholdRepository repository;
GetUserHouseholdsUseCase(this.repository);
Future<List<HouseholdEntity>> call(String userId) async {
return await repository.getUserHouseholds(userId);
}
}
```
Include proper validation and business logic in each use case.
Create custom exception classes for household operations.
flutter analyze lib/features/household/domain/usecases/*.dart passes
Household use cases encapsulate business logic with proper validation
Create Household Provider
lib/features/household/providers/household_provider.dart
Create Riverpod provider for household state management:
```dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
// Use case providers
final createHouseholdUseCaseProvider = Provider<CreateHouseholdUseCase>((ref) {
final repository = ref.watch(householdRepositoryProvider);
return CreateHouseholdUseCase(repository);
});
final generateInviteCodeUseCaseProvider = Provider<GenerateInviteCodeUseCase>((ref) {
final repository = ref.watch(householdRepositoryProvider);
return GenerateInviteCodeUseCase(repository);
});
final joinHouseholdUseCaseProvider = Provider<JoinHouseholdUseCase>((ref) {
final repository = ref.watch(householdRepositoryProvider);
return JoinHouseholdUseCase(repository);
});
final getUserHouseholdsUseCaseProvider = Provider<GetUserHouseholdsUseCase>((ref) {
final repository = ref.watch(householdRepositoryProvider);
return GetUserHouseholdsUseCase(repository);
});
// Main household provider
class HouseholdProvider extends StateNotifier<AsyncValue<List<HouseholdEntity>>> {
final GetUserHouseholdsUseCase _getUserHouseholdsUseCase;
final CreateHouseholdUseCase _createHouseholdUseCase;
final JoinHouseholdUseCase _joinHouseholdUseCase;
final GenerateInviteCodeUseCase _generateInviteCodeUseCase;
HouseholdProvider(
this._getUserHouseholdsUseCase,
this._createHouseholdUseCase,
this._joinHouseholdUseCase,
this._generateInviteCodeUseCase,
) : super(const AsyncValue.loading()) {
_initialize();
}
Future<void> _initialize() async {
state = const AsyncValue.loading();
try {
// Will be called when auth state is available
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
Future<void> loadUserHouseholds(String userId) async {
state = const AsyncValue.loading();
try {
final households = await _getUserHouseholdsUseCase(userId);
state = AsyncValue.data(households);
} catch (e, stack) {
state = AsyncValue.error(e, stack);
}
}
Future<HouseholdEntity> createHousehold(String name, String userId) async {
try {
final household = await _createHouseholdUseCase(name, userId);
// Refresh the list
await loadUserHouseholds(userId);
return household;
} catch (e) {
throw HouseholdOperationException('Failed to create household: $e');
}
}
Future<HouseholdEntity> joinHousehold(String inviteCode, String userId) async {
try {
final household = await _joinHouseholdUseCase(inviteCode, userId);
// Refresh the list
await loadUserHouseholds(userId);
return household;
} catch (e) {
throw HouseholdOperationException('Failed to join household: $e');
}
}
Future<InviteCodeModel> generateInviteCode(String householdId, String userId) async {
try {
return await _generateInviteCodeUseCase(householdId, userId);
} catch (e) {
throw HouseholdOperationException('Failed to generate invite code: $e');
}
}
void clearHouseholds() {
state = const AsyncValue.data([]);
}
}
// Provider instance
final householdProvider = StateNotifierProvider<HouseholdProvider, AsyncValue<List<HouseholdEntity>>>((ref) {
return HouseholdProvider(
ref.read(getUserHouseholdsUseCaseProvider),
ref.read(createHouseholdUseCaseProvider),
ref.read(joinHouseholdUseCaseProvider),
ref.read(generateInviteCodeUseCaseProvider),
);
});
// Current selected household provider
final currentHouseholdProvider = StateProvider<HouseholdEntity?>((ref) => null);
// Repository provider (to be added to main providers file)
final householdRepositoryProvider = Provider<HouseholdRepository>((ref) {
final datasource = ref.read(householdRemoteDatasourceProvider);
return HouseholdRepositoryImpl(datasource);
});
final householdRemoteDatasourceProvider = Provider<HouseholdRemoteDatasource>((ref) {
final client = ref.read(supabaseClientProvider);
return HouseholdRemoteDatasource(client);
});
```
Follow auth provider patterns for consistency.
Include proper error handling and state transitions.
flutter analyze lib/features/household/providers/household_provider.dart passes
Household provider provides reactive state management with proper integration
Create Household Exception Classes
lib/features/household/domain/exceptions/household_exceptions.dart
Create custom exception classes for household operations:
```dart
// Base household exception
abstract class HouseholdException implements Exception {
final String message;
const HouseholdException(this.message);
@override
String toString() => 'HouseholdException: $message';
}
// Validation exceptions
class HouseholdValidationException extends HouseholdException {
const HouseholdValidationException(String message) : super(message);
@override
String toString() => 'HouseholdValidationException: $message';
}
// Operation exceptions
class HouseholdOperationException extends HouseholdException {
const HouseholdOperationException(String message) : super(message);
@override
String toString() => 'HouseholdOperationException: $message';
}
// Specific household exceptions
class HouseholdNotFoundException extends HouseholdException {
const HouseholdNotFoundException(String message) : super(message);
@override
String toString() => 'HouseholdNotFoundException: $message';
}
class InviteCodeException extends HouseholdException {
const InviteCodeException(String message) : super(message);
@override
String toString() => 'InviteCodeException: $message';
}
class HouseholdMembershipException extends HouseholdException {
const HouseholdMembershipException(String message) : super(message);
@override
String toString() => 'HouseholdMembershipException: $message';
}
```
Follow authentication exception patterns for consistency.
flutter analyze lib/features/household/domain/exceptions/household_exceptions.dart passes
Household exception classes provide clear error categorization
1. Use cases encapsulate business logic with proper validation
2. Household provider provides reactive state management following Riverpod patterns
3. Exception classes provide clear error categorization
4. Integration with authentication state is properly structured
5. All household operations handle loading states and errors appropriately
<success_criteria> Household state management is complete with use cases, provider, and exception handling ready for UI integration. </success_criteria>
After completion, create `.planning/phases/02-household-creation/02-03-SUMMARY.md` with state management implementation summary