프로젝트에서 보기 →

출퇴근 체크 앱 | 오름캠프 Flutter 모바일 앱 개발 과정 1기 프로젝트

태그
기술 오름캠프 Flutter 앱만들기
시작일
종료일
수정일

https://www.youtube.com/watch?v=CMjKdIZZ2AM

1. 이건 꼭 알아야 한다[^1]

[? 질문] Flutter로 출퇴근(출근/퇴근) 체크 앱을 만들 때, 최소 기능(MVP)을 어떤 구성으로 구현할 수 있는가[^1]
[= 답] 모바일은 Flutter로 **출근/퇴근 체크 UI + API 호출 + 상태관리(로그인 상태)**를 구성하고, 서버는 Node.js로 출퇴근 시간 저장/갱신 로직을 만들며, DB는 MariaDB로 직원별·날짜별 출퇴근 기록 누적이 가능하도록 설계한다.[^2]

[? 질문] 단순히 버튼을 눌러 시간을 기록하는 것에서 더 나아가, “정상 출근”을 어떤 기준으로 제한할 수 있는가[^3]
[= 답] **위치 기반(지오펜싱에 준하는 거리 조건)**으로 위도/경도를 기준점으로 잡고, 기준점으로부터 20m 이내일 때만 출퇴근 체크가 가능하도록 제한한다.[^4]

[? 질문] 프로젝트에서 “구현된 것”과 “미구현(계획만 있었던 것)”은 무엇인가[^5]
[= 답] 출근 체크/퇴근 체크 + 서버 통신 + DB 기록 + 로그인(서버 측) + 비밀번호 bcrypt 해싱은 구현했지만, 공지사항은 껍데기만, 직원 등록 및 관리자 화면 기능은 미구현이라 DB에 직원을 수동 입력해야 한다.[^6]


2. 큰 그림[^7]

이 콘텐츠는 오름캠프 Flutter 모바일 앱 개발 과정 프로젝트로 제작한 출퇴근 체크 애플리케이션을 데모 영상과 함께 소개하고, 구현한 기능/기술스택/핵심 코드 구조(요청 바디 구성, 서버 처리, 위치 기반 제한, DB 설계 방향)를 순서대로 설명한다.[^7] 또한 “하고 싶었지만 못한 것(공지사항 서버, 관리자/직원등록 기능)”을 명확히 구분해 프로젝트의 현재 완성도를 공유한다.[^6]

  • 핵심 메시지 1 — 출퇴근 체크의 본질은 ‘서버에 시간 기록’: 버튼 클릭 시 서버와 통신하여 출근/퇴근 시간을 기록하고, 체크아웃은 날짜 조건까지 포함해 갱신하도록 구성했다.[^8]
  • 핵심 메시지 2 — 위치 기반 제한으로 ‘아무 데서나 체크’ 방지: 기준 위도/경도에서 20m 이내에서만 체크되도록 하여 출퇴근의 신뢰도를 높이려 했다.[^4]
  • 핵심 메시지 3 — 직원별/날짜별 누적을 위해 DB가 필수: 직원마다 출퇴근 기록이 쌓이고 날짜별 조회가 필요하므로 MariaDB 같은 DB를 사용하고, 키 관계(Primary/Foreign key)를 염두에 두고 연결한다.[^9]

3. 하나씩 살펴보기[^1]

3.1 프로젝트 소개와 데모(시안 영상) 흐름[^1]

📸 0:00

발표자는 먼저 “출퇴근 애플리케이션을 제작해 봤다”고 프로젝트의 성격을 한 문장으로 정의하고, 화면을 “하나씩 넘기며” 설명하겠다고 예고한다.[^1] 이어서 시안(데모) 영상을 보여주며 실제 동작 흐름을 설명한다.[^2]

데모에서 사용자는 앱에 들어가 출근 체크를 누른다.[^2] 이때 출근 체크는 서버와 통신을 수행하기 때문에 “잠깐 기다려야” 하는 지연이 발생한다고 설명한다.[^2] 통신이 완료되면 출근 체크가 완료되고, 이어서 퇴근도 체크가 가능하다고 말한다.[^2] 발표자가 구현한 기능 범위는 여기까지이며, 즉 “출근/퇴근 체크 동작”이 현재 앱의 중심 기능이라고 선을 긋는다.[^2]

