[교정일기] 앱 출시 1년 만에 대규모 업데이트

교정일기 앱 소개

 

교정일기는 치아교정 중인 사용자들이 자신의 치아교정 과정을 한 곳에서 통합적으로 관리할 수 있도록 만든 앱이다.

 

대표적으로 제공하는 기능은 다음과 같다.

  • 교정 시작일로부터의 D-Day 확인
  • 타임라인 탭에서 날짜별로 치료 내용 또는 치아 상태를 기록
  • 앨범 탭에서 치아 사진을 업로드해서 갤러리 형태로 확인
  • 치아 사진 두 장을 선택해서 콜라주 형태의 비교 이미지 생성

 

앱 출시 과정 및 성과

이 앱은 내가 실제로 치아교정을 시작하면서 느낀 불편함에서 출발했다. 교정을 하면서 치료 내용은 메모 앱에, 치아 사진은 갤리리앱에서 따로 관리하는 게 불편하게 느껴져서 치아교정을 하나의 앱에서 관리할 수 있다면 좋겠다는 생각으로 개발을 시작했다.

 

앱을 만들기 위해 노마드 코더에서 Dart, Flutter 강의를 수강했고, 도서관에서 Flutter 개념서를 빌려서 기본 개념을 공부하기도 했다. 스토어 출시 목적으로 앱을 만드는 것은 처음이었지만, 내가 실제로 사용하고 싶은 앱이었기 때문에 사용자 입장에서 기능을 고민하며 즐겁게 개발했던 기억이 있다.

 

개발은 2024년 5월에 시작했고, 생각날 때마다 기능을 조금씩 추가하다 보니 2024년 11월에 개발을 마무리했다. 비공개 테스트를 거친 후에 2024년 12월 2일에 프로덕션으로 정식 출시했다.

 

출시 후 약 1년 3개월이 지난 현재 다음과 같은 성과를 달성했다.

엄청나게 많은 사용자 수는 아니지만, 내가 만든 앱을 지속적으로 사용해 주시는 분들이 있다는 사실이 너무 신기하고 감사하다.

 

  • 전체 설치 수: 380
  • 회원가입 사용자: 약 200명
  • DAU: 약 10명

 

v1.1.0 대규모 업데이트

플레이스토어의 정책 업데이트(16KB 대응 등)를 제외하면 마지막 기능 업데이트는 2025년 6월이었다.

 

그동안 개선하고 싶은 부분이 정말 많았지만 취업 준비와 다른 프로젝트 작업 때문에 계속 우선순위가 뒤로 밀렸다. 그러다가 바쁜 일이 어느 정도 마무리되면서, 그동안 앱을 이용하면서 가장 불편하다고 생각했던 앨범 탭을 중심으로 대규모 업데이트를 진행했다.

 

앨범 탭 갤러리 뷰로 개편 및 로드 개선

기존 앨범 탭에서는 월별로 사진을 묶어 보여주고, 각 월 안에서 사진을 가로 스크롤로 확인해야 했다.

 

하지만 이 구조는 원하는 사진에 바로 접근하기 어렵고, 사진 수가 많아질수록 사용성이 떨어지는 문제가 있었다. 그래서 기본 갤러리 앱과 유사한 그리드 레이아웃으로 앨범 탭을 전면 개편했다. 이제 사용자의 기기 화면 너비에 따라 한 줄에 최소 3장의 사진이 표시되는 갤러리 형태로 사진을 확인할 수 있다.

 

  final ScrollController _scrollController = ScrollController();

  void _onScroll() {
    if (_scrollController.position.pixels >=
        _scrollController.position.maxScrollExtent - 300) {
      teethPhotoController.fetchMoreTeethPhotoData();
    }
  }

  @override
  void initState() {
    super.initState();
    _scrollController.addListener(_onScroll);
    teethPhotoController.initAlbumTabData();
  }

