개인 포트폴리오 및 기술 검증 목적으로 개발한 DailyNet(데일리넷)의 아키텍처 설계와 핵심 기능, 그리고 개발 과정에서 해결한 기술적 트러블슈팅 사례를 공유합니다.
DailyNet은 사용자가 입력한 식단과 운동 데이터를 기반으로 Gemini AI 엔진을 통해 하루의 에너지 수지를 공학적으로 분석하고 '순 칼로리(Net Calories)'를 정밀 산출하는 스마트 다이어트 솔루션입니다.
🏗️ 시스템 아키텍처 (System Architecture)
본 프로젝트는 보안(Security)과 확장성(Scalability)을 극대화하기 위해 독립적인 역할 분담을 지향하는 3-Tier 구조를 채택하고 있습니다.
[Client] Android (Jetpack Compose)
│ (Firebase App Check 검증 및 Google Auth 인증)
▼
[Backend] Firebase Functions (Node.js) & Firestore
│ (프롬프트 은닉 & API Key 보호 & Rate Limiting)
▼
[AI Engine] Google Gemini 2.5 Flash
1. Client (Android)
- 역할: 사용자의 신체 데이터 및 식단/운동 입력 UI/UX 처리
- 특징: 원격 데이터베이스인 Firebase Firestore로부터 유저의 데이터를 실시간으로 가져와(Fetch) 화면에 즉각 반영합니다. 로컬 DB(Room)를 함께 활용하는 Offline-First 전략으로 오프라인 환경에서도 중단 없는 입력을 지원합니다.
2. Backend (Firebase)
- Firebase Functions (Node.js): API Key 노출을 원천 차단하고 AI 프롬프트를 백엔드 내에 은닉하여 비즈니스 로직 유출 및 프롬프트 인젝션을 방지합니다. 사용자별 호출 제한(Rate Limiting)을 구현하여 비용 리스크를 관리합니다.
- Firebase App Check: 앱의 무결성을 검증하고 승인되지 않은 비인가 기기나 위변조된 앱으로부터의 백엔드 호출을 차단하여 자원 어뷰징을 방지합니다.
3. AI Engine (Gemini)
- 역할: 서버로부터 전달받은 가공 데이터를 바탕으로 정밀 분석 결과를 생성합니다.
- 특징: Mifflin-St Jeor 공식을 적용한 정교한 BMR 산출 및 사용자 맞춤형 영양 조언을 제공하며, 분석 결과를 IDE 터미널 로그 형식의 테이블로 출력하여 시각적 전문성을 높였습니다.
🛠️ 기술 스택 (Tech Stack)
| 분류 | 기술 도구 |
| Language | Kotlin |
| UI Framework | Jetpack Compose (Material 3) |
| Architecture | MVVM, Clean Architecture, Repository Pattern |
| DI | Hilt |
| Database | Room (Local), Firebase Firestore (Remote) |
| Backend / Security | Firebase Functions (Node.js), Firebase App Check, Firebase Auth |
| AI Engine | Google Gemini 2.5 Flash |
| Asynchronous | Coroutines, Flow |
| Network / Serialization | Retrofit2, Kotlinx Serialization |
📱 주요 UI 및 기능 Flow
1. 인증 및 사용자 온보딩 파이프라인 (Sign In & Setup)
| 구글 로그인 환경 (Sign In) | 최초 신체 정보 입력 다이얼로그 |
![]() |
![]() |
- Firebase Auth 연동 뷰: 깔끔한 단일 브랜드 이미지를 전달하는 구글 소셜 로그인 기반의 진입 레이아웃을 구성했습니다.
- 최초 1회 프로필 유효성 검증: 로그인 직후 사용자의 키, 시작 몸무게, 성별, 생년월일 정보가 부재할 경우 전면 모달 다이얼로그(ProfileSetupDialog)를 트리거합니다. 성별 FilterChip 선택 및 안전한 날짜 파싱 기반의 온보딩 데이터 셋을 확보하여 Firebase에 저장 후 Gemini AI의 맞춤형 대사량 산출 기초 자료로 바인딩합니다.
2. 메인 대시보드 & 실시간 누적 칼로리 결산 (MainScreen)
| 메인 화면 리스트 및 칼로리 결산 뱃지 | 사용자 계정 관리 바텀시트 |
![]() |
![]() |

