📂 프로젝트
이음 앱 로고

이음: 일상 클리닝 케어

#Flutter, #WebView, #Firebase, #소셜 로그인, #카카오워크,2026-06-24

이음컴퍼니는 처음에 출장 세차 서비스를 소개하는 브랜드 페이지에서 시작했다. 이후 서비스 이용에 필요한 기능을 확장하고, 모바일 환경에서도 서비스를 이용할 수 있도록 이음 앱을 개발했다.

내가 이 프로젝트에 참여한 이유는 단순히 앱을 하나 만들어보고 싶어서가 아니었다. 예전부터 브랜드를 하나 직접 키워보고 싶다는 욕심이 있었다. 이음은 이미 오산과 동탄 지역에서 실제로 서비스를 운영하고 있었고, 아이디어만 있는 프로젝트가 아니라 실제 고객과 서비스가 존재하는 회사였다.

그래서 이 프로젝트라면 내가 디지털 전환에 기여하면서 동시에 이음컴퍼니가 성장하는 과정에도 함께 기여할 수 있겠다고 생각했다. 기존 서비스를 앱이라는 새로운 접점으로 확장하고, 고객과 운영진이 더 편하게 연결될 수 있도록 만드는 일이 내가 해보고 싶었던 방향과 잘 맞았다.

브랜드 페이지를 앱으로 확장한 것은 단순히 새로운 화면을 추가한 일이 아니라, 오프라인 중심의 서비스를 디지털 접점으로 넓혀가는 시도의 일종이었다. 이 글에서는 이음 앱의 구조와 주요 기능, 앱과 웹서비스를 연결하면서 고려한 점을 정리한다.

이음컴퍼니 웹서비스는 eeum.company에서 확인할 수 있다. 홈페이지의 앱 다운로드 메뉴에서 App Store와 Google Play를 통해 이음 앱을 설치할 수 있다. 이 글은 현재 앱 코드와 프로젝트 문서를 기준으로 작성했으며, 실제 사용자 지표는 포함하지 않는다.

청록색 배경에 흰색 이음 심볼이 있는 앱 아이콘

목차

  • 프로젝트 소개
  • 브랜드 페이지에서 앱으로
  • Flutter와 WebView 구조
  • 주요 기능
  • 운영진과 고객을 연결하는 기능
  • 앱과 웹의 세션 연결
  • 개발하면서 고려한 점
  • 마치며

1. 이음 앱 프로젝트 소개

이음은 오산과 동탄 지역에서 출장 세차 서비스를 제공하고 있는 서비스다. 초기에는 이음컴퍼니를 소개하고 서비스 내용을 전달하는 브랜드 페이지 역할이 중심이었다.

이 프로젝트가 말랑과 달랐던 점은 이미 실제로 운영되고 있는 서비스에서 출발했다는 것이다. 고객이 있고, 운영하는 사람이 있고, 서비스를 제공하는 과정이 있었다. 나는 그 위에 앱을 추가하면서 회사가 고객과 만나는 방식을 조금 더 넓히고 싶었다.

이후에는 서비스 이용에 필요한 기능을 추가하고, 사용자가 모바일에서도 서비스에 접근할 수 있도록 앱으로 기능을 확장했다. 이번 앱은 기존 웹서비스의 기능을 Flutter 화면으로 모두 다시 구현하는 방식이 아니라, 웹서비스를 앱 안에서 실행하고 모바일 환경에 필요한 기능을 네이티브 계층에서 보완하는 방식으로 구성했다.

현재 앱의 기본 구조는 다음과 같다.

이음 Flutter 앱
  ↓
WebView
  ↓
https://www.eeum.company/webview
  ↓
기존 이음 웹서비스

앱에서 사용하는 웹 주소는 설정으로 주입할 수 있도록 분리했다. 따라서 로컬 개발 환경에서는 개발 서버를 연결하고, 배포 환경에서는 운영 웹서비스를 연결할 수 있다.

2. 브랜드 페이지에서 앱으로

처음의 웹서비스는 이음컴퍼니의 서비스와 브랜드를 소개하는 역할이 컸다. 하지만 서비스가 실제 이용 과정과 연결되려면 단순한 소개 페이지를 넘어, 사용자가 로그인하고 예약과 상담 같은 기능을 이용할 수 있는 접점이 필요했다.

앱을 만들 때 가장 먼저 고민한 건 기존 웹사이트를 버리고 앱 화면을 처음부터 다시 만들 것인지였다. 이미 실제로 돌아가고 있는 웹사이트가 있었고, 그 안에 서비스 소개와 예약, 상담 같은 기능도 어느 정도 자리 잡고 있었다. 앱을 만든다고 해서 그동안 만든 화면과 기능을 전부 버릴 이유는 없다고 생각했다.

