HomeCase studiesA Road-Trip Planning App That Finds the...

Case study · Rome, Italy

A Road-Trip Planning App That Finds the Brands You Actually Use

Consumer travel and mobility — road-trip planning, en-route discovery and driver navigation assistance

Mapping apps are very good at telling a driver what is nearby. They are much less good at telling a driver where the specific hotel chain, coffee shop, fuel network or charging provider they actually use sits along the road ahead. Our team built a cross-platform road-trip planning application that inverts the question: the driver declares their preferred brands once, and from then on every route they plan shows those brands distinctly from everything else. Delivered as two native mobile applications — one Android, one iOS — over a REST API and administrative platform, with server-side mapping cost control and subscription access through both app stores.

Industry
Consumer travel and mobility — road-trip planning, en-route discovery and driver navigation assistance
Solution
A subscription consumer mobile product: two independently built native applications over a shared REST API, with a server-side administrative back office and a proxied third-party mapping layer.
Platforms
Android · iOS · Web & API
Stack
Laravel · PHP · Bootstrap · MySQL · Kotlin · Java
Location
Rome, Italy · delivered remotely
Role
Engineering, with the team behind for mobile and QA

Delivered remotely for a business based in Rome, Italy. Names withheld by agreement.

Project Overview

Industry: Consumer travel and mobility — road-trip planning, en-route discovery and driver navigation assistance.

Type of solution: A subscription consumer mobile product: two independently built native applications over a shared REST API, with a server-side administrative back office and a proxied third-party mapping layer.

Business context: Long-distance drivers are not neutral about where they stop. Loyalty programmes, rewards cards, fleet accounts, charging network subscriptions and simple familiarity mean most people have a short list of brands they will choose over any alternative — and a general-purpose map treats all of them as equivalent results. The gap between "somewhere to stop" and "the place I would actually choose" is filled today by the driver pulling over to search, or by detours discovered too late to take.

General users: Individual drivers planning and running multi-stop road trips, plus platform administrators managing the brand catalogue, content and monetisation.

General purpose: To turn route planning from a list of directions into a route the driver can actually use — with their own preferred hotels, restaurants, fuel, charging and rental brands surfaced along it, and a single tap to widen the search when those brands are not available.

The Business Challenge

Nearby is not the same as preferred. A driver's real question is not "what is around me" but "where is my brand, on my road, in the next two hours". Answering that requires the application to know something a mapping provider does not: which brands this particular person cares about.

Brands are not tidy data. Business names returned by a places provider are free text. A brand may operate under several names, may include a franchise or location suffix, and may belong to a portfolio where the parent name is the one the customer recognises. Matching a user's stated preference to a real-world listing is genuinely fuzzy work.

A trip is an object, not a query. A road trip is planned days before it is taken, edited repeatedly, reordered by hand, started, paused, resumed hours later and finished. That requires a persistent, ordered, editable itinerary that survives across sessions and devices — not a one-shot route calculation.

Ordering has to be exact. Stops are inserted in the middle, deleted, and dragged into new positions. Every one of those operations has to leave the itinerary in a consistent, gap-free order, from two different client implementations acting on the same data.

Third-party mapping is a metered cost. Nearby search, place detail and directions calls all carry a per-call charge, and the natural interaction model — search as the driver explores the map — makes call volume a function of user enthusiasm. Without control, engagement and cost rise together.

Credentials on a device are credentials in public. Mapping platform keys shipped inside a mobile binary can be extracted and used by anyone, at the account owner's expense.

Two stores, two different truths about who has paid. Subscription entitlement has to be verified against the platform the purchase was made on, kept current as renewals and cancellations occur outside the application, and reconciled when the same store subscription reappears under a different account.

Two platforms, one product. The map interaction at the centre of this application is not trivial, and it had to feel like the same product whether built in Kotlin or Swift.

Our Approach

