· case-study · 2 min read

Flutter·Live2D 캐릭터 생산성 앱 개발 사례

Flutter 앱 안에 Live2D WebView를 넣고, Firebase, FCM, NFC, 백그라운드 타이머, 미디어 콘텐츠, 앱 심사 대응까지 제품 수준으로 정리한 하이브리드 모바일 앱 개발 사례.

Flutter·Live2D 캐릭터 생산성 앱 개발 사례

한 문장 요약

Flutter로 만든 캐릭터 기반 생산성 앱을 개발했습니다. 핵심은 예쁜 캐릭터 화면이 아니라, Live2D WebView, Firebase, FCM, NFC, 집중 타이머, 미디어 콘텐츠, 앱 심사 대응이 서로 충돌하지 않게 제품 구조로 묶는 일이었습니다.

이 프로젝트는 하이브리드 모바일 앱의 전형적인 난도를 보여줍니다. 빠르게 기능을 붙일 수 있지만, 생명주기, 권한, 심사, 서버 데이터, 외부 API가 얽히는 순간 복잡도가 급격히 올라갑니다.

Flutter 안의 Live2D WebView

첫 번째 큰 결정은 Live2D 렌더링을 Flutter 위젯으로 직접 구현하지 않는 것이었습니다. Live2D Cubism Web SDK는 TypeScript와 WebGL 기반으로 안정적으로 동작하고 있었고, Flutter는 모바일 앱의 상태, 네비게이션, 알림, 타이머, 권한을 담당하는 데 적합했습니다.

그래서 구조를 두 레이어로 나눴습니다.

  • Flutter: 앱의 제품 경험, 상태 관리, 인증, 알림, 타이머, 네이티브 권한
  • Live2D WebView: 캐릭터 렌더링, 모션 재생, 터치 반응

두 레이어는 JavaScript bridge로 연결했습니다. Flutter가 WebView 로딩 이후 캐릭터 목록과 현재 캐릭터를 전달하고, WebView는 모델 로드와 터치 이벤트, 모션 상태를 다시 Flutter로 알려줍니다.

하지만 WebView 통합은 단순히 웹 페이지를 넣는 일이 아니었습니다. 앱 시작 시점, onPageFinished 타이밍, 로컬 에셋 서버, CORS, iOS 로컬 네트워크 허용, JavaScript 함수 준비 여부가 모두 실패 지점이 됐습니다. WebView 로딩 완료와 Live2D 런타임 준비 완료는 같은 의미가 아니었습니다.

결국 가장 중요한 것은 브리지 함수가 아니라 준비 상태 계약이었습니다. Flutter가 너무 빨리 명령을 보내면 런타임이 아직 준비되지 않아 실패합니다. 서버에서 캐릭터 모델 경로가 늦게 도착하면 기본 모델에서 실제 모델로 교체하는 폴백 전략도 필요합니다.

캐릭터를 코드가 아니라 데이터로 운영하기

캐릭터 앱에서 캐릭터는 장식이 아닙니다. 캐릭터가 늘어나고, 기본 캐릭터가 바뀌고, 테마와 설명, 레벨, 리소스 경로가 바뀌면 매번 앱 업데이트를 낼 수 없습니다.

그래서 캐릭터 로딩은 세 단계 우선순위로 설계했습니다.

  1. 서버 설정: Firestore에서 캐릭터 순서와 기본 캐릭터를 읽음
  2. 로컬 폴백: 네트워크 실패 시 앱 내부 기본값 사용
  3. SDK 폴백: WebView 초기 로딩 순간에도 빈 화면 방지

이 방식은 안정화뿐 아니라 운영 구조가 됐습니다. 캐릭터는 테마 색상, 썸네일, 설명, 레벨, 경험치, 보유 여부를 함께 갖습니다. 사용자가 캐릭터를 바꾸면 화면의 색상과 상호작용 상태도 함께 바뀝니다. 캐릭터 앱에서는 데이터 모델이 사용자 애착의 구조가 됩니다.

Flutter-live2d 캐릭터 육성, 생산성 앱 개발

One Thing Todo와 미션 루프

이 앱의 Todo는 많은 할 일을 관리하는 도구가 아닙니다. 하루에 하나만 정하는 One Thing Todo에 가깝습니다. 캐릭터 앱에서 Todo가 복잡해지면 사용자는 관리 도구를 쓰는 느낌을 받습니다.

