Skip to content
Back to archipelago.banuacoder.com

Infrastructure SDKs

journey_tracker Free ​

Tier
Freeversion 0.1.4
Command
archipelago generate journey_tracker
Generates
  • infrastructure/journey_tracker_api
  • infrastructure/journey_tracker_impl
Generates first
shared_consent, analytics_sdk, storage_sdkwhen needed and missing, so there is nothing to install first

User journey (time-to-task) tracker. It times each task flow, such as checkout or onboarding, and reports where and why users drop off. Events are consent-gated analytics, and it ships in release builds.

Variables ​

VariableTypeDefaultDescription
appNamestringMyAppThe name of your application
isForMonorepobooleantrueWhether this is being generated as part of a monorepo

Usage ​

bash
archipelago generate journey_tracker

Non-interactive (CI):

bash
archipelago generate journey_tracker --config my_config.json

Generated packages ​

PackageContents
infrastructure/journey_tracker_apiJourneyTracker (the port) and JourneyOutcome. Pure Dart.
infrastructure/journey_tracker_implJourneyTrackerImpl, JourneyNavigatorObserver (an auto_route observer) and JourneyTrackerImplPackageModule.

When analytics_api, storage_sdk_api or consent_api is missing, the brick generates analytics_sdk, storage_sdk or shared_consent first.

What the hooks wire ​

  • JourneyTrackerImplPackageModule in lib/di/injector.dart.
  • JourneyNavigatorObserver in the archipelago:navigator_observers region of lib/app.dart, inside router.config(navigatorObservers: ...). auto_route calls that builder for every navigator, nested ones included, so the tracker sees every screen.
  • lib/host/initializers/journey_tracker_initializer.dart in the post-launch initializer list. It sends a journey that a crash cut short.

A project generated before flutter_modular_monorepo 5.0.0 or app_scaffold 2.0.0 has no navigator_observers region. The hook then prints the line to add to router.config(...).

Start a journey ​

Inject JourneyTracker into the feature where the flow begins:

dart
journeyTracker.start(
  'checkout',
  finishRoute: CheckoutDoneRoute.name,
  timeout: const Duration(minutes: 10), // default: 5 minutes
);

The journey starts at the screen on top. Every page route shown or popped becomes a node in a tree. A screen the user backs out of stays as a branch, called a detour. Dialogs and sheets are not screens and are skipped.

journey_trackerOne flow, one tree, one event

Step 1 of 6, start: start('checkout', finishRoute: CheckoutDoneRoute.name) roots the journey at the screen on top. The running journey is saved to storage on every change.

storage: journey_tracker / active
{  "flow": "checkout",  "total_ms": 0,  "path": ["CartRoute"],  "path_ms": [0],  "detours": 0,  "nodes": [    { "route": "CartRoute", "at_ms": 0, "ms": 0 }  ]}
user_journey event (AnalyticTracker)

Outcomes ​

Each journey ends exactly once:

OutcomeWhen
completedThe finish route is shown.
timeoutThe timeout passes first.
backgroundedThe app goes to the background.
supersededAnother journey starts.
crashThe process died mid-journey. Sent on the next launch.

The event ​

Each journey sends one user_journey event through AnalyticTracker. It shows in the analytics debugger tab like any other event.

KeyValue
flowThe flow name.
outcomecompleted or terminated.
reasontimeout, backgrounded, superseded or crash, when terminated.
total_msTime from start to the end.
pathRoute names from the start screen to the last screen.
path_msTime on top for each screen of path.
detoursScreens visited that are not on path.
nodesEvery screen, in the order first shown: route, at_ms, ms, and parent (the index of the screen it was opened from; the first screen has none). A flat list, so the telemetry scrubber's depth limit never cuts it.

Crash recovery ​

The running journey is saved to the journey_tracker storage namespace on every screen change. On the next launch, the post-launch initializer sends it as crash. The tracker also sends it before it saves anything new, so a new journey never overwrites it.

Privacy ​

  • Only route names (RouteSettings.name) and durations are recorded, never route arguments or URL paths.
  • Nothing is sent unless ConsentPurpose.analytics is granted. A journey that ends while consent is denied is dropped, not queued.

The brick generates its prerequisites.