Model preference as a ranked, grouped catalogue rather than free text. Rather than letting users type brand names, we built a curated brand catalogue per category, maintained in the back office with bulk spreadsheet import, and let users select a small ranked set from it during onboarding. Because brand portfolios rarely map one-to-one to customer-facing names, the catalogue supports preference groups — one user-facing selection can expand to a family of related brands. Catalogue entries also carry per-platform visibility, so the two stores can be given different lists where store policy or partner arrangements require it.

Put every third-party mapping call behind the API. The clients never hold the mapping credential for server-driven lookups. They send a relative provider path and parameters to a single API endpoint; the server attaches the credential, forwards the request, records the call against the user and platform, and returns the response. That gives credential containment, a usage ledger the business can actually read, and one place to enforce a throttle — three problems solved by one decision.

Treat itinerary order as server-owned. Stop ordering is enforced by the API, not by whichever client happened to write last. Inserting a stop at an occupied position cascades the conflicting entries upward; deleting a stop re-indexes the remainder. Both clients offer drag-and-drop reordering and submit the resulting sequence for the server to apply as a unit.

Distinguish rather than filter. Showing only preferred brands produces empty maps in unfamiliar territory, which is exactly when the driver most needs an answer. Instead, preferred brands and everything else are both available, rendered with visually distinct map pins, with per-category layer toggles and a single control to widen the search to all providers — plus an explicit prompt when a search returns none of the user's brands in that area.

Verify entitlement on the server, on both platforms. Purchases are made on-device through the native store flows, then posted to the API and verified server-side against the relevant store — including automatic fallback to the store's sandbox environment during testing. Store-to-server lifecycle notifications are consumed and persisted so that renewals, expiries and cancellations occurring outside the application are reflected without the user having to open it.

Handle the subscription that moves accounts. When a store subscription is presented under an account different from the one that previously held it, the earlier account's entitlement is withdrawn. This is a small piece of logic and an expensive one to omit.

Build both clients natively. Two native codebases, each using its platform's own mapping, billing, sign-in and advertising SDKs directly, with the API as the single shared contract.

The Solution

Ranked brand preferences across five categories. Lodging, restaurants, fuel, EV charging and vehicle rental, each with a small ranked set of preferred brands chosen during a staged onboarding flow and editable at any time.

Preference groups. A single selection can represent a family of related brands, so users pick the name they recognise rather than enumerating a portfolio.

Multi-stop trip planning. Origin, destination and an ordered set of intermediate stops, with route calculation, waypoint routing and route rendering on the map.

Drag-and-drop itinerary editing. Stops are reordered by hand on both clients, with the resulting order applied server-side as a consistent sequence.

Trip lifecycle. Trips move from planned to started to completed, with current position updated as the trip runs so a partially completed trip resumes correctly hours or days later.

Trip notes and per-stop comments. Free-text planning notes attached to the trip and to individual stops.

Route-corridor discovery. Tapping the map searches the surrounding area for places in the enabled categories, classifying results into the user's preferred brands and everything else, rendered with distinct pins.

Layer control. Per-category toggles plus a global control that widens the search from preferred brands only to all providers, with an explicit prompt when no preferred brands are found nearby.

Place detail and navigation handoff. Full place detail on tap, address lookup on long press, and handoff to the device's native navigation application to actually drive the leg.

Choice of map provider on iOS, with a default the user sets themselves, and spoken step-by-step guidance on the alternative provider.

Subscription access. Monthly and annual auto-renewing subscriptions purchased through the native store flows on each platform, verified server-side, with entitlement kept current from store lifecycle notifications.

Multiple sign-in paths. Email and password, three social providers, and Sign in with Apple, alongside password reset, email change and full account deletion.

Advertising with consent management. In-app advertising on both platforms behind the standard consent framework, plus a placement served from the back office.

In-app feedback and store review prompts. Rated feedback captured in-app and reviewed in the back office, with native review prompts surfaced at appropriate moments.

Lifecycle marketing synchronisation. User profiles, preferences and subscription state are synchronised to a third-party marketing-automation platform on a schedule, with every request and response recorded and viewable in the back office so failures surface rather than disappear.