한편, UI 상에 존재하는 공지사항 메뉴/화면은 원래 구현했어야 하는 항목이지만, “서버 구현을 제대로 안 했다”는 이유로 현재는 ‘공지사항 껍데기’(프론트 껍데기만 존재) 수준이라고 밝힌다.[^6] 즉, 공지사항은 화면 요소는 있으나 실제 데이터 연동/기능은 빠진 상태다.[^6]

[!IMPORTANT] 구현 범위 명시 출근/퇴근 체크는 서버 통신까지 포함하여 동작하지만, 공지사항은 서버 미구현으로 실제 기능이 아니라 “껍데기”로 남아 있다.[^6]


3.2 개발 환경(기술 스택)과 “주요 기능이 하나뿐인 이유”[^7]

📸 0:52

발표자는 개발 환경을 명확히 나열한다: 모바일은 Flutter, 서버는 Node.js, DB는 MariaDB를 사용했다고 한다.[^7] 그리고 “주요 기능” 파트에서 출퇴근 앱의 핵심은 무엇보다 출근 시간 체크 기능이라고 강조한다.[^10]

다만 그는 현재 프로젝트에 기능을 “이거 하나만 넣었다”고 말하며, 그래서 앱이 “되게 초라해 보인다”는 소감을 덧붙인다.[^10] 이 발언은 기능 확장(예: 공지사항, 관리자, 직원 등록 등)을 염두에 두었으나 현재는 핵심 기능 중심의 MVP 형태로 마무리되었음을 시사한다.[^6]

  • 앱이 제공하는 중심 가치: 출근/퇴근 시간 기록[^2]
  • 아직 부족한 확장 기능: 공지사항 연동, 직원 등록, 관리자 기능[^6]

3.3 사용 라이브러리 구성과 의도(특히 관리자 화면의 ‘계획 vs 미구현’)[^11]

📸 1:24

발표자는 사용한 라이브러리를 열거하면서 “왜 이 라이브러리가 들어갔는지” 배경을 함께 설명한다.[^11]

3.3.1 위치 권한/위치 수집: Permission Handler + Geolocator[^11]

출퇴근 체크를 위치 기반으로 만들기 때문에 “위치 정보를 가져와야” 하고, 이를 위해 permission_handler를 사용했다고 한다.[^11] 실제 위치 수집은 geolocator를 이용한다고 말하며, 위치 권한 허가(퍼미션) 처리 후 위치 값을 얻는 흐름을 전제로 한다.[^4]

  • permission_handler: 위치 권한 요청/허용 처리 목적[^11]
  • geolocator: 위도/경도 등 위치 정보 획득 목적[^4]

3.3.2 HTTP 통신: Dio[^12]

서버와의 API 통신은 Dio를 사용해 구성했다고 한다.[^12] 뒤의 “주요 코드” 설명에서도 출근/퇴근 요청을 Dio로 날리는 코드가 핵심으로 제시된다.[^8]

3.3.3 상태관리: Provider(로그인 상태)[^13]

발표자는 Provider를 사용해 “상태, 로그인 상태 관리”를 했다고 말한다.[^13] 즉 앱에서 로그인 세션/로그인 여부 같은 상태를 Provider로 관리하는 구조다.[^13]

3.3.4 관리자 화면 UI를 위한 Form Builder: 들어갔지만 기능은 없음[^11]

흥미로운 포인트는 **flutter_form_builder(또는 폼 빌더)**를 넣은 이유다.[^11] 발표자는 원래 관리자 화면을 만들려고 했다고 말한다.[^11] 그 이유는 직원을 앱이 사용하려면 “직원을 등록해 줘야” 하기 때문에 일반 사용자 화면과 별도로 관리자용 직원 등록 화면이 필요하다고 판단했기 때문이다.[^11]

그는 관리자 화면을 “폼 빌더로 만들어서” UI를 구성하려 했고, 실제로 “관리자 화면 UI 제작은 돼 있긴 한데” 기능(로직/연동)을 전혀 만들지 않았다고 고백한다.[^11] 그래서 현재는 “라이브러리만 있는 상태”라고 정리한다.[^11]

