Mobile App Development (React Native/Flutter)

Chapter 5
Navigation and Application Flow

Bachelors Program in Full Stack Development
Mobile App Development (React Native/Flutter)   •   Bachelor

Navigation and Application Flow

Introduction

Navigation is the structure that connects screens, features, and user decisions in a mobile application. It determines not only how users move from one interface to another, but also what the application considers to be the current task, how information is passed between screens, what happens when the Back action is used, and whether a user can return to an unfinished activity after interruption.

In React Native and Flutter, navigation is commonly implemented through a navigation state: a structured description of the routes currently active in the application. A route may represent a screen, a modal, a tab, a drawer destination, or a nested flow. Good navigation design separates three concerns:

  1. Presentation: how a destination appears, such as a stack screen, tab, drawer, or dialog.
  2. Application state: what the user is currently doing and what data has been entered.
  3. Access policy: whether the destination is available to an unauthenticated, authenticated, or authorized user.

This chapter examines stack, tab, drawer, nested, and declarative routing; route parameters; deep links and universal links; authentication flows; state restoration; lifecycle-aware navigation; and professional design practices.

Learning Objectives

By the end of this chapter, you should be able to:

  • Explain the navigation model used by modern mobile frameworks.
  • Implement stack, tab, drawer, and nested navigation patterns.
  • Compare imperative and declarative routing.
  • Pass, validate, and protect route parameters.
  • Design deep-link and universal-link behavior.
  • Construct authentication and authorization flows.
  • Preserve navigation and screen state across lifecycle events.
  • Apply lifecycle-aware navigation and avoid common routing defects.
  • Evaluate navigation architecture from usability, security, accessibility, and maintainability perspectives.

1. Navigation as a State Machine

A useful conceptual model is a finite-state machine. The application occupies a state, an event causes a transition, and navigation displays the resulting state. For example, an online shopping application may have the states SignedOut, SignedIn, Browsing, ProductDetails, and Checkout.

Events include LOGIN_SUCCESS, SELECT_PRODUCT, ADD_TO_CART, LOGOUT, and PAYMENT_COMPLETE. A transition should be explicit and predictable. If a signed-out user requests a protected product-review route, the application may transition to an authentication flow while remembering the original destination.

Navigation state is not identical to UI state. A navigation state might record that the stack contains Home → ProductDetails, whereas UI state includes the selected product, loading status, text entered into a form, and scroll position. Confusing these categories produces defects such as duplicated screens, lost form input, or unauthorized access.

A route generally has:

  • a name or path, such as /products/42;
  • arguments or parameters, such as product identifier 42;
  • a presentation mode, such as push, replace, modal, tab, or drawer;
  • a lifecycle, including creation, focus, blur, suspension, and disposal;
  • an access requirement, such as public, authenticated, or administrator-only.

Navigation transitions should be deterministic: given the same current state and event, the application should produce the same next state, subject to explicitly asynchronous operations such as authentication or data loading.

2. Principal Navigation Patterns

2.1 Stack Navigation

A stack follows the last-in, first-out principle. Opening a new screen pushes a route onto the stack; Back normally pops the top route.

For example:

Home → Catalog → ProductDetails

Back from ProductDetails returns to Catalog; Back again returns to Home.

Stack navigation is suitable for hierarchical tasks, detail pages, forms, and multi-step workflows. It should not be used merely because every screen happens to be technically navigable. A screen that represents a peer destination may be better represented as a tab.

In React Navigation, a simplified stack can be defined as follows:

const Stack = createNativeStackNavigator();

function CatalogStack() {
  return (
    <Stack.Navigator>
      <Stack.Screen name="Catalog" component={CatalogScreen} />
      <Stack.Screen name="ProductDetails" component={ProductDetailsScreen} />
    </Stack.Navigator>
  );
}

A screen can navigate to another route:

navigation.navigate('ProductDetails', { productId: product.id });

The receiving screen must treat the identifier as untrusted input and validate it before requesting data:

function ProductDetailsScreen({ route }) {
  const { productId } = route.params ?? {};
  // Validate type and permitted format before use.
  return <ProductView productId={productId} />;
}

navigate, push, replace, and reset have different meanings. navigate may focus an existing route depending on the navigator; push creates another stack entry; replace removes the current route before showing the new one; and reset replaces the entire navigation state. A payment-success page, for example, may replace a payment form so that Back cannot resubmit it.