Administrative platform. Brand catalogue management with spreadsheet import, user and trip inspection including a map view of any trip, content pages consumed by both clients, advertising, settings, feedback, subscription review, notification composition and the marketing synchronisation log.

Key Features

Ranked brand preferences with grouping Users declare a small ranked set of preferred brands in each of five categories, drawn from a curated catalogue that supports grouping so one selection can stand for a family of related brands.

Preferred-brand distinction along the route Discovery results are separated into the user's brands and everything else, drawn with distinct map pins, so the map answers "where is my brand" rather than "what is nearby".

Server-side mapping proxy with usage logging and throttling All server-driven mapping and places traffic runs through the API rather than from the device, keeping the credential off the client and producing a per-user, per-platform record of every call — with a single place to enforce a limit.

Server-owned itinerary ordering Stop order is enforced by the API with cascading reordering on insertion and re-indexing on deletion, so drag-and-drop editing from two independent clients cannot corrupt the sequence.

Trip lifecycle with resumable current position Trips move through planned, running and completed states, with position updated as the trip progresses so a multi-day journey resumes accurately.

Cross-store subscription verification Purchases are verified server-side against each platform's own verification model, with sandbox fallback for testing and store lifecycle notifications consumed so entitlement stays current without the user opening the app.

Subscription transfer handling When the same store subscription is presented under a different account, the previous account's entitlement is withdrawn — preventing a single purchase from entitling several accounts.

Bulk brand catalogue management The brand catalogue is maintained by non-technical staff through spreadsheet import with idempotent matching, and entries carry per-platform visibility flags.

Dual map provider on iOS with spoken guidance Users select their preferred mapping provider, with turn-by-turn step narration available on the alternative provider.

Auditable marketing synchronisation Every outbound profile synchronisation to the marketing platform is logged with its request and response and surfaced in the back office, so a failed sync is visible rather than silent.

Technical Architecture

Mobile clients. Two independently built native applications sharing no code. The Android client is Kotlin with an MVVM structure, dependency injection, coroutines and a typed HTTP layer with a bearer-token interceptor. The iOS client is Swift and UIKit with storyboards, a shared request manager and a slide-menu container. Both integrate their platform's native mapping and places SDKs, billing framework, sign-in providers, advertising SDK with consent management, and analytics and crash reporting.

API layer. A token-authenticated REST API over OAuth2 bearer tokens, with controllers partitioned into consumer, administrative and authentication trees over a shared base controller that standardises the response envelope across every endpoint.

Mapping proxy layer. A single server-side endpoint through which the clients route their provider calls. It attaches the credential, records the call against the user and platform, applies a throttle and returns the provider response unmodified.

Subscription layer. Platform-specific verification — a receipt posted to one store's verification service with sandbox fallback, and an OAuth-authenticated purchase lookup against the other store's developer API — converging on a single entitlement flag mirrored onto the user record, kept current by a store-to-server notification endpoint that persists both the raw and decoded payloads.

Data layer. A relational database holding users and preferences, trips and their ordered stops, the brand catalogue and its groupings, subscription and notification history, mapping call records, content pages, advertising and settings — evolved through a migration history with dedicated indexing migrations on the high-traffic tables and model-level caching on the catalogue.

Administrative back office. Server-rendered within the same application, covering catalogue, users, trips, content, advertising, subscriptions, feedback, notifications and integration logs.

Supporting services. Cloud object storage for user and advertising imagery with a local-disk fallback path, scheduled synchronisation to a marketing-automation platform, push notification delivery, transactional email, and continuous deployment driven from the hosted CI service.

Flow: Native mobile client (Android / iOS) → Token-authenticated REST API → Mapping and places proxy with usage logging → Third-party mapping platform → Relational data layer (trips, ordered stops, brand catalogue, entitlements) → Store verification and lifecycle notifications → Scheduled marketing synchronisation

Technology Stack

