Files
arasystem/flutter
ttipo 35fcf5e4ef feat(flutter): 앱 로그인 · 토큰 갱신 · 모듈 라우터 기반을 만든다
앱 1단계의 바탕이다(계약 1절 "앱 로그인" — 서버는 다음 차례, 지금은 가짜 서버로 시험한다).

- 토큰: 리프레시 토큰은 보안 저장소에만, 액세스 토큰은 메모리에만. 첫 실행 표시가 없으면 남은 토큰을
  지우고, 안드로이드 백업을 끈다. 보안 저장소 일은 차례로 한다
- 인터셉터: 401 이면 갱신 한 번(동시 401 묶음) 뒤 다시 보내기, AUTH_010 은 me 를 다시 받아 그 단계로,
  미리 갱신 30초 전 · 만료는 응답 Date 로, 세션 세대가 바뀐 뒤의 응답은 조용히 버린다,
  비밀이 든 요청의 실패 예외는 본문 · Bearer 를 지운다
- 세션: 로그인 · 코드 · 새 비밀번호 · 2단계 켜기 단계의 상태와 늦은 결과 버리기(버린 로그인의 토큰은
  조용히 logout), 로그아웃은 메모리부터 비운다, 유예 응답이면 세션 끝 + 조용한 logout
- 모듈 틀: go_router StatefulShellRoute 와 아래 탭 막대 — 모듈 목록은 웹 app-modules.ts 와 짝,
  첫 판은 [메일 · 설정]. 모듈끼리 import 하지 않는다
- Riverpod 자동 다시 하기를 끈다, Pretendard 1.3.9(OFL), 확정 글자 크기, 시험 126건
- 판은 Flutter 3.41.9(Dart 3.11.5) — pubspec 의 sdk 하한과 lock 을 함께

리뷰 세 번 반영(AUTH_010 다시 보내기 · 늦은 응답 · 저장 실패 · 로그아웃 차례 · 비밀이 실린 객체 등).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 11:11:28 +09:00
..
2026-10-26 12:28:48 +09:00
2026-10-26 12:28:48 +09:00
2026-10-26 12:28:48 +09:00
2026-10-26 12:28:48 +09:00

ARA SYSTEM 앱 (Flutter)

ARA SYSTEM 그룹웨어의 모바일 앱이다. 식별자 kr.it.arasystem.app · 이름 "ARA SYSTEM", 안드로이드 먼저(Play 내부 테스트).

이 문서는 개발 PC · 배포에서 알아 둘 사실만 적는다.

개발 실행

cd flutter
cp .env.example .env      # 없으면 analyze 가 asset 경고를 낸다
flutter pub get
flutter analyze           # 0건이어야 한다
flutter test
flutter run               # 에뮬레이터 · USB 실기기
  • API 주소는 .env 의 API_URL 하나다(루트 env 를 따르지 않는다). .env 는 앱 안에 그대로 들어가므로 비밀은 넣지 않는다.
    • Android 에뮬레이터: http://10.0.2.2:3000/api — 에뮬레이터의 10.0.2.2 가 PC 의 127.0.0.1 이다.
    • USB 실기기: adb reverse tcp:3000 tcp:3000 뒤 http://localhost:3000/api. 개발 백엔드는 127.0.0.1 에만 열려 있어 같은 와이파이의 휴대폰은 닿지 않는다(docker 스킬 "호스트 포트").
  • 개발 PC 의 안드로이드 도구: Android Studio 의 JDK 21 · SDK 36 · NDK 28.2(Flutter 기본)로 빌드된다. SDK 라이선스 동의(flutter doctor --android-licenses)는 사람이 직접 한다.

Flutter 판

3.41.9 stable (Dart 3.11.5) 으로 고정이다 — 개발 PC 와 CI 가 같은 판이어야 한다. 판을 바꿀 때 함께 고치는 곳:

  • .github/workflows/ci-flutter.yml 의 flutter-version (CI 는 flutter pub get --enforce-lockfile — lock 그대로)
  • pubspec.yaml 의 environment.sdk, intl(flutter_localizations 가 SDK 마다 정확한 판으로 고정한다 — 새 SDK 의 packages/flutter_localizations/pubspec.yaml 값으로)
  • .claude/skills/docker/SKILL.md 이미지 버전 표의 "Flutter SDK" 줄 · flutter 스킬 "Flutter 판" · 이 문서
  • 개발 PC 의 Flutter 는 다른 프로젝트도 함께 쓴다 — 올리기는 사용자가 정한다.