그래서 앱은 기존 웹서비스를 대체하는 별도의 제품이라기보다, 이미 있는 서비스에 새로운 진입점을 추가하는 방향으로 잡았다. Next.js와 서버는 기존처럼 웹서비스의 일반적인 기능과 데이터를 담당하고, Flutter는 사용자가 앱으로 들어와 웹서비스를 사용할 수 있도록 연결하는 역할을 맡겼다.

이렇게 하면 웹 화면을 수정할 때마다 앱을 다시 심사받고 배포해야 하는 부담도 줄일 수 있다. 서비스 문구나 UI를 바꾸는 일은 웹에서 바로 반영할 수 있고, 앱에는 모바일 환경에서만 필요한 기능을 추가하면 된다. 특히 이 프로젝트는 내가 혼자 개발하고 있었기 때문에, 새로운 기능을 만들 때보다 나중에 계속 관리하기 쉬운 구조인지가 더 중요했다.

결국 WebView를 선택한 건 기술적으로 가장 멋진 방법이라서가 아니라, 이미 있는 것을 버리지 않으면서 혼자서도 계속 운영할 수 있는 방법이라고 판단했기 때문이다.

이 구조를 사용하면 다음과 같은 책임 분리가 가능하다.

  • 웹앱: 화면, 사용자 흐름, 웹서비스 기능
  • Flutter: WebView 컨테이너, 네이티브 SDK, 앱 생명주기
  • 서버: 인증 정보 검증과 웹 세션 발급
  • 운영체제: 카메라 권한, 외부 URL, 플랫폼 callback

앱을 만든다는 이유로 기존 웹서비스를 전부 다시 만드는 대신, 이미 있는 기능을 재사용하면서 모바일 환경에 필요한 부분만 추가하는 방식이었다.

3. Flutter와 WebView 구조

이 구조를 쉽게 말하면, 앱 안에 이음 웹사이트를 보여주는 창을 하나 만든 것이다. 사용자는 앱을 열지만 실제 서비스 화면은 기존 웹서비스에서 가져오고, Flutter는 그 화면이 앱 안에서 자연스럽게 동작하도록 주변을 관리한다.

이음 앱은 Flutter의 MaterialApp에서 시작해 하나의 WebView 페이지를 연다. WebView가 로드하는 화면은 이음 웹서비스의 /webview 경로다.

WebView 페이지에서는 단순히 URL만 여는 것이 아니라 앱과 웹이 함께 동작하기 위한 여러 기능을 관리한다.

  • 웹 페이지 이동과 외부 URL 처리
  • 앱 실행 시 초기 WebView 로딩
  • 앱이 백그라운드에서 돌아왔을 때 세션 복구
  • Android 뒤로 가기 동작
  • 카메라 권한과 파일 선택
  • 새로고침과 로딩 실패 처리
  • 웹과 Flutter 사이의 JavaScript channel 통신

웹에서 처리하기 어려운 모바일 기능은 Flutter가 맡고, 서비스 화면과 데이터 흐름은 기존 웹앱과 서버를 활용한다. 이 경계를 나누면 화면을 수정할 때는 웹을 고치고, 카메라나 로그인처럼 앱에 가까운 기능을 추가할 때는 Flutter를 고치면 된다. 혼자 관리해야 하는 프로젝트에서는 이런 구분이 생각보다 큰 장점이었다.

4. 주요 기능

4-1. 기존 웹서비스를 앱 안에서 이용하기

앱을 실행하면 WebView를 통해 이음 웹서비스의 모바일 화면을 연다. 사용자는 앱에서 서비스와 관련된 웹 화면을 이용하지만, 앱은 WebView의 상태와 이동을 함께 관리한다.

앱의 하단 내비게이션은 웹서비스의 주요 경로와 연결된다. 네이티브 내비게이션에서 탭을 선택하면 WebView가 해당 경로로 이동하고, 웹 페이지를 직접 이동했을 때는 현재 경로를 확인해 선택된 탭을 다시 맞춘다.

이렇게 하면 웹의 URL 상태와 앱의 내비게이션 상태가 서로 따로 놀지 않도록 만들 수 있다.

4-2. 네이티브 소셜 로그인

이음 앱에서는 Kakao, Naver, Apple 로그인을 Flutter의 네이티브 SDK로 처리한다.

웹 로그인 버튼에서 앱 로그인이 필요하다는 메시지를 보내면 Flutter가 해당 provider의 SDK를 호출하고, 로그인 결과를 웹 세션과 연결한다.