또한 사용자의 사진 데이터가 많아질 경우 로딩 시간이 길어질 수 있다는 점을 고려해서 무한 스크롤 방식의 지연 로딩 구조를 적용했다. 사진 데이터를 15개 단위로 나누어서 순차적으로 로드하기 때문에, 사용자가 앨범 탭에 진입했을 때 초기 로딩 부담을 줄이고 보다 빠르게 화면을 확인할 수 있다.

 

치아사진 전체 화면 조회 및 다운로드 기능 추가

기존에는 특정 사진을 클릭하면 하단에서 Bottom Sheet가 올라오는 방식으로 사진을 확인할 수 있었다. 하지만 실제로 사용하다 보니 영역이 제한되어 있어 답답하게 느껴졌고, 사진을 자세히 확인하기도 어려웠다. 그래서 사진 조회 UI를 Full Screen 형태로 전면 개편했다.

 

이제 전체 화면에서 사진을 확대/축소하거나, 좌우 스와이프로 다음 사진으로 넘어가는 등 인터랙션을 통해 더욱 편리하게 사진을 조회할 수 있다. 또한 앨범 탭을 개선하는 과정에서 사진을 기기에 다운로드할 수 있는 기능까지 추가했다.

 

타임라인, 앨범 탭 데이터 정렬 기능 및 로딩 구조 개선

기존에는 타임라인과 앨범 데이터가 항상 최신순으로만 정렬되어 제공됐는데, 교정 과정을 처음부터 순서대로 보고 싶은 사용자도 있을 수 있다고 판단해 정렬 옵션 기능을 추가했다. 앱 바에 정렬 버튼을 누르면 하단에서 Bottom Sheet가 올라와서 최신순 혹은 오래된순 정렬을 선택할 수 있다.

 

또한 데이터 로딩 구조까지 최적화했다. 기존에는 앱 실행 시 타임라인, 치아사진 데이터를 모두 불러와서 이를 홈 탭의 최신 데이터를 보여주는 데에도 사용했다. 그런데 정렬 기능을 추가하면서 홈 탭의 데이터와 각 탭의 데이터 로딩을 분리했기 때문에 앱 실행 시 불필요하게 모든 데이터를 불러올 필요가 없어졌다. 그래서 각 탭의 데이터는 그 탭에 들어갔을 때 불러오도록 Provider 로직을 개선해서 불필요한 데이터 로딩을 줄였다.

 

애드몹 네이티브 광고 배너 추가

교정일기는 이미지 업로드를 위해 AWS S3, API Gateway, Lambda를 사용하는데, 사용자 데이터도 점점 쌓여가면서 AWS에서 적게나마 비용이 발생하기 시작했다.

 

그래서 앱의 지속적인 유지를 위해 이번 업데이트에 광고 배너를 추가했다. 광고를 추가하면서 사용자의 앱 이용 경험을 해치지 않는 것에 집중했고, 이를 위해 커스텀이 가능한 네이티브 배너 광고를 이용해서 전체적인 UI에서 이질감을 느끼지 않도록 노력했다. 

 

사실 광고를 추가하고 나서도 큰 돈은 되지 않지만, 내가 만든 앱에서 무언가 물질적인 보상이 발생하고 있다는 점에서 이 앱을 계속 유지보수하고 발전해 나가고 싶다는 동기부여가 되어주는 거 같다.

 

GA 이벤트 태그 연동 추가

class AnalyticsEvents {
  /// 로그아웃
  static const String logout = 'logout';

  // ===== Navigation Events =====
  /// 홈 탭 이동
  static const String tabHomeNavigated = 'tab_home_navigated';

  /// 타임라인 탭 이동
  static const String tabTimelineNavigated = 'tab_timeline_navigated';

  /// 앨범 탭 이동
  static const String tabAlbumNavigated = 'tab_album_navigated';

  /// 프로필 탭 이동
  static const String tabProfileNavigated = 'tab_profile_navigated';

  // ===== Timeline Events =====
  /// 타임라인 상세 보기
  static const String timelineDetailViewed = 'timeline_detail_viewed';

  /// 타임라인 목록 새로고침
  static const String timelineListRefreshed = 'timeline_list_refreshed';

  // ===== Teeth Photo Events =====
  /// 사진 선택 성공
  static const String photoSelected = 'photo_selected';

  /// 사진 삭제 성공
  static const String photoDeleteSuccess = 'photo_delete_success';

  /// 사진 목록 새로고침
  static const String photoListRefreshed = 'photo_list_refreshed';

  /// 사진 비교 화면 열기
  static const String photoComparisonOpened = 'photo_comparison_opened';

  /// 비교이미지 생성 버튼 탭 (앨범 탭에서 비교 화면으로 이동)
  static const String photoComparisonCreateTapped =
      'photo_comparison_create_tapped';

  /// 비교 이미지 기기 저장(다운로드) 성공
  static const String photoComparisonImageSaved =
      'photo_comparison_image_saved';

  /// 비교 이미지 기기 저장(다운로드) 실패
  static const String photoComparisonImageSaveFailure =
      'photo_comparison_image_save_failure';

  // ===== Album Events =====
  /// 앨범 화면 조회
  static const String albumScreenViewed = 'album_screen_viewed';

  // ===== Settings Events =====
  /// 다크모드 설정 변경
  static const String darkModeSettingChanged = 'dark_mode_setting_changed';
}

사실 앱을 처음 출시했을 때는 GA를 연동만 하고, 세부적인 이벤트 태그는 추가하지 않았었다. 근데 이번에는 사용자들이 어떤 기능을 많이 사용하는지, 개선할 점은 없는지를 파악하기 위해 다양한 이벤트 태그를 추가했다.

 

전체적인 UI 개선

교정일기는 로그인을 해야만 홈 탭에 진입할 수 있는데, 기존 로그인 화면은 너무 흰 배경에 텍스트만 있는 느낌이라 좀 더 완성도 있는 첫인상을 주기 위해 로그인 화면을 개선했다. 로그인 버튼도 화면 하단에 배치하면서 화면이 좀 더 꽉 찬 느낌을 주었다.

 

기존 앱에서는 전체적으로 폰트 크기가 너무 컸던 거 같아서 16px을 기준으로 폰트 사이즈를 좀 줄였다. 교정 시작일 D-Day 카드에 그래디언트 배경을 넣어서 홈 화면의 단조로움을 좀 줄이고 싶었다. 그리고 홈 탭의 앨범 섹션에서 기존에는 최대 3장의 이미지를 가로 스크롤로 보여주었는데, 이걸 갤러리 느낌으로 최대 3장 그리드 레이아웃으로 수정해서 최신 사진이 한눈에 들어오도록 했다.

 

MY 탭에서는 각 버튼에 아이콘을 추가하고, 버튼 항목을 추가했다. 이렇게 모두 정리해서 모아보니 전체적으로 디테일들을 추가해서 앱이 더 완성도 있는 느낌을 주고싶었던 것 같다.

 

기존의 에러 스크린은 단순 정보만 제공했다면, 업데이트를 하면서 홈 탭 버튼을 추가해서 사용자에게 자연스러운 다음 행동을 유도했다.

 

또한 이번에 업데이트하면서 원형 안에 아이콘과 텍스트를 제공하는 `EmptyState`라는 공용 위젯을 만들었는데 데이터가 없거나, 에러가 발생한 경우 등에 일관성 있는 UI를 제공할 수 있게 되었다.

 

추후 업데이트 예정 기능

이번에 업데이트하고 싶은 부분이 더 많았는데 우선순위가 높은 부분만 선별해서 우선 업데이트를 진행했다. 추후에 또 업데이트를 진행하고 싶은 부분이 있는데, 우선은 영어 지원 기능을 추가해서 사용자층을 더욱 확대해보고 싶다. 또 유료 구독 모델도 붙여보고 싶다. 이번에 업데이트하면서 몇몇 기능은 유료 구독을 위해 빼놓았는데, 올해 안에 이 두 가지 목표를 달성하고 싶다. 둘 다 아직 구현해본 적 없는 기능이기 때문에 많이 배울 수 있는 기회가 될 것 같다.