Flutter uses route objects and navigators. A simple imperative example is:

Navigator.of(context).push(
  MaterialPageRoute(
    builder: (_) => ProductDetailsPage(productId: productId),
  ),
);

The result may be returned to the previous page:

final selected = await Navigator.of(context).push<Product>(
  MaterialPageRoute(builder: (_) => const ProductPickerPage()),
);

The type parameter documents the expected result and reduces accidental type errors.

2.2 Tab Navigation

Tabs represent relatively independent top-level destinations, such as Home, Search, Orders, and Profile. Each tab commonly has its own navigation history. If a user opens a product from the Home tab, switches to Profile, and returns to Home, the Home stack should usually remain at the product page.

A tab should not be used for a temporary action, such as a confirmation dialog. Tabs should also be limited in number and labeled clearly because small screens provide limited space.

A common architecture is a tab navigator whose children are stack navigators:

<Tab.Navigator>
  <Tab.Screen name="HomeTab" component={HomeStack} />
  <Tab.Screen name="OrdersTab" component={OrdersStack} />
  <Tab.Screen name="ProfileTab" component={ProfileStack} />
</Tab.Navigator>

This is a nested navigator. Each child stack owns its internal routes, while the tab navigator owns selection among top-level sections.

2.3 Drawer Navigation

A drawer exposes destinations in a side panel, often including settings, help, account management, and less frequently used features. It is appropriate when there are several destinations but persistent tab-bar space is undesirable.

The drawer should not become a substitute for information architecture. A user should be able to understand which destination is active, close the drawer with the system Back action, and reach essential functions without excessive hidden interaction. On larger screens, a drawer may become a permanent navigation rail.

2.4 Modal and Overlay Navigation

A modal interrupts the current task and requires a decision or focused input. Examples include editing a payment method, choosing a date, or confirming deletion. A modal should be used when the user must complete or dismiss a subtask before returning to the underlying context.

It is poor practice to represent every screen as a modal because this weakens hierarchy and can make Back behavior confusing. Modal routes should also define dismissal behavior, including unsaved changes and accessibility announcements.

2.5 Nested Navigation

Nested navigation combines navigators. A typical authenticated application may contain:

Root Navigator → Authentication Flow or Main Tabs → Feature Stack → Detail Screen

Each navigator should have a clear ownership boundary. The root decides whether the user is authenticated. The tab navigator chooses the major section. A feature stack manages its own detail sequence.

Common errors include attempting to navigate directly to a route owned by a child navigator, duplicating route names across unrelated scopes, and resetting a child navigator when only a single screen should change. Events should be sent to the navigator responsible for the destination, or intentionally delegated through a documented interface.

3. Imperative and Declarative Routing

3.1 Imperative Routing

Imperative navigation issues commands: push this route, pop that route, or replace the current route. It is easy to introduce in a button handler and remains useful for small flows and one-off transitions.

Its weakness is that navigation can become scattered across event handlers. The visible screen may then depend on a history of commands that is difficult to inspect, test, or reproduce.

3.2 Declarative Routing

Declarative routing describes the desired navigation state from application state. If authentication state is authenticated, the root renders the main application; otherwise it renders the authentication flow. A deep link updates the desired route state, and the router builds the corresponding page hierarchy.

Conceptually:

function RootRouter({ session }) {
  if (!session) return <AuthFlow />;
  return <MainApplication initialPath="/home" />;
}

In Flutter, the Router API and packages such as go_router commonly support declarative URL-based routing. A conceptual configuration is:

final router = GoRouter(
  redirect: (context, state) {
    final signedIn = authState.isSignedIn;
    final onLogin = state.matchedLocation == '/login';
    if (!signedIn && !onLogin) return '/login';
    if (signedIn && onLogin) return '/home';
    return null;
  },
  routes: [
    GoRoute(path: '/login', builder: (context, state) => const LoginPage()),
    GoRoute(path: '/home', builder: (context, state) => const HomePage()),
  ],
);

The exact APIs vary by framework version and library, but the architectural principle is stable: route state is derived from application state, and guards prevent impossible or unauthorized states.

