Skip to content

feat(EWM-511): Implement Compass Type-Safe Navigation System #889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from

Conversation

levitckii-daniil
Copy link
Contributor

@levitckii-daniil levitckii-daniil commented May 15, 2025

Description

Completely redesigned the navigation system in the application to ensure type safety, improve code organization, and enhance testability.

Solution

Core Navigation Framework

  • Created a new type-safe navigation framework called "Compass" built on top of GoRouter
  • Implemented object-oriented approach replacing string-based routes and parameter passing
  • Added comprehensive documentation in docs/type_safe_navigation.md

Key Components

  • Introduced strongly-typed route data classes which implement CompassRouteData interface lib/app/router/compass/route.dart
  • Created base route abstractions:
    • CompassRoute<T> - For routes with query parameters
    • CompassRouteParameterless<T> - For routes without parameters
    • CompassShellRoute - For shell-based navigation (tabs, drawers)
  • Added navigation guards for authentication, redirects, and feature flags lib/app/router/compass/guard.dart
  • Implemented type-based route resolution using dependency injection lib/app/router/router.dart

Navigation Methods

  • Implemented type-safe navigation operations in lib/app/router/router.dart:
  • compassPoint - Replaces current route with target route (similar to GoRouter's go)
    • Uses route data for type-safe parameter passing
    • Automatically resolves route from the route data type
  • compassPointNamed - Navigates to a named route using route data
    • Uses name-based lookup with typed parameters
  • compassPush - Adds route to navigation stack with result handling
    • Returns a Future that completes when the pushed route is popped
    • Allows for passing typed results between routes
  • compassContinue - Preserves state during navigation
    • Maintains existing query parameters while navigating
    • Allows multi-step workflows to preserve state between steps
  • compassBack - Navigates back with proper cleanup
    • Clears route-specific query parameters before popping
    • Supports returning typed results to previous screen

Route and Guard Organization

  • Created dedicated route.dart files for each feature
    • Contains route class and route data class definitions
    • Located alongside corresponding view/page files
  • Implemented guard.dart files for cross-cutting navigation concerns
    • Authentication guards to redirect unauthenticated users
    • Route restoration guards to preserve navigation state
    • Feature flag guards for conditional routing

Migration

  • Migrated all existing routes to the new Compass system
  • Removed deprecated navigation components:
  • Updated all navigation calls across the application

Other Changes

  • Splash Screen Navigation: Completely reworked splash screen navigation flow

    • Now navigates to previously saved routes from NavigationService
    • Returns clear success/failure from bootstrap process instead of using events
    • Handles bootstrap failures by redirecting to proper error screens
  • Bootstrap Process Refactoring lib/app/service/bootstrap/bootstrap_service.dart

    • Simplified bootstrap process to return success/failure instead of firing events
    • Removed event-based bootstrap error handling in favor of direct returns
    • Removed the dependency on NavigationService during bootstrap
    • Now splash screen checks saved route and navigate to this route instead of guard that was inlined inside old router
  • Navigation State Management lib/feature/root/restore_subroutes_guard.dart

    • Replaced complex NavigationService with a simpler version focused only on persistence
    • Added dedicated guard for restoring navigation state when switching tabs
    • Implemented proper cleanup of navigation subscriptions
  • Onboarding Flow Improvements lib/feature/onboarding/guard.dart

    • Added specialized guard for onboarding flow management
    • Improved handling of authentication-based redirects
  • App Update Handling lib/feature/update_version/guard.dart

    • Migrated update version handling to use the guard pattern
    • Improved update detection and presentation flow
  • Navigation State Persistence: Implemented new approach to saving/restoring navigation state

    • Uses dedicated NavigationService with lightweight storage
    • Stores only the necessary location data for restoration
  • Tab Navigation Enhancements lib/feature/root/view/root_tab.dart

    • Rewrote tab navigation to work with route data objects instead of enum values
    • Improved state management for tab selection and visibility
  • Bottom Navigation Bar: Rewrote tab selection logic

    • Now uses a stream-based approach to track current route
    • Maps route types to tabs instead of using path strings
  • Updated build.yaml to support feature-specific route code generation

Type of Change

  • ✨ New feature (non-breaking change which adds functionality)
  • 🛠️ Bug fix (non-breaking change which fixes an issue)
  • ❌ Breaking change (fix or feature that would cause existing functionality to change)
  • 🧹 Code refactor
  • ✅ Build configuration change
  • 📝 Documentation
  • 🗑️ Chore

Copy link
Contributor

@Odrin Odrin left a comment

Choose a reason for hiding this comment

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

dart format

Copy link
Contributor

Choose a reason for hiding this comment

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

Можем переименовать в *.instructions.md что бы Copilot автоматом мог цеплять из папки?
https://code.visualstudio.com/docs/copilot/copilot-customization#_use-instruction-files

Copy link
Contributor Author

Choose a reason for hiding this comment

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

На самом деле такая дока не совсем подходит для ai, это больше для людей. У меня есть набор рулсов, но я их использую с claude, там можно эмбедить в CLAUDE.md ссылки на файлы (также вроде и с копайлотом можно). Для AI рулсы выглядят вот так:

Core Principles

  1. Always use type-safe navigation

    • Use the Compass system for all navigation
    • Never use raw GoRouter methods or Navigator directly
    • Leverage type safety to prevent runtime errors
  2. Route Structure

    • Place route files in the same feature folder as their corresponding screens
    • Use the route.dart naming convention for route files
    • Follow a consistent implementation pattern for all routes
  3. DI Registration Pattern

    • Register routes with @named and @Singleton(as: CompassBaseRoute)
    • Register guards with @named and @Singleton(as: CompassGuard)
    • Inject routes into other routes using @Named.from(ConcreteRouteType)

Route Implementation

Route Data Classes

  1. DO NOT use Freezed for route data classes

    • Freezed changes runtime types, breaking router resolution
    • Create simple immutable classes with final fields instead
  2. Route data should be immutable

    • Use final fields for all properties
    • Implement proper toString, equality, and hashCode methods manually
  3. Type Selection

    • Use CompassRouteParameterless<T> for routes without parameters
    • Use CompassRoute<T> for routes with parameters
    • Use CompassShellRoute for tabbed or nested navigation

Navigation Methods

  1. Use the correct navigation method for each scenario

    • compassPoint - Standard navigation, replacing current screen
    • compassPush - Adding to navigation stack with optional result
    • compassContinue - Navigation while preserving state
    • compassBack - Return to previous screen, optionally with result
  2. Parameter Handling

    • Make route data conversions robust with proper validation
    • Handle missing or invalid parameters gracefully
    • Provide sensible defaults where possible

Guards and Protection

  1. Leverage guards for cross-cutting concerns

    • Authentication/authorization
    • Feature flags
    • Analytics tracking
    • Deep link handling
  2. Implement guards with appropriate priorities

    • Use priorityHigh (3) for critical guards like authentication
    • Use priorityMedium (2) for feature flags
    • Use priorityLow (1) for analytics and non-blocking guards

Examples

1. Defining a Route Data Class

const _userIdQueryParam = 'userId';
const _displayNameQueryParam = 'displayName';

class ProfileRouteData implements CompassRouteDataQuery {
  const ProfileRouteData({
    required this.userId,
    this.displayName,
  });

  final String userId;
  final String? displayName;

  @override
  Map<String, String> toQueryParams() {
    return {
      _userIdQueryParam: userId,
      if (displayName != null) _displayNameQueryParam: displayName!,
    };
  }
}

2. Implementing a Route Class

@named
@Singleton(as: CompassBaseRoute)
class ProfileRoute extends CompassRoute<ProfileRouteData> {
  ProfileRoute() : super(
    name: 'profile',
    path: '/profile',
    isTopLevel: true,
    builder: (context, data, state) => ProfilePage(userId: data.userId),
  );

  @override
  ProfileRouteData fromQueryParams(Map<String, String> queryParams) {
    return ProfileRouteData(
      userId: queryParams[_userIdQueryParam]!,
      displayName: queryParams[_displayNameQueryParam],
    );
  }
}

///
/// Since this mixin is for parameterless routes, this method should
/// return a default instance of the route data class.
T dataFabric();
Copy link
Contributor

Choose a reason for hiding this comment

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

Fabric - ткань. Название метода максимально непонятное.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Поправил

@levitckii-daniil levitckii-daniil force-pushed the feature/EWM-511_typesafe_navigation branch from 2153b23 to 994253c Compare May 15, 2025 13:22
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