FILIP FRLIĆ
ZAGREB--:--:--

Case study

Travel Buddy

A trip tracker I designed, built and shipped to both stores on my own — offline-first, with a world map drawn entirely in Skia and native widgets on iOS and Android.

Role
Solo — design, mobile, backend, infrastructure
When
Live since April 2026
Platforms
iOS · Android
Highlight
Passion project
  • React Native
  • Expo
  • TypeScript
  • Supabase
  • Skia
  • Reanimated
  • Legend-State
  • RevenueCat
Travel Buddy app screenshot

The brief

Travel Buddy turns a trip into something you keep: log the countries you have been to, watch a world map fill in, and hold photos, notes, dates and ratings against each trip. It is a personal product, designed and built solo, live on the App Store and Google Play since April 2026.

Two constraints drove most of the engineering. Travel happens where connectivity does not, so the app had to be genuinely usable with no signal rather than merely degrade politely. And a map is the entire visual identity of the product, so it had to feel bespoke rather than like someone else’s tiles with pins on top.

A world map with no map SDK

The map is drawn in React Native Skia directly over bundled 1:50m country geometry. There is no map SDK and there are no runtime tiles: visited countries are filled from the user’s own data, trips drop pins inside them, photo pins open a gallery, and tapping a country opens its detail screen.

Dropping the map SDK removed the app’s heaviest dependency, its per-request billing, and its network requirement in one decision. Because the geometry ships in the bundle, the map is fully interactive in airplane mode over the Atlantic — which is exactly when someone is most likely to be logging a trip.

The same renderer produces the shareable image cards and an Equal Earth projection pre-rendered to PNG for the large home screen widget.

Offline-first sync

State lives in Legend-State persisted to MMKV, with Supabase as the backing store. Writes land locally first and sync when connectivity returns, so creating a trip, attaching photos or editing notes never blocks on the network.

Postgres row-level security does the access control, which matters once data stops being purely private: a one-way follow graph means wishlists and planned trips are readable by people you have a mutual accepted follow with, and by nobody else. Getting that right in RLS rather than in app code means a client bug cannot leak another user’s trips.

Native widgets from one shared module

There are three home screen widgets — a visited-countries count, an upcoming-trip countdown, and a travel map — implemented natively with WidgetKit on iOS and Glance on Android, both reading from a single shared data module rather than two divergent implementations.

The AI features (country descriptions, itinerary drafts, next-trip suggestions) run in Supabase edge functions rather than the client, which keeps keys off the device and lets prompts change without shipping a build.

Shipping it as a product, not a project

The parts that are easy to skip on a solo app are the ones that decide whether it survives: subscriptions through RevenueCat with a trial gate, Sentry and PostHog for what breaks and what people actually use, six languages, light and dark themes, sign-in with Apple and Google, deferred deep links for invites, and account deletion that really deletes.

Store metadata and screenshots are handled by a small automation tool in the repo rather than by hand, because the parts of shipping you dread doing are the parts that quietly stop happening.

Outcome

Live on both stores since April 2026 and iterating — most recently at version 1.7. It is also the testbed where new Reanimated and Skia patterns get tried before they turn up in client work.