빌드

flutter build apk --debug          # 2~3분 · 약 160MB (build/ 는 git 이 무시한다)
flutter build appbundle --release  # Play 에 올리는 꼴 — 아래 서명 키가 먼저다
  • release 매니페스트(android/app/src/main/AndroidManifest.xml)에 INTERNET 권한이 있어야 네트워크가 된다(flutter create 기본은 debug · profile 에만 둔다).

배포 준비

  • release 서명 키는 저장소 밖의 비밀이다. 지금 build.gradle.kts 의 release 는 debug 키로 서명한다 — 배포 전에 업로드 키(keystore)를 만들고 key.properties 로 읽게 한다. keystore · key.properties 는 커밋하지 않고(android/.gitignore 가 무시한다) 비밀번호 관리자에 둔다(잃으면 같은 앱으로 업데이트를 올릴 수 없다 — Play 앱 서명을 켜 두면 업로드 키는 다시 받을 수 있다).
  • 안드로이드: Play 내부 테스트 — Play Console 계정(1회 25달러, 법인 계정은 D-U-N-S 번호), 테스터 100명까지 메일 목록으로 · 심사 없이 · 업데이트가 저절로. 식별자(kr.it.arasystem.app)는 올린 뒤 바꿀 수 없다. APK 를 직접 나눠 주는 길도 있지만 Google 의 "인증된 개발자만 설치" 정책(2026-09 부터 나라별)이 한국에 언제 걸리는지 확인이 필요하다.
  • iOS: 맥 · Apple 개발자 계정이 먼저다 — 빌드 · 서명 · 시뮬레이터가 모두 맥(Xcode)에서만 되고, 배포 · 푸시(APNs 키)는 Apple Developer Program(연 99달러, 법인은 D-U-N-S)이 든다. 내부 배포는 TestFlight 내부 테스터(빌드 90일 만료). 맥에서 처음 빌드할 때 ios/Runner/Info.plist 에 CFBundleLocalizations(ko)가 있는지 확인한다 — 없으면 복사 · 붙여 넣기 같은 시스템 글이 한국어로 나오지 않는다. bundle id 는 kr.it.arasystem.app 으로 맞춰 두었다(맥에서 확인 전).

보안 메모

  • 토큰: 리프레시 토큰만 flutter_secure_storage(Android Keystore · iOS Keychain), 액세스 토큰은 메모리. 비밀번호 · 토큰을 로그에 남기지 않는다(LogInterceptor 를 넣지 않는다).
  • Android 백업을 껐다(android:allowBackup="false" · res/xml/data_extraction_rules.xml) — 보안 저장소의 값은 이 기기의 Keystore 키로 암호화되어 다른 기기로 복원되면 읽지 못하고, 첫 실행 표시까지 복원되면 남은 토큰 지우기가 돌지 않는다.
  • iOS Keychain 은 앱을 지워도 남는다 — 일반 저장소(shared_preferences)의 첫 실행 표시가 없으면 앱을 띄우기 전에 보안 저장소를 모두 지운다(core/storage/token_store.dart 의 prepare).
  • 만료는 기기 시계로 보지 않는다 — 응답의 Date(서버 시각)로 남은 시간을 잰다.

나중에 넣을 때 주의

  • build_runner(freezed · json_serializable · riverpod_generator): 같은 폴더의 lib/core/api/error_lexicon.g.dart(백엔드 레지스트리에서 만든 사전 — 직접 고치지 않는다)를 build_runner 가 자기 출력으로 보고 지울 수 있다. 넣을 때는 build.yaml 의 generate_for 를 모델 폴더로 좁히고, 처음 한 번 돌린 뒤 사전이 그대로인지(git diff) 확인한다.
  • 첨부 받기(스트림 · 바이트 응답): responseType 이 bytes · stream 이면 실패 응답의 본문도 JSON 으로 풀리지 않아 toApiException 이 HTTP 상태로만 코드를 정한다(FILE_007 · FILE_008 같은 코드를 잃는다). 받기를 만들 때 실패 본문(작다)을 끝까지 읽어 UTF-8 · JSON 으로 풀어 표준 실패의 error.code 를 꺼낸 뒤 코드를 정한다. 성공 본문은 지금처럼 풀지 않고 그대로 넘긴다(시험이 지킨다).

라이선스

글꼴 Pretendard 1.3.9(SIL OFL 1.1 — assets/licenses/pretendard-ofl.txt)는 앱의 [설정] → [앱 정보] → [오픈소스 라이선스]에 보인다.