[!WARNING] 기능 미완성의 구체적 범위 관리자 화면은 UI만 있고 직원 등록 기능/연동이 없어서 실사용 흐름(직원 등록 → 로그인/출퇴근) 중 “등록” 단계가 비어 있다.[^11]


3.4 출근 체크 API 요청(클라이언트) 구조: 유저ID + 날짜[^8]

📸 2:18

발표자는 “주요 코드”로 출근 체크 시의 API 요청 코드를 보여준다.[^8] 여기서 핵심은 출근 체크 요청 바디(body)에 무엇을 담아 보내는가이다.[^8]

그에 따르면 출근 체크를 할 때:

  • 유저 아이디(userId)
  • 해당 날짜(오늘 날짜)

를 바디에 담아서 서버로 전송한다.[^8] 서버는 이를 받아 “해당 직원의 아이디로” 출근 시간을 체크(기록)해 준다고 설명한다.[^8] 즉 서버는 userId를 기준으로 직원(사용자)을 특정하고, 날짜를 기준으로 “그날의 출근 기록”을 생성하거나 업데이트하는 구조로 이해된다.[^8]

또한 “체크아웃도 동일”하다고 먼저 말하며, 큰 틀에서는 출근과 퇴근 모두 비슷하게 API 요청/서버 저장 흐름을 가진다고 연결한다.[^8]


3.5 퇴근 체크(체크아웃) API 요청이 더 복잡한 이유: 날짜 매칭 + 3개 필드[^14]

📸 3:06

발표자는 퇴근 체크는 출근 체크보다 조건이 더 필요하다고 설명한다.[^14]

[? 질문] 퇴근 체크는 왜 유저 아이디만으로 처리하면 안 되는가[^14]
[= 답] 같은 유저라도 날짜별 출퇴근 기록이 다르므로, 단순히 userId만 보내면 “어느 날의 기록을 퇴근 처리할지”가 불명확해진다. 그래서 해당 날짜 조건이 함께 필요하다.[^14]

그래서 체크아웃 요청은 “그냥 유저 아이디로만 해서 시간 똑같이 체크할 수는 없어서” 날짜가 “똑같으면(같은 날이면)” 퇴근 시간을 별도로 기록하도록, 바디에 총 세 개를 담아 보내는 형태로 구성했다고 한다.[^14]

그가 언급한 체크아웃 요청에 포함되는 요소는 다음 흐름으로 제시된다:

  • 유저 아이디[^14]
  • 오늘 날짜(출근 기록과 매칭하기 위한 날짜)[^14]
  • (서버가 받는 시간) “국제 시간 포함한 형식”으로 받는 시간/타임스탬프[^15]

발표자는 서버가 “국제 시간표/국제 시간 포함한 형식으로 받는 시간이랑, 유저 아이디랑, 오늘 날짜”를 받으면 퇴근 시간 체크를 해준다고 말한다.[^15] 즉 서버는 (userId, date)를 키로 특정 레코드를 찾고, 그 레코드의 checkoutTime 같은 필드를 업데이트하는 방식으로 추정할 수 있다.[^15]


3.6 서버 구성 개요: 출퇴근 시간 기록 처리의 중심[^16]

📸 3:40

발표자는 “그런 식으로 서버는 구성되어 있다”고 말하며, 앞서 설명한 요청 바디 구조(출근/퇴근)와 서버의 역할을 요약한다.[^16] 핵심은 서버가 앱에서 보내는 userId/날짜/시간을 받아 DB에 기록하는 것이다.[^16]

이 맥락에서 앱은 단순히 로컬 시간을 저장하는 게 아니라, 서버와 통신해 기록을 중앙화함으로써:

  • 여러 직원 기록을 한 곳에 저장하고[^9]
  • 날짜별로 누적/조회가 가능하며[^9]
  • 로그인/권한 등 확장이 가능해진다[^13]

는 구조적 이점을 가진다(발표자의 설명 취지상).[^^9][^13]


3.7 출퇴근 체크의 ‘기준’을 시간에서 위치로 확장: 20m 거리 조건[^3]