그래서 Todo 입력, 완료, 기분 선택, 축하 화면을 하나의 흐름으로 만들었습니다. 완료 후에는 단순 체크 표시가 아니라 사용자가 그 일을 해낸 기분을 선택합니다. 이 정보는 이후 푸시 메시지와 미션 상태의 문맥으로 이어집니다.

미션 시스템도 같은 루프에 연결됩니다. 캐릭터와 상호작용하기, 오늘 할 일 완료하기, 외부 상점 둘러보기 같은 일일 미션은 완료, 수령 가능, 수령 완료 상태를 가집니다. 여러 기기에서 중복 보상을 받지 않도록 서버 함수와 중복 방지 키를 사용했습니다.

FCM, 타이머, NFC

캐릭터 앱이 제품이 되려면 사용자가 앱을 열지 않은 시간도 고려해야 합니다. 개인화 푸시는 FCM, 서버 크론, 중복 방지, 알림 탭 복구 흐름을 함께 설계해야 했습니다. 단순히 메시지를 보내는 것이 아니라, 어떤 이벤트가 어떤 사용자에게 한 번만 도달해야 하는지를 정의해야 합니다.

집중 타이머는 더 까다로웠습니다. iOS와 Android는 백그라운드 실행 정책이 다릅니다. iOS에서는 Live Activity나 알림 중심의 설계가 필요하고, Android에서는 foreground service, exact alarm, full-screen intent 같은 권한 이슈가 따라옵니다. 타이머 기능은 UX보다 플랫폼 정책을 먼저 확인해야 하는 영역이었습니다.

NFC 태그도 마찬가지였습니다. 태그를 읽고 콘텐츠를 여는 기능 자체는 간단해 보이지만, 앱 설치 전후 딥링크, iOS/Android의 태그 처리 차이, 오프라인 상태, 잘못된 태그 데이터까지 고려해야 합니다. 스마트 굿즈와 앱 콘텐츠를 연결하려면 기능보다 플랫폼 제약을 먼저 봐야 했습니다.

Firebase와 관리자 CMS

운영 데이터는 Firebase 중심으로 정리했습니다. Auth, Firestore, Storage, Messaging, Functions가 앱 운영의 기본 축이 됐습니다. 캐릭터, 대사, 푸시, NFC 콘텐츠, 포인트 보상처럼 자주 바뀌는 값은 앱 바이너리 밖에서 관리해야 했습니다.

그래서 관리자 CMS도 함께 설계했습니다. 관리자 기능이 없으면 모바일 앱 업데이트가 운영 도구가 됩니다. 캐릭터 설명을 바꾸거나, 대사를 수정하거나, 푸시 문구를 조정하거나, NFC 콘텐츠를 바꾸는 일마다 앱 심사를 다시 거칠 수는 없습니다.

외부 포인트 API 연동도 클라이언트에서 직접 호출하지 않았습니다. 인증 정보가 노출되고 중복 지급을 막기 어렵기 때문입니다. 앱은 미션 상태를 기록하고, 서버 함수가 중복 방지 키를 검증한 뒤 외부 API를 호출하는 구조로 정리했습니다.

Face Tracking과 Unity PoC를 제품에서 분리하기

iOS에서는 ARKit Face Tracking을 사용해 사용자의 고개 방향과 표정을 Live2D 파라미터로 전달하는 실험도 진행했습니다. ARFaceAnchor에서 transform과 blendshape 값을 추출하고, Flutter EventChannel을 거쳐 WebView의 JavaScript bridge로 보낸 뒤, Live2D 모델의 머리, 눈, 입 파라미터에 반영하는 구조입니다.

실제 구현에서는 축 이름보다 기기 테스트가 중요했습니다. 일반적으로 pitch와 yaw라고 부르는 값이 Live2D 모델에서 기대한 방향과 다르게 움직였고, 사용자는 캐릭터와 마주 보고 있으므로 좌우 반전도 필요했습니다. WebView에 JavaScript를 너무 자주 호출하면 성능이 떨어져 업데이트 빈도도 조절했습니다.

3D 확장을 위한 Unity PoC도 진행했습니다. Unity 6 기반 3D 씬을 Flutter 앱 안에 임베드하고, 향후 face tracking 데이터를 blendshape로 연결하는 방향을 검토했습니다. PoC는 빌드에 성공했지만, 제품 반영 단계에서는 심사와 런타임 안정성이 더 중요했습니다. 사용하지 않는 Unity 관련 프레임워크가 iOS 심사에서 문제가 될 수 있었고, Flutter 엔진 재진입 문제도 있었습니다.