- Reactive UI 흐름: Room DB의 변경 사항을 실시간 관찰(StateFlow)하여 메인 화면의 총 누적 칼로리와 주차별 결산 데이터를 자동 갱신합니다.
- 동적 주차 헤더 분리: 월별 주차 계산 헬퍼 함수를 통해 05월 5주차와 같이 직관적인 그룹핑을 제공하며, 칼로리 적자(감량 상태)일 때는 초록색, 과잉일 때는 붉은색으로 표현하여 UX 직관성을 높였습니다.
- 안전한 세션 차단 인터페이스: 프로필 아바타 인터랙션 시 호출되는 ModalBottomSheet를 통해 로그아웃 및 회원탈퇴 분기를 명확히 격리했습니다.
3. 세션 종료 백그라운드 태스크 제어
| 로그아웃 처리 중 로딩 화면 |
![]() |
- Immersive 시스템 레이아웃 토글: 로그아웃 및 회원탈퇴 등 로컬/원격 데이터를 일괄 파기하는 무거운 트랜잭션이 작동할 때, SystemUiController를 연동하여 시스템 바를 일시적으로 은닉(WindowInsetsCompat)하고 터블 클릭 차단 레이어를 오버레이하여 데이터 유실 없는 안정적인 세션 초기화를 보장합니다.
💡 주요 기술적 트러블 슈팅 (Troubleshooting)
1. AI 분석 도중 화면 이탈 후 재진입 시 UI 꼬임 및 초기화 문제
- 문제점: 초기 설계 시 화면 진입점 생명주기(initializeTodayData)에서 무조건 오늘 날짜의 빈 초안 객체를 생성해 덮어씌우는 바람에, 백그라운드에서 분석이 도는 도중 유저가 나갔다 들어오면 로딩 창이 풀려버리거나 입력 데이터가 날아가는 현상이 발생했습니다.
- 해결책: ViewModel의 초기화 로직에 현재 analyzing 플래그 상태를 체크하는 Guard 구문을 추가했습니다.동시에 BackHandler와 상단 탑바의 뒤로가기 진입점에도 if (!uiState.analyzing) mainViewModel.clearTodayDraft() 조건을 걸어, 분석 중일 때는 뒤로 나가더라도 로컬 메모리 상의 임시 데이터와 분석 중인 UI 상태가 파괴되지 않도록 제어했습니다. 이로 인해 유저가 분석 중에 나갔다 다시 들어오면 기존 데이터와 "AI 분석 중..." 로딩 인디케이터 상태가 완벽하게 복원됩니다.
-
Kotlin
fun initializeTodayData() = viewModelScope.launch { // 💡 이미 백그라운드에서 AI 분석이 작동 중이라면 오늘 데이터 상태를 초기화하지 않고 리턴 if (_uiState.value.analyzing) return@launch val latestWeight = repository.getLatestWeight() cachedWeight = latestWeight // ... 이후 초안 로드 및 생성 로직 }
2. 백그라운드 상태에서의 안전한 화면 전환 트리거 분리
- 문제점: UI 레이어의 LaunchedEffect 내에서 완료 토스트 메시지를 트리거로 삼아 onBack() 화면 전환을 수행하도록 설계했으나, 유저가 홈 화면으로 나갔을 때(Background) 코루틴이 일시 정지되었다가 앱 재진입 시점에 누적된 이벤트를 처리하며 화면이 픽 꺼지는 UX 어색함이 있었습니다.
- 해결책: 분석이 성공한 시점에 화면을 닫고 데이터를 초기화하는 비즈니스 로직 트리거(clearTodayDraft())를 ViewModel 내부의 성공 콜백 내로 완전 이전하고, UI 단의 LaunchedEffect는 순수하게 토스트 메시지만 팝업하도록 역할을 분리하여 백그라운드 전환 시의 안정성을 확보했습니다.





댓글