글쓰기
투명성 대시보드를 주머니 속에
투명성 쿼리 API에는 진작부터 웹 대시보드가 있었다. 없던 것은 홈 화면 아이콘이었다. 그래서 이제 데이터 탐색기가 네이티브 앱이 되었다. Android와 데스크톱에서 돌아가며, 단일 Kotlin Multiplatform 코드베이스에서 나오고, 웹사이트가 쓰는 것과 완전히 똑같은 공개 API와 통신한다.
지금 바로 Android 빌드를 내려받아 사이드로드할 수 있다.
왜 굳이 앱인가
이 데이터의 대부분은 자리 잡고 들여다보는 것이 아니라 잠깐 확인하는 것이다 — 삭제 추세, 어느 플랫폼의 수치가 다른 플랫폼과 어떻게 비교되는지, 특정 국가의 이번 기간 요청은 어떤 모습인지. 그것은 데스크톱보다 휴대폰의 상호작용이다. 넓은 화면과 마우스를 전제하는 대시보드는 이런 빠른 확인을 필요 이상으로 번거롭게 만든다. 이 앱은 그 반대로 지어졌다. 휴대폰이 먼저이고, 넓은 레이아웃은 거기서 확장되는 쪽이지 그 역이 아니다.
하나의 코드베이스, 두 개의 플랫폼
의미 있는 모든 것은 하나의 공유 소스 세트에 있다. DTO, /api/overview와 /api/explore를 호출하는 Ktor 클라이언트, 각 쿼리를 만드는 리포지토리, 그리고 모든 화면과 차트다. Compose Multiplatform이 그 동일한 UI를 Android에서도, 데스크톱 창에서도 렌더링한다. 플랫폼별로 나뉘는 것은 단 두 가지뿐이다 — 진입점(Android의 Activity, 데스크톱의 창)과 HTTP 엔진인데, Kotlin의 expect/actual로 연결해 Android는 OkHttp를, 데스크톱은 CIO를 쓴다. 동기화를 맞춰야 할 두 번째 앱 구현이 없다. 두 번째 구현 자체가 없기 때문이다.
API를 정직하게 유지하는 것과 같은 규율이 클라이언트로도 이어진다. 앱은 SQL을 결코 보내지 않는다. 쿼리는 구조화된 ExploreRequest — 테이블 이름에 타입이 지정된 필터, 그룹화, 집계를 더한 것 — 이며, 서버가 이를 검증하고 컴파일한다. 앱은 그 요청을 만들지, 문자열을 만들지 않는다. 새로운 데이터셋 뷰를 추가하는 것은 손으로 쓴 쿼리가 아니라 리포지토리 메서드 하나다.
모바일 우선, 말 그대로
레이아웃은 자신의 너비를 측정해 적응한다. 특정 브레이크포인트 아래에서는 휴대폰이다. 하단 내비게이션, 단일 열, 그리고 작은 화면에서 기대하는 대로 목록 → 상세로 이어지는 탐색 흐름. 그 위에서는 같은 화면이 내비게이션 레일과 마스터-디테일 뷰로 확장된다. 화면들이 무상태이기에 — 상태는 불변 UI 상태를 StateFlow로 노출하는 단일 뷰모델로 끌어올려져 있다 — 동일한 컴포저블이 휴대폰에서도, 데스크톱 창에서도, 그리고 위 프레임들을 만들어낸 스크린샷 하네스에서도 렌더링된다.
차트는 이미지 서비스에서 가져오는 것이 아니라 Compose 캔버스 위에 그린다. 그래서 어떤 밀도에서도 선명하며, 보여주는 것에 정직하다 — 구간은 구간으로 남고, 비율은 결코 합산되지 않으며, 데이터가 없는 기간은 0이 아니라 데이터 없음으로 읽힌다.
제대로 챙길 만한 Android 디테일
네이티브 앱은 네이티브답게 느껴져야 하고, 그것은 작은 것들의 더미다. 테마 아이콘을 위한 단색 레이어를 갖춘 적응형 런처 아이콘, 삭제 요청 뷰나 데이터셋 브라우저로 곧장 뛰어드는 런처 바로가기, 시스템의 뒤로 가기 제스처가 어디로 향하는지 미리 보여주는 예측형 뒤로 가기, 그리고 Android 자체 목록에 표시되는 앱별 언어 설정 — 앱은 이미 8개 언어로 번역되어 있으므로 거기서 고른 언어를 그대로 따른다. 릴리스 빌드는 리소스 축소와 함께 R8을 거치는데, 이것이 온전한 Compose 앱이 약 1.5MB에 안착하는 방법이다.
받기
Android APK는 직접 다운로드다 — 브라우저에서의 설치를 허용하고 열면 된다. 웹 대시보드와 같은 데이터, 같은 쿼리 모델, 같은 주의사항이다. 다만 이제 홈 화면에 산다. 창에서 쓰고 싶다면 같은 코드베이스에서 나오는 데스크톱 빌드가 있다.