Declarative routing improves deep-link handling, restoration, testing, and consistency. It requires disciplined state modeling. If authentication, selected tab, and pending destination are represented inconsistently, the router may redirect repeatedly or display an incomplete route.

4. Route Parameters and Data Flow

Parameters should communicate the minimum information required to identify a destination. Prefer stable identifiers such as productId over passing entire mutable model objects. The destination can load the current object, avoiding stale data and reducing serialization problems.

Common parameter categories include:

Parameter typeExampleTypical use
Path parameter/products/42Resource identity
Query parameter/search?q=shoesFilters and optional criteria
Route stateSelected tab or pending actionIn-app transition context
Secure session stateAuth token held by credential storageAuthentication, not URL display

Parameters must be validated for type, range, format, and authorization. A syntactically valid identifier does not prove that the current user may access the resource. Server-side authorization remains necessary.

Avoid placing passwords, access tokens, payment data, or personal secrets in paths and query strings. URLs may be recorded in logs, analytics, browser history, crash reports, or screenshots. Even non-sensitive identifiers may reveal business information.

When a child screen changes data, choose a clear result contract. It may return a saved object, a boolean indicating a change, or nothing if shared application state updates automatically. Do not rely on hidden side effects when a result can express the contract explicitly.

5. Deep Links, App Links, and Universal Links

A deep link opens a specific destination inside an application. For example, exampleapp://products/42 uses a custom scheme. Custom schemes are easy to register but can be claimed by multiple applications and are not inherently associated with a verified web domain.

HTTP(S)-based links provide a stronger user experience. Android App Links and iOS Universal Links associate a web domain with an application through platform verification files and application configuration. A verified link can open the application directly, while an unverified or unsupported link can fall back to a website.

A robust deep-link pipeline is:

  1. Receive the URL from the operating system.
  2. Parse its scheme, host, path, and query parameters.
  3. Validate the route and parameters.
  4. Check authentication and authorization requirements.
  5. Store a pending destination if login is required.
  6. Construct navigation state.
  7. Display an error or safe fallback for invalid content.

The same link may arrive when the app is cold-started, resumed from the background, or already active. These cases must be tested separately. Navigation should be idempotent where possible: processing the same event twice should not create duplicate screens or repeat a destructive action.

A deep link should not bypass access control. If /orders/123 requires authentication, the app should show the login flow and return to the order only after successful authentication and an authorization check.

Universal and App Links also create governance responsibilities. Domain ownership, certificate management, association files, privacy disclosures, and link handling must be maintained through application releases. Broken verification can silently change user experience.

6. Authentication and Authorization Flows

Authentication establishes identity; authorization determines what that identity may do. Navigation should reflect both but must not be treated as the security boundary. The server must enforce permissions for every protected operation.

A root authentication flow commonly has these states:

  • Unknown: the application is checking stored credentials or refreshing a session.
  • Signed out: login and registration are available.
  • Signing in: credentials or an external identity provider are being processed.
  • Signed in: the main application is available.
  • Expired or revoked: the session must be refreshed or the user must sign in again.

Showing the login screen during the Unknown state can cause a visible flash and may discard a deep link. A loading or splash state should be used until the session decision is known.

After successful login, the application should replace or reset the authentication flow rather than leave login screens underneath the main application. Otherwise, pressing Back may expose a login form or allow repeated submission. On logout, clear protected navigation state and sensitive cached data. A logout transition should not merely push a login screen over authenticated pages.

For role-based access, navigation may hide unavailable features, but hidden UI is not sufficient protection. Route guards and server authorization should agree. If a user loses permission while a screen is open, the application should handle the server response and redirect or show an informative error.

External identity-provider flows require careful return handling. The callback URL must be validated, state and nonce values should be used according to the provider's protocol, and authorization codes must not be exposed through general-purpose logging.

7. State Preservation and Lifecycle-Aware Navigation

Mobile operating systems can suspend, terminate, or recreate an application. A navigation design that works only while the process remains alive is incomplete.

Distinguish three forms of state:

  1. Ephemeral UI state: focus, animation progress, a temporary visual selection.
  2. Recoverable workflow state: draft text, selected filters, checkout step, and scroll position.
  3. Durable domain state: saved orders, account preferences, and server-backed records.

Ephemeral state may be recreated. Recoverable state should be stored in a view model, state-management layer, or platform restoration mechanism. Durable state should be persisted appropriately, with security and consistency controls.