📸 3:52

발표자는 출퇴근 체크 기준을 “그냥 시간만 체크하는 게 아니라” 위치 기반으로 체크되도록 만들었다고 강조한다.[^3] 즉 버튼을 누른다고 항상 기록되는 것이 아니라, “허용된 위치”라는 전제가 충족될 때만 출퇴근 체크가 되게 설계했다.[^4]

그는 코드 화면에는 일부만 나오지만, 실제 아이디어는 다음과 같다고 설명한다:

  1. 먼저 기준이 되는 위도/경도를 정한다.[^4]
  2. 발표자는 테스트를 위해 “회사” 대신 자기 집을 기준으로 놓고 실험했다고 말한다.[^4]
  3. 사용자의 현재 위치가 기준점 근방인지 판단하기 위해 거리 임계값을 둔다.[^4]
  4. 임계값은 예시로 20m를 사용했다.[^4]
  5. 기준점에서 20m보다 더 멀면 출퇴근 체크가 안 되고, 20m 안에 있어야 출퇴근 체크가 가능하도록 했다.[^4]

[? 질문] 위치 기반 출퇴근 체크에서 “20m”는 무엇을 의미하는가[^4]
[= 답] 기준점(회사/집의 위도·경도)과 사용자 현재 위치 사이의 거리가 20m 이내일 때만 출퇴근 체크 API 호출(또는 체크 처리)이 허용되는 임계값이다.[^4]

그는 위치 권한 허가가 필요하므로 permission_handler를 사용하고, 위치 획득은 geolocator로 수행한다고 다시 연결한다.[^4]

[!TIP] 위치 기반 체크를 MVP로 넣는 방식(발표자 흐름) 기준 위도/경도를 “회사 좌표”로 고정해 두고, 사용자 현재 좌표와의 거리 계산 결과가 임계값(예: 20m) 이내일 때만 체크 버튼 로직이 실행되도록 만든다.[^4]


3.8 DB에 출퇴근 기록이 쌓이는 모습과, DB를 쓴 이유(직원별/날짜별 누적)[^17]

📸 4:43

발표자는 “홍길동 씨가 출퇴근 체크를 하면” DB에 “출퇴근 기록이 된다”고 하며, 특정 사용자 예시(홍길동)를 들어 기록 누적을 설명한다.[^17] 즉 출퇴근 이벤트가 단발성 로그가 아니라 DB 테이블에 누적 저장된다는 점을 강조한다.[^17]

그리고 “DB를 굳이 쓴 이유”를 명확히 말한다.[^9] 출퇴근 앱은 단순히 출퇴근 버튼만 있는 게 아니라:

  • 직원들마다 출퇴근 기록이 “쌓여야” 하고[^9]
  • “회당(회사?) 날짜별로 볼 수 있어야” 한다[^9]

는 요구가 있기 때문이다.[^9] 즉 사용자별/날짜별 조회 요건이 DB 사용의 당위성이다.[^9]


3.9 키 설계 언급: Primary Key id와 다른 테이블의 Foreign Key 연결[^18]

📸 5:25

발표자는 DB 설계의 핵심으로 키 관계를 짧게 언급한다.[^18] 그는 “프라이머리 키가 id라서 다른 테이블에서는 이 id를 포랭키(Foreign key)로 받아서 쓴다”고 말한다.[^18] 즉 직원(유저) 테이블의 기본키 id를 출퇴근 기록 테이블 등에서 참조키로 사용하여 관계를 맺는 방식이다.[^18]

또한 그는 “아이디 값으로… 유저네임 관련된 아이디 값으로 받아서… 정보 처리를 하고 있는 상태”라고 설명하며, 실무적으로는 userId를 기준으로 여러 테이블을 조인하거나 참조하여 기록/사용자 정보를 연결하는 형태임을 시사한다.[^18]


3.10 로그인/직원 등록의 현재 상태: 로그인 서버 구현은 했지만 등록은 수동 입력[^19]

📸 5:46

발표자는 마지막에 로그인과 직원 등록을 분리해 현재 구현 상태를 설명한다.[^19]

  • 로그인도 마찬가지로 서버는 구현했다.[^19]
  • 하지만 직원 등록 부분 구현을 안 했다.[^19]