Category Technology
Backend language PHP
Backend framework Laravel
Database MySQL with Doctrine DBAL and a migration-managed schema
API authentication OAuth2 bearer tokens (Laravel Passport)
Query caching Model-level caching on catalogue reads
Object storage Google Cloud Storage with local-disk fallback
Mapping and places Google Maps Platform — Maps, Places, Directions and Nearby Search, proxied server-side
Spreadsheet import Maatwebsite Excel
Image processing Intervention Image
Email SMTP and transactional email providers
Content security Spatie CSP with a hand-written policy
Web delivery Progressive Web App support, Bootstrap admin theme, DataTables
CI/CD Hosted CI pipeline driving Capistrano deployment
Android language Kotlin on a modern Android Gradle Plugin and Java 17 toolchain
Android architecture MVVM with ViewModel and LiveData, Koin dependency injection, coroutines
Android networking Retrofit and OkHttp with a bearer-token interceptor
Android platform SDKs Google Maps and Places SDKs, Maps Utils, Play Services Location, Play Billing, Play In-App Review
iOS language Swift with UIKit, storyboards and XIBs
iOS networking Alamofire with SwiftyJSON
iOS platform SDKs Google Maps and Places SDKs, MapKit with speech synthesis, StoreKit
Identity Email and password, Google Sign-In, Facebook Login, Sign in with Apple with Keychain storage
Monetisation Native store subscription billing on both platforms; Google Mobile Ads with the UMP consent framework
Analytics and monitoring Firebase Analytics and Crashlytics, server-side log viewer
Lifecycle marketing Scheduled synchronisation to a third-party marketing-automation platform with per-request audit logging

Technical Challenges & Solutions

Challenge Our Approach
A driver's real question is "where is my brand", not "what is nearby" A curated per-category brand catalogue with grouping support, from which users choose a small ranked set, and a discovery layer that separates preferred brands from everything else with visually distinct map pins rather than filtering the map down to nothing.
Business names from a places provider are free text and brands operate under multiple names Preference groups let one user-facing selection expand to a family of related brands, and the catalogue is maintained by non-technical staff through idempotent spreadsheet import so it can be corrected and extended without a release.
Metered third-party mapping costs that scale with user enthusiasm All server-driven provider traffic runs through a single API proxy that records every call against the user and platform and applies a throttle, turning an invisible variable cost into an observable, controllable one.
Mapping credentials cannot safely live on a device The proxy holds the credential server-side, so server-driven lookups never require the client to carry it.
Two independent clients editing the same ordered itinerary Stop ordering is owned by the API — insertion at an occupied position cascades conflicting entries upward, deletion re-indexes the remainder, and drag-and-drop reordering is submitted as a sequence applied as a unit.
A trip spans days and must resume mid-journey Trips carry an explicit lifecycle state and a current position updated as the journey progresses, so a partially completed itinerary resumes accurately rather than restarting.
Two app stores with entirely different models of who has paid Platform-specific server-side verification — receipt verification with sandbox fallback on one store, OAuth-authenticated purchase lookup on the other — converging on a single entitlement flag, kept current by consuming store lifecycle notifications rather than waiting for the user to reopen the app.
One store subscription appearing under several accounts Subscription identity is tracked by the store's own original transaction identifier, and presenting it under a new account withdraws the entitlement from the previous one.
Outbound marketing synchronisation failing silently Every synchronisation request and its response are recorded and surfaced in the back office, with batch processing in chunks and internal accounts explicitly excluded.

Security & Reliability

Token-based authentication. OAuth2 bearer tokens issued on sign-in, with registration, password reset, email change and account deletion flows, and multiple identity providers including Sign in with Apple.

Credential containment for mapping. Holding the mapping credential server-side and proxying provider traffic keeps it out of distributed binaries for all server-driven lookups, and gives a single point at which usage can be observed and limited.

Server-side entitlement verification. Subscription state is never trusted from the client. Purchases are verified against the issuing store, and lifecycle notifications are persisted in both raw and decoded form so entitlement history is reconstructable.

Entitlement exclusivity. Tracking store subscription identity prevents a single purchase from entitling multiple accounts.

