계산이 맞아도 앱처럼 느껴지지 않을 때
계산 로직이 어느 정도 잡혔다고 생각한 순간이 있었다. 야간 구간 겹침 처리도 됐고, 자정 넘김도 해결됐고, 공휴일 분기도 붙었다.
그런데 앱을 열면 뭔가 어색했다.
근무를 하나 입력하면, 홈 화면의 이번 달 합계가 바뀌어야 한다. 당연한 것처럼 들리지만, 이게 자동으로 연결되지 않으면 사용자는 매번 화면을 직접 새로고침하거나 앱을 껐다 켜야 한다. 계산 결과가 정확해도, 그 결과가 즉시 반영되지 않으면 계산기가 아니라 함수처럼 느껴진다.
앱다운 경험은 정확성만으로 만들어지지 않는다. 입력과 출력이 끊김 없이 이어지는 흐름이 있어야 한다.
입력 → 계산 → 합계 반영: 피드백 루프 만들기
이 문제를 해결하는 구조가 Provider/ChangeNotifier였다.
근무 기록 리스트를 Provider로 관리하면, 새 항목이 추가되거나 수정될 때 notifyListeners()가 호출되고, 이 Provider를 바라보는 모든 위젯이 자동으로 다시 그려진다. 홈 화면의 합계 카드도, 근무 리스트도 별도로 새로고침할 필요 없이 반영된다.
class ShiftProvider extends ChangeNotifier {
final List<ShiftEntry> _shifts = [];
void addShift(ShiftEntry shift) {
_shifts.add(shift);
notifyListeners(); // 이 한 줄이 연결된 모든 화면을 갱신한다
}
double get totalSalary =>
_shifts.fold(0, (sum, s) => sum + s.calculatedPay);
}
상태 관리를 배우면 “왜 필요한지 잘 모르겠다”는 느낌이 먼저 온다. 이 프로젝트에서는 그 이유를 코드로 직접 체감했다. 입력 하나가 화면 전체에 즉시 반영되는 경험이 생기고 나서야, 상태 관리가 단순한 패턴 공부가 아니라는 걸 알게 됐다.
로컬 저장만으로 MVP를 완성할 수 있다
기록이 앱을 껐다 켜도 남아야 한다. 이건 선택이 아니라 기본이다.
초기에는 서버 연동을 고려하지 않았다. SharedPreferences로 시급 설정과 근무 기록을 로컬에 저장하는 구조로 시작했다. 복잡한 인증도, 네트워크 요청도 없다. 앱을 열면 저장된 기록이 그대로 이어진다.
이 결정이 MVP 완성에 결정적이었다.
서버를 붙이려면 API 설계, 인증, 에러 처리, 오프라인 대응까지 생각해야 한다. 초기 버전에서 그걸 전부 다루는 건 주객이 전도된다. 로컬 저장만으로도 사용자가 매일 근무를 입력하고, 이번 달 예상 급여를 확인하고, 다음에 다시 열었을 때 기록이 이어지는 경험을 줄 수 있었다.
근무 입력 → SharedPreferences 저장
앱 재시작 → 저장된 기록 불러오기 → Provider에 반영 → 홈 화면 갱신
단순한 흐름이지만, 이게 제대로 작동하는 순간부터 앱이 실제로 쓸 수 있는 것처럼 느껴지기 시작했다.
Phase 3부터 앱의 성격이 바뀌었다
커밋 히스토리를 phase 단위로 묶으면 흐름이 보인다.
Phase 2까지는 “계산이 맞게 작동하는가”가 기준이었다. Phase 3부터는 기준이 달라졌다.
premium feature guard + settings/statistics pages — 이 커밋 이후부터 앱이 계산기에서 서비스로 넘어가기 시작했다. 프리미엄 구독(RevenueCat), 통계 페이지, PDF/CSV export, Google Drive 백업, 근무 패턴 자동 생성기까지 붙었다. 기능 하나가 더해질 때마다 앱의 정체성이 조금씩 바뀌었다.
특히 export와 백업 기능을 넣으면서 느낌이 달라졌다. 사용자가 자신의 데이터를 PDF로 뽑고, 다른 기기로 백업할 수 있다는 건 단순한 편의 기능이 아니다. 그 기능이 붙는 순간, “내가 만드는 게 토이 프로젝트가 아닐 수도 있겠다”는 감각이 생겼다.
처음에는 계산기였다. 기록이 붙으면서 기록 관리 앱이 됐다. 통계와 export가 붙으면서 리포트 도구가 됐다. 백업과 구독이 붙으면서 서비스 형태를 갖추기 시작했다.
마지막 10%가 생각보다 오래 걸렸다
Phase 5의 커밋들을 보면 이런 것들이 있다.
changing app iconsloading animation fixupdate on legal_onboarding pagefixing uidelete confirmationAndroidManifest/build.gradle.kts, notification permissions, signing
기능 구현과는 다른 종류의 작업들이다. 아이콘, 로딩 처리, 삭제 확인 다이얼로그, 배포 서명, 권한 설정.
이것들은 하나하나가 작아 보인다. 하지만 전부 더하면 생각보다 시간이 걸린다. 아이콘 하나를 바꾸려면 해상도별 이미지가 필요하고, AndroidManifest 설정을 건드리면 빌드 에러가 따라온다. 삭제 확인 다이얼로그는 UX 흐름 전체를 다시 검토하게 만든다.
막상 기능이 어느 정도 갖춰지고 나니, 진짜 시간을 잡아먹은 건 이런 마지막 10%였다. 앱은 기능으로 완성되지 않는다. 아이콘, 로딩, 권한, 온보딩 문구, 배포 설정 같은 것들이 전체 완성도를 결정하는 경우가 많다.
느낀 점
이번 프로젝트에서 가장 크게 체감한 건, 내가 잘 아는 문제를 고른 것이 얼마나 실질적인 차이를 만드는가 하는 점이었다.
기능 우선순위를 잡을 때도, 계산 규칙의 예외 케이스를 정의할 때도, “이건 현장에서 실제로 중요한 부분이다”라고 바로 판단할 수 있었다. 모르는 분야였다면 처음부터 다시 조사해야 했을 것들이, 이미 머릿속에 있었다. 문제를 아는 것과 모르는 것의 차이는 기능 수가 아니라 결정 속도에서 드러났다.
또 하나는 범위를 줄이는 결정의 효과다. 달력을 버리고 리스트를 택했을 때, 앱의 목적이 더 선명해졌다. 선명해진 목적이 이후 모든 기능 추가 결정의 기준이 됐다. 범위를 줄이는 판단이 결과적으로 더 좋은 앱을 만든다는 걸 이 프로젝트에서 직접 확인했다.
배운 점
기술적으로는 Flutter/Dart로 화면과 로직을 함께 다루는 흐름, Provider로 상태를 관리하는 방식, SharedPreferences로 로컬 저장을 구성하는 법을 익혔다.
하지만 더 오래 남을 것은 구조를 먼저 생각하는 습관이다. models/providers/screens/utils/widgets로 처음부터 분리했기 때문에, Phase 3 이후 기능을 하나씩 붙여가는 과정이 비교적 깨끗하게 유지됐다. 단일 파일에 다 넣었다면 통계 페이지나 export 기능을 붙일 때 이미 뒤엉켜 있었을 것이다.
생성형 도구에 대해서도 배웠다. Stitch와 Jules를 쓰면서 막연한 요청과 구조화된 요청의 결과 차이를 직접 경험했다. 도구를 잘 쓰는 능력은 결국 내가 무엇을 원하는지 먼저 정확히 아는 것과 같았다.
다음에 다듬고 싶은 것
계산 로직의 단위 테스트는 Phase 2에서 한 번 작성했지만(unit tests for shift salary calculation logic), 이후 기능이 확장되면서 테스트 커버리지가 따라가지 못했다. 야간 구간 겹침, 자정 넘김, 공휴일 분기 같은 경계값 케이스는 반드시 테스트로 보호해야 한다. 계산이 틀리면 신뢰를 잃는 앱이기 때문이다.
법률 온보딩 페이지에 수당 계산 기준을 안내하긴 했지만, 이 앱의 계산을 얼마나 신뢰해야 하는지에 대한 안내는 더 명확하게 다듬고 싶다. 계산 규칙은 앱에 반영하려고 한 기준이고, 실제 급여와 다를 수 있다는 점을 사용자가 쉽게 확인할 수 있어야 한다.
그리고 언젠가는 공공데이터 API를 연동해 공휴일 여부를 자동으로 처리하고 싶다. 지금은 사용자가 토글로 직접 입력하는 방식이지만, 날짜를 입력하면 공휴일 여부가 자동으로 반영되면 입력 흐름이 훨씬 자연스러워질 것이다.
마치며
WorkWage는 Flutter 문법을 익히는 연습이기도 했지만, 그보다 더 크게는 내가 실제로 이해하는 문제를 제품으로 바꾸는 훈련이었다.
기능을 많이 넣는 것보다, 사용자가 가장 자주 마주하는 흐름을 정확하게 만드는 일이 더 중요하다는 걸 배웠다. 그 흐름이 “근무 입력 → 이번 달 합계 즉시 반영”이라는 피드백 루프였고, 기록, 요약, 저장, 수정이 붙을수록 어떤 값이 원본이고 어떤 값이 계산 결과인지 분리하는 상태 설계가 더 중요해졌다.
Community
Comments
Comments appear immediately. Use report if something needs review.
No comments yet.