Skip to content

feat(amplify_event_enrichment): add example app - #7076

Open
ekjotmultani wants to merge 33 commits into
feat/event-enrichment-clientfrom
feat/event-enrichment-client-example
Open

ekjotmultani wants to merge 33 commits into
feat/event-enrichment-clientfrom
feat/event-enrichment-client-example

Conversation

@ekjotmultani

Copy link
Copy Markdown
Member

Description

Adds the example Flutter app for the amplify_event_enrichment package, demonstrating event enrichment client usage with session tracking, global fields, and lifecycle observation.

This PR is stacked on #7029 (core package implementation). Review #7029 first.

Structure (3-way split)

PR Branch Content
#7029 feat/event-enrichment-client Core package (lib only)
This PR feat/event-enrichment-client-example Example app (92 files)
#7068 feat/event-enrichment-client-tests Unit tests (5 files)

What's included

  • Full Flutter example app with Android, iOS, and macOS targets
  • Demonstrates EventEnrichmentClient initialization and usage
  • Shows session management, global fields, and lifecycle observation

@codecov-commenter

codecov-commenter commented Jun 30, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 56.78%. Comparing base (e30dc91) to head (905a53a).

Additional details and impacted files
@@                       Coverage Diff                        @@
##           feat/event-enrichment-client    #7076      +/-   ##
================================================================
+ Coverage                         56.57%   56.78%   +0.21%     
================================================================
  Files                               122      125       +3     
  Lines                              8301     8365      +64     
  Branches                           3612     3644      +32     
================================================================
+ Hits                               4696     4750      +54     
- Misses                             3605     3615      +10     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

dependencies:
amplify_event_enrichment:
path: ../
amplify_foundation_dart: ">=2.11.0 <2.12.0"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In example apps for our libs in this repo, I'd use any as a version so that we don't need to care about version bumps.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, switched the example deps to any.

@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from 14c3d90 to 2a2ccfe Compare July 6, 2026 20:24
@ekjotmultani
ekjotmultani marked this pull request as ready for review July 6, 2026 22:42
@ekjotmultani
ekjotmultani requested a review from a team as a code owner July 6, 2026 22:42
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client branch from 90d1c28 to f357a12 Compare July 29, 2026 21:23
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from 2a2ccfe to 9b0c398 Compare July 29, 2026 21:23
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch 4 times, most recently from c5c9e18 to c25df04 Compare August 21, 2026 17:38
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client branch 2 times, most recently from 09f6a20 to 155ac32 Compare August 24, 2026 19:35
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from c25df04 to 47bfbf6 Compare August 24, 2026 19:35
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client branch from 155ac32 to b271735 Compare August 24, 2026 19:37
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch 2 times, most recently from cbbe5e6 to d22bc72 Compare August 25, 2026 20:31
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from d22bc72 to fd389de Compare September 1, 2026 18:57
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from fd389de to 82ede0b Compare September 10, 2026 15:11
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch 2 times, most recently from 4a59dfc to 57f9692 Compare September 21, 2026 18:27
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from 57f9692 to 4a9ec80 Compare October 23, 2026 07:48
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client branch from dd6a04c to 541d0d9 Compare October 23, 2026 07:57
Records an event, stops the session, then records again and asserts the second event gets a new session id with no stop_timestamp, proving a stopped session is not reused.
Apply prefer_const, prefer_int_literals, and cascade_invocations fixes plus formatting so the per-package CI passes analyze (--fatal-infos --fatal-warnings) and the format check on both enrichment packages.
…etadata provider

- assert a sink failure surfaces through Result.error rather than throwing
- assert EventEnrichmentClientFlutter.create degrades gracefully when a device metadata provider throws
- add coverage for the default PlatformDeviceMetadataProvider
…'s new surface

Adapts the suites to the async EventSink and Future-returning record().

New coverage:
- a sink that suspends before throwing, proving record() catches an
  asynchronous failure and returns it as Result.error instead of letting it
  escape to the zone. Verified by removing the await, which makes this test
  fail with an unhandled "async sink failure".
- a sink that throws synchronously, kept as a separate case.
- the injected clientIdProvider, its failure fallback, and the shared
  preferences default.
- the wrapper's session controls, including recording with
  autoSessionTracking disabled to pin the lazy session start.
… semantics

Renames the fakes and call sites for `EventSink` -> `Sender` and
`sink:` -> `sender:`.

New coverage for the session-stop fix, all of which fails without it:

- SessionManager: resume after an explicit `stopSession` leaves tracking
  stopped; resume after a timeout stop still starts a fresh session;
  `startSession` clears the explicit stop so lifecycle handling resumes;
  `clearSession` counts as explicit; a stop with no preceding background
  is not resurrected either
- FlutterLifecycleObserver: neither `onResume` nor a `resumed`
  `AppLifecycleState` restarts a session stopped explicitly
- Client: `handleAppResumed` does not restart after an explicit stop,
  `record()` still lazily starts one, and `close()` drops the session and
  blocks later restarts

The envelope tests are untouched: the json_serializable conversion had to
satisfy them as written.