Do not assume that a screen's mount or creation event runs only once during the user's conceptual visit. A screen may remain mounted while losing focus, or it may be rebuilt without being disposed. Use focus-aware callbacks for tasks that should occur when a route becomes visible, such as refreshing a list, and cleanup callbacks for subscriptions, timers, and listeners.

React Native commonly uses lifecycle and focus hooks from the navigation library, for example:

useFocusEffect(
  React.useCallback(() => {
    let active = true;
    loadOrders().then(data => {
      if (active) setOrders(data);
    });
    return () => { active = false; };
  }, [])
);

The cancellation flag prevents an obsolete asynchronous result from updating a screen after it loses focus. Production code may additionally use request cancellation supported by the data layer.

Flutter widgets have lifecycle methods such as initState, didChangeDependencies, didUpdateWidget, and dispose. A route becoming visible again after a child route is popped may require a route observer, a returned result, or a state-management event. dispose is the appropriate place to release controllers, subscriptions, and focus resources.

Back navigation must be designed for each platform. Android system Back, iOS gestures, hardware keyboards, and web browser Back may have different semantics. Unsaved form changes require an explicit policy: save automatically, prompt, discard, or prevent leaving until validation is complete.

8. Worked Example: A Protected Product Link

Consider the link:

https://shop.example/products/42?ref=email

Assume the product route is public but purchasing requires authentication.

Step 1: Parse. The path identifies product 42; ref=email is an optional attribution value.

Step 2: Validate. Confirm that 42 is a valid identifier and that ref belongs to an allowed set or is safely treated as opaque metadata.

Step 3: Navigate. Open the product detail screen. Do not place the entire product object in the URL.

Step 4: Begin purchase. If the user selects Buy and is signed out, create a pending intent such as PurchaseProduct(42).

Step 5: Authenticate. Present the login flow. Store the pending intent in memory for a short flow, and use protected persistence only if recovery across process death is required.

Step 6: Resume. After login, re-check authorization and current product availability, then execute or display the purchase confirmation. Do not assume the product, price, or stock status is unchanged.

Step 7: Complete. Replace the checkout form with a confirmation state or clear it so Back cannot unintentionally repeat payment.

This example illustrates that navigation, authentication, domain validation, and lifecycle recovery are related but distinct responsibilities.

9. Illustrative Case Study: CampusConnect

Background

CampusConnect is a fictional university mobile application containing announcements, course schedules, messages, and student services. It must support links from email, preserve each tab's position, and protect student records.

Problem

The first implementation pushed every destination onto one global stack. Users lost their tab positions, deep links opened duplicate screens, and logging out left protected screens beneath the login page. A process restart also discarded an unfinished service request.

Analysis

The application had four top-level peer destinations, so tabs were more appropriate than a single stack. Each destination also had internal hierarchy. The service-request flow required authentication and durable draft recovery. Finally, announcements could be linked from email and the university website.

Approach

The revised architecture used:

  • a root router for session state;
  • an authentication flow for signed-out users;
  • a tab navigator for Home, Courses, Messages, and Services;
  • a stack under each tab;
  • a verified HTTPS link for announcement and service routes;
  • route guards for student-specific records;
  • a draft repository for incomplete service requests;
  • focus-aware refresh for messages and schedules;
  • reset behavior on logout to remove protected route state.

The deep-link handler parsed an announcement identifier and routed to the Home stack. If the link targeted a protected service request, the router retained a pending destination until login completed. Duplicate link events were identified by event ID and ignored after successful handling.

Outcome

Users could switch tabs without losing context, links opened the intended content, and logout no longer exposed protected screens. Draft service requests survived ordinary process recreation because the draft repository stored only necessary fields and applied encryption appropriate to the platform. The team also added automated tests for cold-start links, warm-start links, expired sessions, repeated Back actions, and unauthorized route access.

Learning Points

The principal lesson is architectural: navigation should reflect the user's mental model and the application's security model. A single global stack was technically simple but semantically incorrect. Ownership boundaries, explicit route contracts, and lifecycle testing produced a more reliable system.

10. Testing and Professional Practice