그래서 “만약 직원을 등록해야 된다면” DB에 “직접 수동으로 입력을 해줘야 되는 상태”라고 말한다.[^19] 즉 관리자 UI/기능 미구현의 결과가 운영 프로세스(직원 추가)에서 수동 작업으로 전가되어 있다는 뜻이다.[^19]

또한 비밀번호 처리는 bcrypt를 써서 해싱 처리했다고 말한다.[^20] 즉 평문 저장이 아니라 해시 기반 저장을 적용해 기본적인 보안 조치를 취했다는 점을 공유한다.[^20]

[!IMPORTANT] 인증 파트의 구현 포인트 직원 등록 기능은 빠져 있어 계정을 DB에 수동 생성해야 하지만, 비밀번호는 bcrypt로 해싱 처리하여 저장하도록 했다.[^19]


4. 핵심 통찰[^1]

  1. [c 출퇴근 앱의 MVP는 “체크 버튼 + 서버 통신 + DB 누적”만으로도 성립한다.] 발표자는 기능이 “하나뿐이라 초라”하다고 말하지만, 실제로 출근/퇴근의 핵심 가치는 서버에 시간 기록을 남기고 누적하는 데 있다.[^10]

    • 실행: 출근/퇴근 각각에 대해 “요청 바디에 무엇을 담을지(userId, date, time)”부터 먼저 고정한다.[^8]
  2. [h 퇴근 체크는 출근보다 식별 조건이 더 필요해진다.] userId만으로는 날짜별 레코드를 특정하기 어려워 날짜 조건(오늘 날짜) 등 추가 필드가 필요하다는 점을 명시적으로 드러낸다.[^14]

    • 실행: (userId, date)를 사실상 키로 보고, checkout 업데이트 API를 분리하거나 명확히 설계한다.[^15]
  3. [h ‘시간 기록’에 ‘위치 조건’을 더하면 부정 체크를 줄일 수 있다.] 20m 임계값을 두어 기준점 근처에서만 체크되게 만들었다는 점이 신뢰성 확보 장치로 제시된다.[^4]

    • 실행: permission_handler로 권한 → geolocator로 위치 → 거리 계산 → 임계값 통과 시만 API 호출 흐름을 구성한다.[^4]
  4. [m 라이브러리는 “현재 구현”뿐 아니라 “계획했던 기능”의 흔적을 남긴다.] Form builder는 관리자/직원등록을 만들려는 계획의 산물이지만, 기능 미구현으로 라이브러리만 남았다고 말한다.[^11]

    • 실행: 다음 단계에서 관리자 기능을 붙일 계획이라면 UI가 아닌 “등록 API/DB 스키마/권한 모델”부터 우선 구현한다.[^11]
  5. [m DB 도입의 이유는 ‘저장’ 자체가 아니라 ‘직원별·날짜별 조회 가능성’이다.] 출퇴근 데이터는 누적되고 조회되어야 하므로 DB가 필요하다는 문제 정의가 분명하다.[^9]

    • 실행: 출퇴근 테이블에 userId(FK)와 date 컬럼을 두고 조회 인덱스를 고려한다(발표자가 키 관계를 언급한 맥락).[^18]

5. 헷갈리는 용어 정리[^11]

  • Flutter: 모바일 앱 UI/로직을 구현한 프레임워크(발표자의 앱 개발 환경).[^7]
  • Node.js: 출퇴근 체크 요청을 처리하는 서버 런타임/환경.[^7]
  • MariaDB: 직원별 출퇴근 기록을 누적 저장하는 관계형 데이터베이스.[^7]
  • Dio: Flutter에서 HTTP API 요청을 보내기 위해 사용한 라이브러리.[^8]
  • Provider: Flutter 상태관리 도구로, 발표자는 로그인 상태 관리를 위해 사용.[^13]
  • permission_handler: 위치 권한 요청/허용 처리를 위해 사용한 라이브러리.[^11]
  • geolocator: 위도/경도 등 위치 정보를 가져오고 거리 계산 기반 기능에 활용 가능한 라이브러리(발표자 사용 맥락).[^4]
  • (Flutter) Form Builder: 폼 기반 입력 UI를 쉽게 만들기 위한 도구로, 관리자 화면(직원 등록) UI 제작 목적으로 도입했으나 기능은 미구현.[^11]
  • Primary Key / Foreign Key: 유저 테이블의 id(PK)를 다른 테이블에서 참조(FK)해 관계를 맺는 DB 키 개념.[^18]
  • bcrypt: 비밀번호를 해싱 처리하는 알고리즘/라이브러리로 발표자는 비밀번호 저장에 사용.[^20]