Content security policy. An explicit, hand-written policy governs which sources the administrative interface may load from.

Integration observability. Outbound marketing synchronisation and inbound mapping calls are both recorded as structured data, so integration failures and usage anomalies are visible rather than inferred.

Crash and analytics instrumentation on both mobile platforms, with server-side log inspection.

Consent-managed advertising. Advertising on both platforms runs behind the standard consent framework rather than being served unconditionally.

Scalability & Performance

Cached catalogue reads. The brand catalogue is read on nearly every screen and changes rarely, making it the natural candidate for model-level caching.

Targeted indexing. Dedicated migrations add indexes to the tables carrying the highest read volume as the schema has grown.

Throttled, logged external calls. The mapping proxy bounds third-party call volume and makes it measurable, which matters more than raw throughput in a product whose principal marginal cost is per-call.

Chunked outbound synchronisation. Profile synchronisation to the marketing platform processes in batches rather than loading the full user set.

Externalised media. User and advertising imagery is stored in cloud object storage rather than on the application server, keeping the application tier stateless and horizontally scalable.

On-device rendering. Route decoding, polyline rendering and marker management run on the device using the platform mapping libraries, keeping the server out of the interactive rendering path.

Native clients. Building natively on each platform gives direct access to platform mapping, location and billing frameworks, with baseline profiles generated for the Android release to improve startup.

Business Outcomes

  • Route planning answers the question drivers actually ask — where the brands they use sit along the road ahead, rather than what happens to be nearby.
  • Preferences are captured once and reused everywhere, with grouping so users select the brand name they recognise rather than enumerating a portfolio.
  • Itineraries survive real use — inserted, deleted and reordered stops leave a consistent sequence, and a multi-day trip resumes mid-journey.
  • Third-party mapping cost became visible and bounded rather than an invisible variable that scales with engagement.
  • Subscription entitlement is verified, current and exclusive, reflecting renewals and cancellations that happen outside the application and preventing one purchase from covering several accounts.
  • The brand catalogue is a business asset, not a code change — maintained by non-technical staff through spreadsheet import, with per-platform control.
  • Integration failures surface as data, so a marketing synchronisation problem is seen in the back office rather than discovered through its absence.
  • One product across two platforms, each built natively against its own mapping, billing and identity frameworks over a single shared API contract.

Why it worked

Consumer applications built on metered third-party data have a failure mode that is easy to miss during a demo: the more people enjoy the product, the more it costs to run, and by the time anyone notices, the usage pattern is baked into the interaction design. The right time to decide where the provider credential lives, who is allowed to call the provider and how many times, and where those calls get recorded, is before the first map screen is built.

Our team builds for that reality. We put the third-party mapping layer behind the API from the start, so the credential stayed off the device, the call volume became a number someone could look at, and the throttle had one place to live. We made the itinerary's ordering the server's responsibility rather than a race between two clients. We verified subscription entitlement on the server against both stores, consumed the lifecycle notifications so the state stays true between sessions, and handled the case where a purchase changes hands. And we made the brand catalogue — the thing the product's entire value rests on — editable by the people who understand the brands, without a release.

Our teams work across Laravel and modern PHP, native Android in Kotlin and native iOS in Swift, mapping and location platforms, cross-store subscription commerce, and third-party integration pipelines — with the judgement to know which decisions are cheap now and expensive later.

Final Summary

A driver on a long trip is not looking for somewhere to stop. They are looking for the hotel chain where their points are, the restaurant they know, the fuel network on their card, the charging network their car and subscription are set up for. General mapping applications treat all of those as equivalent search results, and the gap gets filled by pulling over to search, or by a detour spotted too late.

Our team built a cross-platform road-trip planning application that closes that gap. Drivers declare a small ranked set of preferred brands across lodging, restaurants, fuel, charging and rental during onboarding, drawn from a curated catalogue that supports brand families. From then on, every route they plan distinguishes their brands from everything else on the map, with per-category layers and a single control to widen the search when their brands are not around. Trips are persistent, ordered, editable objects with a real lifecycle — planned at home, edited by hand, started, resumed mid-journey and completed.