provider마다 로그인 결과를 전달하는 방법과 플랫폼 설정이 다르기 때문에, 앱 내부에서는 각 provider의 결과를 공통된 형태로 정리하는 계층을 두었다.

  • Kakao: KakaoTalk 로그인과 계정 로그인 처리
  • Naver: callback 결과와 access token 조회 처리
  • Apple: iOS와 Android의 플랫폼별 설정 처리

소셜 로그인 자체의 세부 구현과 callback, timeout, 세션 race condition에 대한 내용은 별도의 글에서 자세히 정리했다.

Flutter 하이브리드 앱에서 소셜 로그인을 붙이며 배운 것

4-3. 푸시 알림과 앱 아이콘 배지

Firebase Messaging을 사용해 푸시 알림을 처리하고, 알림을 눌렀을 때 WebView 안의 특정 경로로 이동할 수 있도록 구성했다.

채팅과 관련된 알림이나 읽지 않은 항목은 앱 아이콘 배지와도 연결된다. 앱이 실행 중인지, 백그라운드에 있는지에 따라 알림을 처리하는 시점이 달라질 수 있기 때문에 알림 수와 WebView 상태를 함께 관리해야 했다.

4-4. 모바일 환경에 맞춘 WebView 기능

웹서비스를 WebView에서 실행하려면 브라우저에서는 자연스럽게 동작하던 기능을 앱 환경에 맞게 다시 연결해야 한다.

현재 앱에는 다음과 같은 보완 기능이 있다.

  • 카메라 사용 권한 처리
  • Android 파일 선택
  • 외부 링크를 시스템 브라우저로 열기
  • WebView 새로고침
  • 네트워크 오류 및 페이지 로딩 실패 화면
  • 앱 시작 시 로딩 오버레이
  • Android 뒤로 가기와 앱 종료 처리

즉, 이 앱은 WebView 하나만 띄우는 얇은 앱 셸이라기보다, 웹서비스가 모바일 앱 안에서 안정적으로 동작하도록 주변 환경을 관리하는 역할까지 담당한다.

5. 운영진과 고객을 연결하는 기능

앱을 서비스 이용 접점으로 확장하면서 중요했던 것은 고객이 앱에서 신청하고 상담하는 것뿐만 아니라, 운영진이 그 요청을 놓치지 않고 이어서 처리할 수 있는 흐름을 만드는 일이었다. 이음 웹서비스에는 이 흐름을 지원하기 위해 카카오워크 연동과 고객용 이용 내역 화면이 함께 구성되어 있다.

5-1. 카카오워크를 운영 화면으로 활용하기

운영진과 현장 담당자가 사용할 협업 도구를 고르는 과정도 생각보다 중요했다. 개발자에게는 Slack이나 Discord가 익숙하지만, 이음에서 실제로 청소와 케어를 담당하는 분들이나 일반 운영진에게는 새로운 협업 도구 자체가 부담이 될 수 있었다. 별도의 앱을 새로 만들어줄 수도 있었지만, 그러면 또 다른 화면과 사용법을 익혀야 했다.

그래서 처음부터 새로운 운영 앱을 만드는 대신, 이미 익숙하게 사용할 수 있는 협업 도구를 찾았다. 초기 비용 부담이 크지 않고, 카카오톡 계정으로 쉽게 접근할 수 있으며, 현장에서도 설명을 많이 하지 않아도 사용할 수 있는 도구가 필요했다. 그러다 카카오워크를 알게 됐고, 카카오톡과 비슷한 접근성이 있다는 점에서 거부감이 적을 것 같았다.

공식 문서를 확인해보니 봇을 추가하고 외부 서비스와 연동하는 기능도 제공하고 있었다. 별도의 스타트업 메신저나 개발 도구를 깊게 알지 못해도 운영진이 사용할 수 있고, 개발자는 필요한 알림과 조회 기능을 연결할 수 있다는 점이 잘 맞았다. 결국 카카오워크를 선택한 이유는 개발자에게 익숙한 도구라서가 아니라, 실제 현장에서 사용할 사람들에게 익숙하고 부담이 적은 도구라고 판단했기 때문이다.

새로운 서비스 신청이나 상담 요청이 접수되면 카카오워크 운영 대화방으로 알림이 전달된다. 알림에는 서비스 종류, 신청 내용, 고객, 접수 경로, 희망 일정, 지역과 같은 정보가 구조화된 블록으로 포함된다. 상담 요청의 경우에는 상담 구분, 관심 서비스, 문의 내용과 희망 연락 방식까지 함께 전달된다.