참고(콘텐츠 정보)[^1]

  • 제목: 출퇴근 체크 앱 | 오름캠프 Flutter 모바일 앱 개발 과정 1기 프로젝트[^1]
  • 채널: 모두의연구소[^1]
  • 길이: 6분 13초[^1]
  • 링크: https://www.youtube.com/watch?v=CMjKdIZZ2AM[^1]
  • 키워드(제공): 오름캠프, Flutter, 앱만들기, 앱개발, 앱 개발자, 앱 개발 프로젝트, 부트캠프 프로젝트, 모두의연구소, 부스트캠프, 엘리스코딩[^1]

[^1]: @[00:00]~@[00:26] “출퇴근 애플리케이션을 제작… 하나씩 넘기면 설명… 시안 영상…” + 사용자가 제공한 메타데이터(제목/채널/길이/링크/키워드). [^2]: @[00:31] “출근 체크를 누르면… 서버랑 통신… 잠깐 기다려야… 체크… 퇴근도 체크… 기능이 여기까지” [^3]: @[03:52] “출퇴근의 기준이… 시간만 체크하는게 아니라… 위치 기반으로 체크” [^4]: @[04:03]~@[04:32] “위도 경도를 처음에 정해요… 집을 기준… 20m… 20m 더 멀면… 안 되고… 20m 안에 있어야… 가능” [^5]: @[00:45]~@[00:52] “공지사항은 구현… 서버 구현을 제대로 안 했어… 껍데기” [^6]: @[00:45] 공지사항 껍데기 언급 + @[01:36]~@[01:47] 관리자 화면/직원등록 기능 미구현 + @[05:46] 직원 등록 미구현으로 수동 입력. [^7]: @[00:52] “개발 환경은 플러터… 서버는 노드JS… 디비는 마리아디비” [^8]: @[02:18]~@[02:38] “출근 체크… 디오… API 요청… 유저 아이디… 해당 날짜… 바디에 담아서 보내요” [^9]: @[04:55]~@[05:19] “DB… 굳이… 직원들마다 출퇴근 기록이 쌓여야… 날짜별로 볼 수 있어야…” [^10]: @[01:07]~@[01:14] “출근 시간 체크 기능이 제일 주요 기능… 하나만 넣었는데… 초라해” [^11]: @[01:24]~@[01:47] “지오로케이터… 퍼미션 핸들러… 플랫트폼 빌더… 관리자 화면… 직원 등록… UI 제작… 기능 전혀… 라이브러리만” [^12]: @[01:47]~@[02:03] “HTTP 통신… 디오…” [^13]: @[02:03] “프로바이더 써서… 로그인 상태 관리를” [^14]: @[03:06]~@[03:22] “체크아웃… 유저 아이디로만… 시간 똑같이 체크할 수는 없어서… 해당 날짜… 총 세 개를 바디에…” [^15]: @[03:22] “국제 시간 포함한 형식으로 받는 시간이랑… 유저 아이디… 오늘 날짜… 퇴근시간 체크” [^16]: @[03:40] “그런 식으로 서버는 구성” [^17]: @[04:43] “홍길동 씨가 출퇴근 체크를 하면… 디비에 출퇴근 기록” [^18]: @[05:25] “프라이머리 키가 id라서… 다른 테이블… 포랭키로… 아이디 값으로… 정보 처리를” [^19]: @[05:46] “로그인도… 서버는 구현… 직원 등록 부분 구현을 안 해… 수동으로 입력” [^20]: @[06:09] “비밀번호… 해싱… 비크립트(bcrypt)… 해싱 처리”

← 프로젝트에서 보기