Navigation testing should include more than checking whether a button opens a screen. Test:

  • correct initial route for each session state;
  • Back behavior and gesture behavior;
  • repeated taps and duplicate navigation events;
  • parameter validation and missing parameters;
  • deep links in cold, warm, and foreground states;
  • authentication redirects and return destinations;
  • logout and session expiration;
  • state restoration after rotation, suspension, or process recreation;
  • accessibility labels, focus order, and screen-reader announcements;
  • large text, localization, and right-to-left layouts.

Unit tests can verify route parsing, guards, and state transitions. Integration tests can launch the application with a URL and inspect the resulting screen. End-to-end tests are especially valuable for authentication because provider callbacks, operating-system link handling, and application state interact.

Navigation events should be observable without recording sensitive parameters. Analytics may track a screen category or transition outcome, but must avoid passwords, tokens, private message content, and unnecessary personal identifiers. Retention and access policies should be documented.

11. Challenges and Limitations

Navigation libraries reduce implementation effort but do not eliminate design decisions. APIs differ between React Native libraries and Flutter versions, and code examples must be checked against the selected framework release.

Deep-link behavior depends on operating-system configuration, domain verification, installed applications, and network conditions. It cannot be tested solely in a simulator's ordinary screen flow.

Declarative routing can produce redirect loops when guards change state during evaluation. Imperative routing can produce hidden history and race conditions. Both styles require explicit contracts and tests.

State restoration is limited by platform storage, privacy requirements, and the cost of serializing data. Not every screen should be restored exactly as it was. Sensitive screens may need to restart deliberately after a security timeout.

Finally, navigation complexity increases with nested navigators, modals, external authentication, and asynchronous data. A route map, ownership convention, and documented Back policy are practical forms of technical governance.

12. Best Practices Checklist

  • Model navigation as state and events, not only button commands.
  • Use stacks for hierarchy, tabs for peer destinations, drawers for secondary areas, and modals for focused interruptions.
  • Give each navigator clear ownership.
  • Prefer stable identifiers over passing large mutable objects.
  • Validate parameters and enforce authorization on the server.
  • Keep secrets out of URLs and analytics.
  • Handle cold-start and warm-start deep links.
  • Use verified HTTPS links when web and app destinations should be associated.
  • Reset protected navigation state on logout.
  • Preserve recoverable drafts deliberately and securely.
  • Use focus-aware refresh and dispose resources correctly.
  • Make Back behavior, duplicate events, and unsaved changes explicit.
  • Test accessibility, localization, lifecycle recreation, and failure states.

Chapter Summary

Navigation is an application-state problem as well as a screen-transition problem. Stack navigation models hierarchy; tabs model peer destinations; drawers expose secondary areas; modals support focused tasks; and nested navigators divide ownership. Imperative routing issues commands, whereas declarative routing derives visible routes from application state.

Route parameters should be minimal, validated, and free of secrets. Deep links and verified App Links or Universal Links require parsing, authentication checks, safe fallbacks, and cold- and warm-start testing. Authentication flows should distinguish unknown, signed-out, signing-in, signed-in, and expired states. Logout must clear protected navigation history rather than merely display a login page.

Reliable applications separate ephemeral, recoverable, and durable state; use lifecycle-aware callbacks; cancel obsolete asynchronous work; and define platform-appropriate Back behavior. Professional navigation architecture also includes accessibility, privacy, analytics governance, automated testing, and explicit ownership boundaries.

Key Terms

Navigation state: The structured description of the routes currently active in an application.

Route: A destination and its presentation and lifecycle information.

Stack navigation: Hierarchical navigation using push and pop operations.

Tab navigation: Peer-level navigation among persistent top-level sections.

Drawer navigation: Navigation exposed through a side panel or navigation rail.

Modal: A focused overlay or route requiring completion or dismissal before returning.

Nested navigator: A navigator placed inside another navigator.

Imperative routing: Navigation performed by issuing commands such as push or pop.

Declarative routing: Navigation derived from application and route state.

Path parameter: A value embedded in a route path to identify a resource.

Query parameter: An optional key-value value appended to a route URL.

Deep link: A link that opens a specific destination within an application.

Universal Link: An iOS HTTPS link associated with a verified application.

App Link: An Android HTTPS link associated with a verified application.

Authentication: The process of establishing a user's identity.

Authorization: The process of determining what an identified user may access.

Route guard: Logic that permits, redirects, or rejects route access.

