EmotiFlow는 사용자의 감정을 기록하면 AI가 함께 질문하고 위로하며, 때로는 이미지를 만들고 음악을 추천해 주는 감정 일기 앱입니다. 본격적인 업무를 받기 전 2주 동안 혼자 설계·구현하며 Cursor AI에 익숙해지는 것이 1차 목표였고, 이후 1주간 실무를 시작하면서 배운 점을 반영해 정리했습니다.
기술 스택: Flutter(Dart), Firebase(Auth/Firestore/Storage), Riverpod, GoRouter, Material3, Google Gemini API
문제: 처음에는 Provider 패턴으로 시작했지만, 복잡한 상태 전이와 의존성 관리에서 한계를 느꼈습니다.
해결: Riverpod 기반 MVVM 아키텍처로 전환하여 features/ 단위 모듈화를 구현했습니다. 각 기능별로 독립적인 상태 관리와 의존성 주입을 통해 코드의 가독성과 유지보수성을 크게 향상시켰습니다.
학습: 상태 관리 라이브러리의 선택이 전체 앱 구조에 미치는 영향을 체감했고, "설계 먼저, 코드 나중"의 중요성을 깨달았습니다.
문제: Firestore 스트림과 로컬 상태(검색/필터/삭제 모드)가 충돌하여 사용자 경험이 불안정했습니다.
해결: 상태를 UI 레벨과 비즈니스 로직 레벨로 분리하고, 각각의 생명주기를 명확히 정의했습니다. 검색 토글, 필터 태그 바, 선택 오버레이와 확인 다이얼로그 등 UI 신호를 분명히 하여 사용자의 인지 부하를 줄였습니다.
기술적 구현:
// 상태 분리 예시
class DiaryListState {
final List<Diary> diaries;
final bool isSearchMode;
final bool isDeleteMode;
final List<String> selectedIds;
// 각 상태별 독립적인 업데이트 메서드
DiaryListState copyWith({...});
}
학습: "빠르게 보이되, 헷갈리지 않게"가 실시간 인터페이스에서 가장 중요한 원칙이라는 것을 체득했습니다.
문제: Gemini API 응답의 일관성 부족과 예외 상황 처리가 불안정했습니다.
해결: 프롬프트 규칙을 체계화하고 폴백 시스템을 구축했습니다. 질문 톤, 길이, "후속 질문은 1개" 같은 규칙을 프롬프트로 고정했고, 실패 시 보여줄 폴백 문안을 정해두었습니다.
기술적 구현:
class GeminiService {
static const String _basePrompt = '''
당신은 따뜻하고 공감적인 감정 상담사입니다.
- 응답은 항상 따뜻한 톤을 유지하세요
- 후속 질문은 반드시 1개만 하세요
- 사용자의 감정을 인정하고 위로해주세요
''';
Future<String> generateResponse(String userInput) async {
try {
// API 호출 로직
} catch (e) {
return _getFallbackResponse(userInput);
}
}
}
학습: AI와의 협업에서 "생성된 코드의 의도를 내가 설명할 수 있을 때만 합친다"는 기준을 지키며 리뷰-수정 루틴을 만들었습니다.
문제: 감정과 음악을 연결하는 추천 알고리즘과 외부 API 연동의 복잡성, 저작권 이슈를 고려해야 했습니다.
해결: 감정→태그 매핑 시스템을 설계하고, 단계적 구현 전략을 수립했습니다. 미리듣기, 로열티 프리 대안, 오프라인 모드 같은 안전장치를 우선 구상했습니다.
기술적 구현:
class EmotionMusicMapping {
static const Map<Emotion, List<String>> _mapping = {
Emotion.joy: ['upbeat', 'energetic', 'pop'],
Emotion.sadness: ['calm', 'melancholic', 'acoustic'],
// ... 더 많은 감정별 매핑
};
static List<String> getMusicTags(Emotion emotion) {
return _mapping[emotion] ?? ['neutral', 'ambient'];
}
}
학습: 외부 서비스 연동 시 "단계적 구현"과 "안전장치 우선"의 중요성을 체감했습니다.
문제: 각 화면마다 다른 스타일과 색상이 사용되어 일관성이 떨어졌습니다.
해결: 다크/라이트 테마, 타이포그래피, 버튼/카드/입력 위젯을 일관화했습니다. Material3 디자인 시스템을 기반으로 공통 컴포넌트 라이브러리를 구축했습니다.
기술적 구현:
class AppTheme {
static ThemeData get lightTheme => ThemeData(
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(
seedColor: AppColors.primary,
brightness: Brightness.light,
),
// 일관된 컴포넌트 스타일 정의
);
}
학습: 작은 일관성이 누적되면 학습 비용이 줄고, 새로운 화면을 만드는 속도가 빨라진다는 것을 체감했습니다.
상황: 검색과 필터를 동시에 적용할 때 데이터가 중복되거나 누락되는 현상이 발생했습니다.
원인 분석: 스트림 구독이 여러 개 생성되어 메모리 누수와 데이터 불일치가 발생했습니다.
해결 과정:
1. 스트림 구독을 단일화하고, 로컬 상태 변경 시에만 UI를 업데이트하도록 수정
2. StreamBuilder와 Consumer를 적절히 조합하여 불필요한 리빌드 방지
3. 디버깅을 위한 로그 시스템 구축
결과: 검색/필터 적용 시 즉시 갱신되며, 메모리 사용량이 30% 감소했습니다.
상황: Gemini API 응답이 지연되거나 실패할 때 사용자가 무한 대기하는 상황이 발생했습니다.
원인 분석: 네트워크 지연과 API 제한으로 인한 응답 실패가 적절히 처리되지 않았습니다.
해결 과정:
1. 타임아웃 설정과 재시도 로직 구현
2. 로딩 상태와 에러 상태를 명확히 구분
3. 사용자 친화적인 폴백 메시지 제공
결과: 응답 실패 시 3초 내에 적절한 안내 메시지가 표시되어 사용자 경험이 크게 개선되었습니다.
상황: 일기 목록의 다양한 상태(검색, 필터, 정렬, 삭제 모드)가 하나의 클래스에 모두 포함되어 코드가 복잡해졌습니다.
원인 분석: 상태 간의 의존성과 업데이트 로직이 명확하지 않아 유지보수가 어려워졌습니다.
해결 과정:
1. 상태를 UI 상태와 비즈니스 상태로 분리
2. 각 상태 변경에 대한 명확한 인터페이스 정의
3. 상태 변경 로그를 통한 디버깅 시스템 구축
결과: 코드 가독성이 향상되고, 새로운 기능 추가 시 기존 코드 수정이 최소화되었습니다.
자율적인 환경이 제 페이스를 끌어올렸습니다. 대신 결과로 증명하려고 문서·커밋·데모를 자주 남겼고, 의사결정 배경을 짧게라도 적어두었습니다. 혼자 진행했지만 혼자만 이해하는 코드를 만들지 않으려 했습니다. 이후 실무에 들어가면서도 같은 태도로 작업하고 있습니다.
작성자: EmotiFlow 인턴
기간: 2주 개인 프로젝트 + 1주 실무 접목
버전: v1.2 (제출용)