Underneath, three decisions carry the product. All server-driven mapping and places traffic is proxied through the API, keeping the provider credential off the device and turning a metered, engagement-driven cost into something logged, bounded and observable. Itinerary ordering is owned by the server, so drag-and-drop editing from two independently built native clients cannot corrupt the sequence. And subscription entitlement is verified server-side against both app stores, kept current from store lifecycle notifications and made exclusive to one account at a time. Around that sit a brand catalogue maintained by spreadsheet rather than by release, an administrative platform covering content, advertising, feedback and integration logs, and audited synchronisation to a marketing platform so nothing fails quietly.

01 — Questions

asked about this kind of project

How do you control Google Maps Platform costs in a consumer app?

Route the calls through your own API rather than making them from the device. Once every provider request passes through one server-side endpoint, you get three things at once: the credential stays off the client, every call is logged against a user and a platform so cost becomes a number someone can look at, and there is exactly one place to enforce a limit. Decide this before the map interaction is designed — an interaction that fires a search on every pan is very hard to walk back once users expect it.

Should the mapping API key live in the mobile app or on the server?

Anything that can be called server-side should be. Keys shipped in a mobile binary can be extracted, and a mapping key is a billable asset. Platform SDKs that render maps on-device will still need their own restricted key, but those should be locked to the application signature and bundle identifier and scoped to only the APIs they need. Server-driven lookups — nearby search, directions, place detail — belong behind your own API.

How do you keep a user-reorderable itinerary consistent across two clients?

Make ordering the server's responsibility. Clients propose an order; the server applies it. Inserting at an occupied position should cascade the conflicting entries rather than duplicating a position, and deletion should re-index the remainder so no gaps accumulate. If both clients compute order independently and write it, they will eventually disagree, and the user will see stops in the wrong sequence with no obvious cause.

How do you verify app store subscriptions properly?

Never on the client, and never once. Verify the purchase server-side against the issuing store — the two major stores expose completely different mechanisms — and then keep it current by consuming their server-to-server lifecycle notifications, so renewals, cancellations and expiries that happen outside your app are reflected without the user opening it. Also handle the subscription that moves accounts: track the store's own transaction identity and withdraw the entitlement from the previous account when it reappears elsewhere.

Native apps on both platforms, or cross-platform?

It depends on how much of the product is platform surface. Where the core experience is heavy on maps, location, billing and platform sign-in — as here — native gives direct access to the mature first-party SDKs without a bridging layer between you and the frameworks doing the hard work. The cost is two implementations of the same behaviour, which makes a well-specified shared API contract the thing that keeps them honest.

How do you personalise recommendations without a machine learning pipeline?

Ask. A short, well-designed onboarding that captures explicit preferences against a curated catalogue outperforms inferred preferences for a long time, and it is transparent to the user in a way inference is not. The engineering effort goes into the catalogue instead — keeping it current, grouping related entries so users select the name they recognise, and making it editable by the people who understand the domain rather than by a developer doing a release.

Why curate a catalogue rather than let users type what they want?

Because free text does not match anything reliably. Real-world listings carry franchise suffixes, regional naming and portfolio brands, and a typed string will miss most of them. A curated catalogue with grouping gives you a stable, correctable vocabulary — and when matching goes wrong, you fix it in one place for everyone rather than in each user's saved text.

What should be built first in a location-based consumer app?

The parts that are expensive to change later: where the third-party credentials live, how provider calls are routed and metered, and who owns the ordering and state of the user's core object. None of those demo well. All of them determine whether the product is affordable to run and consistent to use once real people are on it.

03 — Similar project?

describe what is different about yours

Build yours.

Tell me what exists, what you need and the deadline. Fixed price for a defined scope, or hourly from $10 with a written estimate first.

Ahmedabad, India · IST (UTC+5:30) · --:-- IST · Mon–Fri 09:00–18:00 IST · US & EU overlap daily

No newsletter, no CRM. Just a reply.