49 -> 57 dart, 14 -> 17 flutter.
Adds a matrix over every path that can end a session, asserting event type,
that the event's session section carries the ended session with its stop
timestamp and duration, and exact emission counts.

Emits once: explicit stopSession(), stopping a paused session, the session
timeout expiring under fakeAsync, close() on a running session, and
startSession() displacing a running session (asserting the displaced session is
the one reported).

Emits nothing: no session ever started (stop and close), stopSession() twice,
close() after a stop, close() after a timeout, the first startSession(), a
pause/resume inside the timeout, a resume after an explicit stop, and record()'s
lazy start after a stop.

Also covers a sender that suspends before failing on the session-stop event,
which is only caught because the emission awaits send() inside its own guard,
and asserts the client stays usable afterwards.

Existing client tests await stopSession()/close() now that they return
Future<void>. The envelope tests are untouched.
Extends the session boundary matrix to _session.start and renames the file,
which now covers both events.

Emits a start: at construction with autoSessionTracking on, on an explicit
startSession() after the displaced stop, on record()'s lazy start ahead of the
recorded event, and when a resume follows a session timeout. The start event's
session section carries the new session's id and start timestamp with no stop
fields, and gets the same enrichment and globals a recorded event gets.

Does not emit a start: at construction with autoSessionTracking off, on a
pause/resume inside the timeout window (same session), on a resume after an
explicit stop, and on any end path. A full timeout cycle asserts two starts and
two stops, interleaved, one pair per session and matched by session id.

Two ordering tests use a sender that logs when each send begins and completes,
which a synchronously-appending sender cannot distinguish: a displaced stop
completes before the new start begins, and a lazy start completes before
record()'s own send.

The sender-failure group now covers startSession() as well, and counts spell out
that both the start and stop attempts failed.

Existing client tests updated for the eager start now preceding the first
recorded event. The envelope tests are untouched.
…fields

Session timeout: the resume path is exercised with a timer that never fires,
which is what iOS does to a backgrounded Dart isolate, so only the elapsed-time
comparison can detect the expiry. Covers the restart, the stop-then-start
report order, that an explicit stop still wins, and that both the resume path
and the timer path attribute the stop to the pause rather than to detection.

Initial fields: the launch _session.start carries initialUserId and the initial
globals, and carries none of them when they are not passed, through both the
Dart constructor and the wrapper's create().
aft only emits ddc_test and dart2js_test alongside native_test, which needs a
test/ directory, so the jobs the build_test dev dependency gates land with the
suite.
… starts

record() during a pause whose timeout has already passed must close the dead
session out and stamp the event with its replacement, not with the session that
ended when the app was backgrounded. Inside the timeout it must stay silent on
the same session.

Also covers that a session started while the app is backgrounded starts paused
and can time out there, and that one started after a resume is active.
…calls and close

Covers two unawaited startSession() calls (the second displaces the first,
end reported before the replacing start), two unawaited sessionForRecording()
calls (one shared session, one start), and a second close() awaiting the same
teardown. Also asserts the post-close record() error type.
… ordering

Three behaviours had no test holding them:

_onTimeoutExpired's paused check. A pause timer already dispatched when the
resume cancelled it still runs its callback, and without the check it ends a
session that is active again. Covered with a timer stand-in that ignores cancel
and hands its callback to the test, plus the stop-while-paused case for
symmetry.

The session threaded out of a restart. A clearSession() landing while the start
is still reporting used to make the recording call fail on a null check, so
both the recording and the explicit start path now pin that the session they
resolve with is the one that was created.

close() setting its closed flag before the teardown. A record() made in the
same turn as close(), while the final stop emission is still in flight, has to
be refused and its event never sent.
Covers records and boundaries sharing one queue: a record ahead of a stop
called in the same turn, a gated send holding everything behind it, one send
finishing before the next begins, and a failed send not stalling the rest.

Also pins that nothing started through the sessionManager getter during
close() reaches the sender, that the launch start is built at construction,
and moves the background launch tests onto fake time.
`ConsoleEventSink` -> `ConsoleSender`, implementing `Sender` instead of
`EventSink`, and `sink:` -> `sender:` at the create() call.
stopSession() and close() now return Future<void>, completing once the session
stop event has been handed to the Sender.

dispose() cannot await, so it fires close() best-effort with a comment saying
so. The Close button awaits instead, so the log line follows the emitted event
rather than racing it, and a Stop Session button was added to make the
_session.stop envelope visible without closing the client.
Adds a Start Session button so the displacement pair is visible (the running
session's _session.stop, then the new session's _session.start), and documents
the full walkthrough for both event types in the example README.
…xample app

The buttons demonstrate setUserId and addGlobalAttribute, which cannot reach the
launch _session.start. create() now takes the initial values that can.
The example README said the app records events "with attributes and metrics",
but nothing it recorded carried a metric. The purchase event now sends one, and
its button says so.

Also rewords the three em dashes out of the README; no other example README in
the repo uses them.
@ekjotmultani
ekjotmultani force-pushed the feat/event-enrichment-client-example branch from 4a9ec80 to 905a53a Compare October 23, 2026 17:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants