Course outline

Flutter / Inclusive interfaces and target evidence / Lesson 17

Lifecycle resume and trust boundaries

Revalidate volatile capabilities and authorization after interruption without treating client state as authoritative.

About 5 min readContent reviewed
On this page

Lesson goals

  • Application visibility is not domain truth
  • Observe lifecycle with a scoped owner
  • Validate a command at the authoritative boundary

Application visibility is not domain truth

An application can be paused, hidden, detached, or resumed while remote state continues to change. A locally cached session label does not prove the server still accepts commands. Device permission, authentication, availability, and active route input can all become obsolete during interruption. Decide which state is safe to display as historical context and which must be revalidated before consequential actions. Do not automatically replay the last destructive or transactional command merely because the app becomes active again. Resume is an opportunity to reconcile state, not evidence of renewed user intent.

Observe lifecycle with a scoped owner

Flutter provides lifecycle observation APIs, but callbacks are not a promise that every process transition will be delivered before termination. Persist important drafts through deliberate save policy rather than waiting for one final notification. The first fragment registers an AppLifecycleListener once and disposes it with its State owner. Its resume callback delegates to a reconciliation function rather than starting multiple independent requests. That function needs a duplicate-work and freshness policy because several events may request refresh close together. The fragment is illustrative and assumes a compatible project SDK.

dart Illustrative Dart or Flutter fragment; requires the declared SDK host.
late final AppLifecycleListener lifecycle;
@override void initState() {
  super.initState();
  lifecycle = AppLifecycleListener(onResume: refreshCapabilityState);
}
@override void dispose() { lifecycle.dispose(); super.dispose(); }
// refreshCapabilityState reconciles status; it does not replay a save.
Expected output
Resuming requests status reconciliation while the owning listener remains active.

The surrounding State supplies refreshCapabilityState. Lifecycle delivery and actual platform interruption were not tested here.

Validate a command at the authoritative boundary

The second fragment makes an important distinction: the client can reject an obviously invalid seat count before sending, but it cannot prove permission or available capacity. The repository’s authoritative result determines success. A disabled button is helpful interaction feedback, not access control. A malicious or stale client can still attempt a command, so the service must enforce authorization and current invariants. Handle conflict as a meaningful result instead of showing unconditional success after awaiting an operation. Preserve the user’s draft when retry or correction remains possible.

dart Illustrative Dart or Flutter fragment; requires the declared SDK host.
enum ReserveOutcome { confirmed, conflict, denied }
abstract interface class Reservations {
  Future<ReserveOutcome> reserve(String workshopId, int seats);
}
Future<ReserveOutcome> submit(Reservations service, String id, int seats) {
  if (id.isEmpty || seats < 1) throw ArgumentError('Invalid reservation');
  return service.reserve(id, seats);
}
Expected output
Local invalid input is rejected; a valid-looking command still receives an authoritative outcome.

No service implementation or real reservation is supplied. The enum records application categories without pretending to enforce server policy.

Treat logs and app bundles as observable artifacts

Do not embed a privileged backend secret in Dart constants, asset files, or build-time defines. Distributed client artifacts and diagnostics are not secret stores. Obfuscation can make symbol names less readable but is not encryption and does not enforce authorization. Use an appropriate server-side boundary for privileged operations and platform-supported protected storage for credentials when needed, following the selected target’s documented guarantees. Keep logs focused on safe operation identity and failure category rather than complete tokens, personal records, or raw payloads copied for convenience.

Make logout a coordinated state transition

Logout should invalidate the appropriate session state, cancel or obsolete sensitive pending work, clear scoped caches according to policy, and navigate to a meaningful unauthenticated state. Removing one visible page while leaving a shared model authenticated creates contradictory behavior. A late response from the previous session must not populate the next user’s screen. Include session identity in freshness checks where needed, in addition to per-request generation. Define what happens to unsent drafts and explain the choice without retaining private content indefinitely by accident.

Exercise interruption and identity changes

Use fakes to simulate resume after permission changes, session expiry during a pending operation, logout before a response, and a second user entering before the first user’s request finishes. Assert that no obsolete result crosses the new ownership boundary. Target tests should separately cover supported lifecycle behavior and permission settings interactions. A pure lifecycle callback test cannot establish process survival or secure storage. Keep verification claims narrow and retain synthetic fixtures so this testing never requires real accounts, real reservations, or production credentials.

Common errors and fixes

Resume repeats an earlier save

Why it happens
Visibility was mistaken for a new user command.
How to fix it
Reconcile status and require explicit intent for a new command.
Prevent it next time
Test repeated resume while a command is pending.

A previous session response reaches the next user

Why it happens
Freshness checks omitted session ownership.
How to fix it
Invalidate pending work and compare session identity before commit.
Prevent it next time
Test logout and login between request and response.

An obfuscated client contains a privileged secret

Why it happens
Symbol obfuscation was treated as confidentiality.
How to fix it
Move privileged operations behind an authorized server boundary.
Prevent it next time
Review distributed configuration and logs for secret ownership.

Check your understanding

1. Why is a disabled Reserve button insufficient to enforce permission?

Show worked solution

The button only controls one client interaction. A stale or modified client can still send the command, and permission can change after rendering. The authoritative service must check current identity, authorization, and capacity when evaluating the operation.

2. What extra identity is needed when the same screen survives an account switch?

Show worked solution

Compare the session or account generation as well as the request generation. Invalidate old work when ownership changes and avoid reusing private caches across scopes. A mounted State alone says nothing about which account is entitled to the result.

Key takeaways

  • Application visibility is not domain truth
  • Exercise interruption and identity changes

Official references