고객이 이음톡으로 메시지를 보내는 경우에도 카카오워크에 고객명과 상담방, 메시지 내용이 전달된다. 운영진은 별도의 모니터링 화면을 계속 열어두지 않고도 평소 사용하는 업무 도구에서 신규 신청·상담·고객 메시지를 확인할 수 있다. 앱에서 시작된 요청인지 PC 웹에서 시작된 요청인지도 알림에 구분해 표시한다.

운영 조회 기능도 카카오워크 안에 버튼 형태로 제공한다. 운영진은 고정된 운영 패널에서 당일 신규 고객, 신규 신청, 신규 상담, 최근 오류와 오류 상위 항목을 조회할 수 있다. 신청 후속 알림이 외부 서비스로 전달되지 않은 경우에는 카카오워크에서 상태를 확인하고, 실제 상태 변경은 인증된 이음 관리자 화면으로 이동해 명시적으로 실행하도록 경계를 나눴다.

이 구조의 목적은 카카오워크에서 모든 데이터를 직접 수정하게 만드는 것이 아니다. 운영진에게는 익숙한 대화방에서 확인과 조회를 제공하고, 권한이 필요한 변경 작업은 이음 관리자 화면에서 처리하게 해 운영 편의성과 안전한 처리 흐름을 함께 가져가는 것이다.

5-2. 고객이 이용 과정을 확인할 수 있도록 하기

고객 입장에서는 신청을 제출한 뒤 실제로 어떤 상태인지, 일정이 언제인지, 누가 서비스를 담당하는지 알 수 있어야 한다. 앱의 WebView 화면에서는 신청 상태와 결제 상태, 방문 일정, 서비스 대상, 주소, 배정된 마스터 정보와 연락처를 한곳에서 확인할 수 있다.

서비스가 진행된 뒤에는 케어 기록도 이어서 확인할 수 있다. 회차별 작업 상태와 일정, 담당 마스터, 작업 메모, 케어 전·후 사진을 제공하고, 일정이 변경된 경우에는 변경 이력과 메모를 남긴다. 고객이 서비스가 어떻게 진행되었는지 나중에 다시 확인할 수 있는 구조다.

완료된 케어 기록에는 후기 작성 기능도 연결되어 있다. 고객은 텍스트 후기와 사진을 남길 수 있고, 앱은 사진 업로드 상태와 실패를 구분해 후기 등록 전에 확인한다. 채팅 알림과 푸시 알림, 읽지 않은 메시지 배지를 함께 사용해 운영진과 고객 사이의 상담이 끊기지 않도록 보완한다.

이 기능들은 앱에만 새 화면을 만든 결과라기보다, 신청부터 일정 조율, 서비스 진행, 결과 확인과 후기까지의 경험을 하나의 흐름으로 연결한 결과에 가깝다. 앱은 이 과정을 모바일에서 쉽게 접근할 수 있게 하고, 웹서비스와 서버는 실제 서비스 데이터와 상태를 관리한다.

6. 앱과 웹의 세션 연결

하이브리드 앱에서 가장 신경 쓴 부분은 앱의 로그인 상태와 WebView의 로그인 상태를 연결하는 일이었다.

이음 앱은 Firebase refresh token을 안전한 저장소에 보관하고, 앱을 다시 실행했을 때 이를 사용해 Firebase ID token을 갱신한다. 이후 웹서비스와 세션을 동기화한 뒤 WebView를 연다.

흐름을 단순화하면 다음과 같다.

앱 실행
  ↓
안전한 저장소에서 refresh token 확인
  ↓
Firebase 세션 복구
  ↓
웹서비스에 세션 동기화
  ↓
WebView에서 /webview 열기

WebView 내부에서는 Firebase Web SDK의 상태만으로 로그인 여부를 판단하지 않는다. 서버가 발급한 웹 세션을 기준으로 화면을 구성한다.

로그아웃할 때도 웹 세션만 삭제하는 것으로 끝내지 않고, 앱에 저장된 refresh token과 네이티브 provider 세션을 함께 정리한다. 앱 로그아웃과 웹 로그아웃이 서로 다른 상태로 남지 않도록 양쪽의 정리 시점을 맞추는 것이 중요했다.

7. 개발하면서 고려한 점

7-1. 앱과 웹의 경계를 섞지 않기

처음부터 Flutter가 웹서비스의 모든 상태를 직접 관리하도록 만들면 앱과 웹의 책임이 섞이기 쉽다.

이음 앱에서는 웹서비스의 사용자 흐름은 웹앱이 계속 소유하고, Flutter는 네이티브 SDK와 WebView 생명주기를 담당하도록 나누었다. 앱에서 필요한 기능이 생길 때마다 Flutter에 상태를 추가하기보다, 정말 네이티브 처리가 필요한 기능인지 먼저 구분했다.

7-2. 로그인 요청의 이동 경로 검증하기

웹에서 앱으로 전달하는 로그인 요청에는 로그인 이후 돌아갈 경로가 포함된다. 이 경로를 아무 값이나 허용하면 로그인 이후 외부 주소로 이동하는 문제가 생길 수 있다.

그래서 앱에서는 허용된 WebView 경로만 다음 경로로 사용할 수 있도록 제한한다.

허용
  /webview
  /webview/...

거부
  https://...
  //...
  외부 도메인 경로

로그인 기능은 provider SDK를 호출하는 것만으로 끝나지 않는다. 로그인 이후 이동할 위치도 앱과 웹의 경계에서 검증해야 한다.

7-3. 앱이 멈춘 것처럼 보이지 않게 하기

WebView 로딩이나 네이티브 로그인 callback이 예상대로 오지 않으면 사용자는 앱이 멈췄다고 느낄 수 있다.

이를 방지하기 위해 앱에는 로딩 제한 시간, WebView 오류 화면, 로그인 timeout, 다시 시도할 수 있는 흐름을 둔다. 문제를 무조건 성공으로 처리하는 대신, 현재 상태를 종료하고 사용자가 다음 행동을 선택할 수 있도록 하는 방향이다.

7-4. 테스트는 경계면을 중심으로 작성하기

이음 앱의 테스트는 화면이 보이는지만 확인하는 데 그치지 않는다. 네이티브 로그인, WebView bridge, 세션 저장소, 로그아웃 순서, Android와 iOS 설정처럼 앱과 외부 시스템이 만나는 경계를 확인한다.

실제 provider 앱을 모든 테스트에서 실행하는 대신, provider client와 세션 저장소를 대체할 수 있도록 만들어 다음과 같은 상황을 검증한다.

  • 로그인 성공 결과가 세션 동기화로 이어지는가
  • 로그인 취소와 오류를 구분하는가
  • 빈 token을 성공으로 처리하지 않는가
  • 로그아웃 중 다시 로그인해도 세션이 꼬이지 않는가
  • WebView가 잘못된 외부 경로를 열지 않는가
  • 네트워크 오류가 무한 로딩으로 남지 않는가

8. 마치며

이음 앱을 만들면서 앱 개발은 기존 웹서비스를 모바일 화면에 넣는 작업만은 아니라는 것을 다시 확인했다.

기존 웹서비스를 재사용하는 구조를 선택하더라도 앱과 웹 사이에는 새로운 경계가 생긴다. 네이티브 소셜 로그인, WebView 생명주기, 서버 세션, 푸시 알림, 플랫폼 권한을 함께 고려해야 했다.

이번 프로젝트에서 정리한 역할은 다음과 같다.

  • 웹앱은 서비스 화면과 사용자 흐름을 담당한다.
  • Flutter는 WebView와 네이티브 기능을 연결한다.
  • 서버는 인증 정보를 검증하고 웹 세션을 발급한다.
  • 앱은 저장된 세션을 안전하게 복구하고 로그아웃 시 정리한다.

여기에 운영 관점의 역할도 더해졌다.

  • 카카오워크는 운영진이 신청과 상담 흐름을 확인하는 업무 접점이 된다.
  • 관리자 화면은 권한이 필요한 후속 처리와 복구 작업을 담당한다.
  • 앱은 고객이 신청 상태, 일정, 케어 기록과 상담을 확인하는 모바일 접점이 된다.

특히 모든 기능을 Flutter로 다시 만드는 대신, 이미 운영 중인 웹서비스를 중심에 두고 필요한 기능만 네이티브로 확장한 선택이 이 프로젝트의 중요한 방향이었다.

이음 앱은 아직도 웹서비스와 앱의 경계를 계속 다듬어가는 프로젝트다. 앞으로 기능이 추가될 때도 어떤 로직을 앱이 소유해야 하고, 어떤 로직을 웹서비스에 남겨야 하는지를 먼저 판단하면서 확장하려고 한다.

scorchedrice.com

개발하며 배운 것과 프로젝트의 흔적을 기록하는 개인 공간입니다.

한지웅HONGIK UNIV. MATERIAL SCIENCE & ENGINEERING 17
2025. Han Ji Woong. All rights reserved.Built with Gatsby, MDX, and a small constellation of notes.