State restoration: Reconstructing appropriate application or screen state after interruption.

Lifecycle-aware navigation: Navigation behavior coordinated with creation, focus, suspension, and disposal.

Idempotence: The property that repeating an operation does not create an unintended additional effect.

Review Questions

Short Answer Questions

  1. What is the difference between stack navigation and tab navigation?
  2. Why should route parameters generally contain stable identifiers rather than complete model objects?
  3. What is the distinction between authentication and authorization?
  4. Name two situations in which lifecycle-aware navigation is important.
  5. Why should secrets not be placed in deep-link URLs?

Long Answer Questions

  1. Explain how a root navigator, authentication flow, tab navigator, and feature stack can be composed in a mobile application.
  2. Compare imperative and declarative routing, including the strengths and risks of each approach.
  3. Describe a complete deep-link processing pipeline from receiving a URL to displaying the destination.
  4. Explain how an application should handle logout, session expiration, and pending destinations.
  5. Discuss the difference between ephemeral UI state, recoverable workflow state, and durable domain state, with examples.

Analytical / Application Questions

  1. Design a navigation architecture for a banking application containing accounts, transfers, statements, support, and authentication. Justify your use of stacks, tabs, drawers, and modals.
  2. A user opens a protected link from an email while signed out, logs in, and then presses Back twice. Specify the expected navigation state after each important transition and identify potential security risks.
  3. A React Native application refreshes data every time a screen component mounts, causing duplicate requests and stale updates. Analyze the lifecycle problem and propose a focus-aware, cancellation-safe solution.

Multiple Choice Questions with Answers

Question 1

Which navigation pattern best represents a hierarchical sequence such as Home → Catalog → Product Details?

A. Stack navigation

B. Drawer navigation

C. Tab navigation

D. Modal-only navigation

Correct Answer: A. Stack navigation

Question 2

What is the principal architectural idea of declarative routing?

A. Every button directly pushes a new screen

B. Visible routes are derived from application and route state

C. All screens are stored in one global stack

D. Routes cannot contain parameters

Correct Answer: B. Visible routes are derived from application and route state

Question 3

Which item should generally not be placed in a deep-link URL?

A. A public product identifier

B. A non-sensitive filter

C. An access token

D. A route name

Correct Answer: C. An access token

Question 4

What should usually happen to protected navigation state when a user logs out?

A. It should remain underneath the login screen

B. It should be reset or cleared

C. It should be copied into the URL

D. It should be converted into a modal

Correct Answer: B. It should be reset or cleared

Question 5

What does authorization determine?

A. Whether a user has established an identity

B. Which resources or actions an identified user may access

C. Whether a device has a network connection

D. Whether a screen has finished rendering

Correct Answer: B. Which resources or actions an identified user may access

Question 6

Why is a focus-aware callback useful for a screen displaying messages?

A. It can refresh data when the screen becomes visible again

B. It permanently disables navigation gestures

C. It encrypts route parameters automatically

D. It guarantees that the operating system will never terminate the app

Correct Answer: A. It can refresh data when the screen becomes visible again

Question 7

Which statement about Android App Links and iOS Universal Links is most accurate?

A. They are verified HTTPS associations between domains and applications

B. They are passwords embedded in route paths

C. They can only work with offline applications

D. They eliminate the need for server-side authorization

Correct Answer: A. They are verified HTTPS associations between domains and applications

Question 8

Which state is appropriate while an application is still checking whether stored credentials are valid?

A. Definitely signed out

B. Unknown or session-checking

C. Administrator

D. Payment completed

Correct Answer: B. Unknown or session-checking

Question 9

What is a major advantage of giving each tab its own stack navigator?

A. It prevents all screens from receiving parameters

B. It preserves independent navigation history for each tab

C. It makes authorization unnecessary

D. It forces every route to be modal

Correct Answer: B. It preserves independent navigation history for each tab

Question 10

What is the purpose of cancelling or ignoring an obsolete asynchronous result after a screen loses focus?

A. To prevent stale work from updating an inactive screen

B. To make every request execute twice

C. To bypass route validation

D. To convert imperative routing into drawer navigation

Correct Answer: A. To prevent stale work from updating an inactive screen

Mobile App Development (React Native/Flutter)
Chapter 5 of 15