최종 판단은 PoC 성공과 제품 반영을 분리하는 것이었습니다. 가능성은 확인했지만, 현재 제품에는 안정적인 Flutter와 Live2D 구조를 유지했습니다. 실험이 성공했다고 해서 출시 코드에 반드시 남겨야 하는 것은 아닙니다.

심사와 릴리즈 문서화

모바일 앱 개발에서 구현 완료와 출시 가능은 다릅니다. 로컬 기기에서 잘 동작해도 App Store와 Google Play 심사를 통과하려면 권한, 프라이버시 설명, 네이티브 프레임워크, 디바이스 지원 조건, 버전 코드, 심사 메모가 맞아야 합니다.

iOS에서는 사용하지 않는 네이티브 프레임워크도 빌드 산출물에 포함되어 있으면 심사 대상이 됩니다. Face Tracking 기능은 TrueDepth API 사용 목적을 명확히 설명해야 했고, 얼굴 데이터를 저장하거나 서버로 보내지 않는다는 점도 문서화해야 했습니다.

Android에서는 타이머 기능 때문에 foreground service, full-screen intent, exact alarm 같은 민감 권한 설명이 필요했습니다. 패키지가 Manifest에 자동으로 병합한 권한도 앱의 권한으로 간주되므로, 어떤 의존성이 어떤 권한을 추가하는지 확인해야 했습니다.

이 모든 과정을 관리 가능하게 만든 것은 문서였습니다. 기능 설계뿐 아니라 심사 거절, 권한 선언, iOS 설정, Android 설정, 푸시 오류, 미디어 오류, NFC 설정, 서버 연동 기록을 남겼습니다. 릴리즈 문서는 개발 기록이 아니라 다음 배포를 안전하게 만드는 운영 도구입니다.

프로젝트에서 얻은 것

하이브리드 구조의 준비 상태 계약

Flutter와 WebView를 연결할 때 중요한 것은 명령을 주고받는 함수가 아니라 언제 명령을 보내도 안전한지 정의하는 것입니다.

운영 데이터는 앱 밖으로

캐릭터, 대사, 푸시, NFC 콘텐츠, 포인트 정책처럼 자주 바뀌는 값은 서버와 CMS에서 관리해야 합니다. 그래야 앱 업데이트가 운영 도구가 되지 않습니다.

PoC와 제품 코드는 다르다

Face Tracking과 Unity 실험은 의미가 있었지만, 출시 코드에는 안정성이 우선입니다. 성공한 실험도 제품에 넣지 않는 결정이 필요할 수 있습니다.

자주 받는 질문

Flutter 앱 안에 Live2D를 넣는 방식이 안정적인가요?
가능합니다. 다만 WebView 로딩 완료와 Live2D 런타임 준비 완료를 구분하고, 명령 전송 타이밍과 폴백 모델 전략을 명확히 해야 합니다.

NFC나 백그라운드 타이머는 앱 심사에 영향을 주나요?
영향을 줄 수 있습니다. iOS와 Android 모두 권한 설명과 실제 사용 목적이 중요합니다. 특히 Android는 의존성이 자동으로 추가한 권한도 확인해야 합니다.

Unity PoC를 했는데 왜 제품에는 넣지 않았나요?
기술 가능성은 확인했지만, 현재 제품의 안정성과 심사 리스크를 고려하면 제거하는 편이 더 낫다고 판단했습니다. 모바일 제품에서는 빼는 결정도 중요한 기술 결정입니다.


캐릭터 앱, 생산성 앱, Flutter 기반 하이브리드 앱, NFC/푸시/미디어 기능이 필요한 모바일 제품을 준비 중이시라면 프로젝트 문의 로 핵심 기능과 심사 대상 권한을 알려주세요. 초기 설계에서 플랫폼 제약까지 함께 검토하는 편이 출시 리스크를 크게 줄입니다.

Hammergrid Lab은 Flutter, Firebase, WebView, Live2D, NFC, 푸시 알림을 결합한 모바일 제품을 설계하고 개발합니다.

관련 서비스

3d웹, 3dweb, 3d 컨피규레이터, 3D 제품 컨피규레이터, 3D 제품 뷰어, WebGPU, Three.js

English version: Building a Flutter + Live2D Character Productivity App

← 인사이트로

프로젝트 문의