PortfolioTripnThrill Case Study
đŸ”ī¸
Recommended

Kyoto Itinerary

Japan â€ĸ 6 Days

Flight Ticket

KIX Terminal 1 â€ĸ 10:20 AM

Active

Upcoming Stops

â›Šī¸

Fushimi Inari Shrine

09:00 AM

🎋

Arashiyama Bamboo Forest

02:30 PM

Re-Generate with AI
Offline-First Itinerary Engine

TripnThrill

TripnThrill delivers an ultra-reliable offline travel management experience. It automatically caches routes, maps, and schedules. Once internet access is restored, it initiates idempotent REST requests to safely sync state changes without risking double-bookings.

RoleLead Full Stack App Engineer
PlatformsiOS, Android, Web
Timeline5 Months (2023)
ArchitectureBLoC Pattern
100% SafeConflict Resolution
Full AppOffline Mode
< 800msClient-Server Sync
Tech Stack & Concepts
BLoC PatternRepository PatternBackground SyncIdempotent APIs

The Problem

Travelers frequently experience unstable internet in transit, causing sync errors, overlapping timelines, and lost booking updates during trip plans.

Product Thinking

An itinerary app must remain fully interactive offline. Any addition, re-ordering, or deletion should feel instantaneous to the traveler, with synchronization resolved automatically in the background.

Engineering Challenges

Reconciling conflicting edits to shared family travel itineraries that were made on different devices offline.

Key Learnings

Acquired deep proficiency in event-driven architecture, API idempotency headers, and background worker jobs.

Behind the Code

Architectural & Code Decisions

Deep technical breakdowns of individual structural choices and implementation patterns.

DECISION 01

BLoC Event Flow architecture

Enforced strict separation between UI touch inputs and data logic through BLoC events, maintaining immutable state models that are safe from race conditions.

dart
// Travel Itinerary BLoC Event Handler
class ItineraryBloc extends Bloc<ItineraryEvent, ItineraryState> {
  final TravelRepository repo;
  
  ItineraryBloc(this.repo) : super(ItineraryInitial()) {
    on<SyncOfflineChanges>(_onSyncOfflineChanges);
  }

  Future<void> _onSyncOfflineChanges(SyncOfflineChanges event, Emitter<ItineraryState> emit) async {
    emit(ItinerarySyncing());
    final result = await repo.pushLocalQueueToServer();
    emit(result ? ItinerarySynced() : ItinerarySyncFailed());
  }
}
DECISION 02

Idempotent Sync Queue

Engineered a persistent local SQLite queue to store offline write actions, using UUID headers to guarantee that duplicate server calls never create duplicate bookings.

dart
// Safe POST request with Idempotency Key
Future<Response> createItineraryStop(Stop stop) async {
  return await dio.post(
    '/stops',
    data: stop.toJson(),
    options: Options(
      headers: {
        'X-Idempotency-Key': stop.localUuid, // Client generated UUID
      },
    ),
  );
}