Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      The Value-Driven AI Roadmap

      September 9, 2025

      This week in AI updates: Mistral’s new Le Chat features, ChatGPT updates, and more (September 5, 2025)

      September 6, 2025

      Designing For TV: Principles, Patterns And Practical Guidance (Part 2)

      September 5, 2025

      Neo4j introduces new graph architecture that allows operational and analytics workloads to be run together

      September 5, 2025

      Lenovo Legion Go 2 specs unveiled: The handheld gaming device to watch this October

      September 10, 2025

      As Windows 10 support ends, users weigh costly extended security program against upgrading to Windows 11

      September 10, 2025

      Lenovo’s Legion Glasses 2 update could change handheld gaming

      September 10, 2025

      Is Lenovo’s refreshed LOQ tower enough to compete? New OLED monitors raise the stakes at IFA 2025

      September 10, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      External Forces Reshaping Financial Services in 2025 and Beyond

      September 10, 2025
      Recent

      External Forces Reshaping Financial Services in 2025 and Beyond

      September 10, 2025

      Why It’s Time to Move from SharePoint On-Premises to SharePoint Online

      September 10, 2025

      Apple’s Big Move: The Future of Mobile

      September 10, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      Lenovo Legion Go 2 specs unveiled: The handheld gaming device to watch this October

      September 10, 2025
      Recent

      Lenovo Legion Go 2 specs unveiled: The handheld gaming device to watch this October

      September 10, 2025

      As Windows 10 support ends, users weigh costly extended security program against upgrading to Windows 11

      September 10, 2025

      Lenovo’s Legion Glasses 2 update could change handheld gaming

      September 10, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Get Started With Navigation in Flutter Using AutoRoute

    How to Get Started With Navigation in Flutter Using AutoRoute

    September 9, 2025

    Navigation is one of the most important parts of any mobile application. Users expect to move seamlessly between screens such as home, settings, profile, and more.

    While Flutter provides built-in navigation using Navigator, managing routes can quickly become complex in large apps. That’s where routing packages like AutoRoute come in. AutoRoute streamlines navigation by generating strongly typed routes, reducing boilerplate, and making your codebase easier to maintain.

    This article will guide you through setting up and using AutoRoute in a Flutter project. By the end, you will have a working project with multiple screens and a structured routing system.

    Table of Contents

    1. Prerequisites

    2. What We Will Build

    3. Step 1: Setting Up the Project

    4. Step 2: Organizing the Project Structure

    5. Step 3: Defining Routes with AutoRoute

      • Breaking it down

      • Why do this before screens?

    6. Step 4: Generating Route Files

    7. Step 5: Setting Up AutoRoute in main.dart

      • Key Points
    8. Step 6: Creating Screens

      • screen1.dart

      • Breaking it down

      • Repeating for Other Screens

      • Why is this important?

    9. Step 7: Control Screen

      • Step-by-Step Breakdown

      • How It Works Together

    10. Screenshots

    11. Best Practices When Using AutoRoute

    12. Conclusion

    Prerequisites

    Before starting, you should have:

    • Flutter SDK installed and configured (Flutter installation guide).

    • A basic understanding of Flutter widgets, stateless vs. stateful widgets, and the Navigator API.

    • Familiarity with running commands in the terminal.

    • An IDE like Android Studio, VS Code, or IntelliJ.

    If you already know how to build simple Flutter apps, you are ready.

    What We Will Build

    We will create a Flutter app with four screens:

    • Control Screen: the main screen with buttons to navigate to other screens.

    • Screen 1, Screen 2, Screen 3: simple pages that demonstrate navigation.

    Our navigation will be managed entirely by AutoRoute, ensuring a clean and scalable project structure.

    Step 1: Setting Up the Project

    Start by creating a new Flutter project:

    flutter create auto_route_example
    

    Navigate into the project folder and open the pubspec.yaml file. Add the following dependencies:

    <span class="hljs-attr">dependencies:</span>
      <span class="hljs-attr">auto_route:</span> <span class="hljs-string">^7.8.4</span>
    
    <span class="hljs-attr">dev_dependencies:</span>
      <span class="hljs-attr">auto_route_generator:</span> <span class="hljs-number">7.3</span><span class="hljs-number">.2</span>
      <span class="hljs-attr">build_runner:</span>
    

    Run the command below to install packages:

    flutter pub get
    

    Step 2: Organizing the Project Structure

    For scalability, keep your project organized. Create the following folder structure inside lib:

    /lib
      /screens
        /sub_screens
          screen1.dart
          screen2.dart
          screen3.dart
        control_screen.dart
      /route_config
        app_route.dart
    main.dart
    

    This structure separates screens from routing logic, making the app more maintainable.

    Step 3: Defining Routes with AutoRoute

    Before we start annotating the actual screens, let’s first set up the route configuration. This file will act as the map of our app’s navigation: it tells AutoRoute which paths exist and which screens they should point to.

    Create lib/route_config/app_route.dart and add the following:

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:auto_route/annotations.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:auto_route/auto_route.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'app_route.gr.dart'</span>; <span class="hljs-comment">// generated file</span>
    
    <span class="hljs-meta">@AutoRouterConfig</span>()
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">AppRouter</span> <span class="hljs-keyword">extends</span> $<span class="hljs-title">AppRouter</span> </span>{
      <span class="hljs-meta">@override</span>
      <span class="hljs-built_in">List</span><AutoRoute> <span class="hljs-keyword">get</span> routes => [
            AutoRoute(path: <span class="hljs-string">'/'</span>, page: Controlscreen.page),
            AutoRoute(path: <span class="hljs-string">'/screen1'</span>, page: Screen1.page),
            AutoRoute(path: <span class="hljs-string">'/screen2'</span>, page: Screen2.page),
            AutoRoute(path: <span class="hljs-string">'/screen3'</span>, page: Screen3.page),
          ];
    }
    

    Breaking the code down:

    1. @AutoRouterConfig() tells AutoRoute: “Here is the central routing configuration. Please use this to generate the navigation system.”

    2. class AppRouter extends $AppRouter:

      • AppRouter is our router definition.

      • $AppRouter will be generated by AutoRoute once we run code generation. It contains the heavy lifting (like route classes and helpers).

    List<AutoRoute> get routes => [...]

    • This is where we declare our app’s navigation map.

    • Each AutoRoute has a path (/screen1) and a page (Screen1.page).

    Example:

    • / → Controlscreen (our starting page).

    • /screen1 → Screen1.

    • /screen2 → Screen2.

    • /screen3 → Screen3.

    Right now, these pages (Screen1.page, and so on) don’t exist yet. We’ll create and annotate them in Step 6.

    • page: Screen1.page

      • This .page getter will only become available after we annotate the screens with @RoutePage.

      • AutoRoute relies on that annotation to generate the correct page factories.

    Why do this before screens?

    By defining routes early, you set a clear navigation blueprint for your app. Later, when we create the screens, we’ll simply “hook them into” this structure with @RoutePage. This helps keep the tutorial logical: define the map first, then build the destinations.

    Step 4: Generating Route Files

    To generate route files, run:

    flutter pub run build_runner build
    

    Or, to watch for changes automatically:

    flutter pub run build_runner watch
    

    This creates app_route.gr.dart in the route_config folder. The file includes strongly typed classes for each screen, such as Controlscreen, Screen1, Screen2, and Screen3.

    This means instead of relying on raw strings for navigation, you’ll use these generated classes, reducing bugs from typos and providing better IDE autocompletion.

    Step 5: Setting Up AutoRoute in main.dart

    In main.dart, configure the app to use AutoRoute:

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/services.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'route_config/app_route.dart'</span>;
    
    <span class="hljs-keyword">void</span> main() {
      runApp(<span class="hljs-keyword">const</span> MyApp());
    }
    
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MyApp</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
      <span class="hljs-keyword">const</span> MyApp({<span class="hljs-keyword">super</span>.key});
    
      <span class="hljs-meta">@override</span>
      Widget build(BuildContext context) {
        <span class="hljs-keyword">final</span> appRouter = AppRouter();
    
        SystemChrome.setSystemUIOverlayStyle(
          <span class="hljs-keyword">const</span> SystemUiOverlayStyle(
            statusBarColor: Colors.transparent,
            statusBarIconBrightness: Brightness.dark,
          ),
        );
    
        <span class="hljs-keyword">return</span> MaterialApp.router(
          title: <span class="hljs-string">'Flutter AutoRoute Example'</span>,
          theme: ThemeData(
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.brown),
            useMaterial3: <span class="hljs-keyword">true</span>,
          ),
          routerConfig: appRouter.config(),
        );
      }
    }
    

    Key points in this code:

    • MaterialApp.router replaces the traditional MaterialApp when using AutoRoute.

    • appRouter.config() provides AutoRoute’s configuration.

    Step 6: Creating Screens

    In Flutter, each page or section of your app is typically represented by a screen. Screens are just widgets (usually wrapped in a Scaffold) that contain the UI and logic for that page. Since we are using AutoRoute, each screen that we want to navigate to must be annotated with @RoutePage.

    The @RoutePage annotation tells AutoRoute, “This widget is a route. Please include it in the generated route system.”

    Without this annotation, AutoRoute will not know about the screen, and you won’t be able to navigate to it using the router.

    screen1.dart

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:auto_route/annotations.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
    
    <span class="hljs-meta">@RoutePage</span>(name: <span class="hljs-string">'Screen1'</span>)
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Screen1</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
      <span class="hljs-keyword">const</span> Screen1({<span class="hljs-keyword">super</span>.key});
    
      <span class="hljs-meta">@override</span>
      Widget build(BuildContext context) {
        <span class="hljs-keyword">return</span> Scaffold(
          appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Screen 1'</span>)),
          body: <span class="hljs-keyword">const</span> Center(child: Text(<span class="hljs-string">'Welcome to Screen 1'</span>)),
        );
      }
    }
    

    Breaking the code here down:

    1. @RoutePage(name: 'Screen1')

      • This annotation registers the widget as a routable page.

      • The name parameter gives AutoRoute a clear identifier for the screen, which will also be reflected in the generated app_route.gr.dart file.

    2. class Screen1 extends StatelessWidget

      • Defines the screen as a stateless widget because it has no dynamic state in this example.

      • For more complex pages (like forms or dashboards), you could use StatefulWidget.

    3. Scaffold

      • Provides the basic layout structure for Material Design apps.

      • Contains the AppBar (top bar with the title) and the body (main content area).

    4. AppBar(title: const Text('Screen 1'))

      • Displays a top app bar with the title “Screen 1”.
    5. body: Center(child: Text('Welcome to Screen 1'))

      • Centers the text in the middle of the screen.

      • In real applications, this is where you’d add your widgets (lists, forms, dashboards, and so on).

    Repeating for Other Screens

    Follow the exact same structure for Screen2 and Screen3:

    • Create screen2.dart and screen3.dart inside sub_screens.

    • Annotate each class with @RoutePage and give it a unique name (Screen2, Screen3).

    • Update the UI inside the body to reflect which screen it is.

    For example:

    <span class="hljs-meta">@RoutePage</span>(name: <span class="hljs-string">'Screen2'</span>)
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Screen2</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
      <span class="hljs-keyword">const</span> Screen2({<span class="hljs-keyword">super</span>.key});
    
      <span class="hljs-meta">@override</span>
      Widget build(BuildContext context) {
        <span class="hljs-keyword">return</span> Scaffold(
          appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Screen 2'</span>)),
          body: <span class="hljs-keyword">const</span> Center(child: Text(<span class="hljs-string">'Welcome to Screen 2'</span>)),
        );
      }
    }
    

    Why is this important?

    AutoRoute scans your project and looks for @RoutePage annotations. It then generates strongly typed navigation classes so you can write context.router.push(const Screen2()) instead of manually typing route strings like '/screen2'. This eliminates human error (like typos in route strings) and makes navigation easier to maintain as your app grows.

    Step 7: Control Screen

    The ControlScreen acts as the entry point of our app. It’s the first screen that loads when the app starts (because in app_route.dart, we set / > Controlscreen).

    This screen doesn’t show any complex content, instead, it provides buttons to navigate to other screens. Think of it like a menu or dashboard that directs you to the other routes.

    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:auto_route/annotations.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:auto_route/auto_route.dart'</span>;
    <span class="hljs-keyword">import</span> <span class="hljs-string">'package:flutter/material.dart'</span>;
    
    <span class="hljs-keyword">enum</span> Screen { screen1, screen2, screen3 }
    
    <span class="hljs-meta">@RoutePage</span>(name: <span class="hljs-string">'Controlscreen'</span>)
    <span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ControlScreen</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">StatelessWidget</span> </span>{
      <span class="hljs-keyword">const</span> ControlScreen({<span class="hljs-keyword">super</span>.key});
    
      <span class="hljs-keyword">void</span> navigateToScreen(BuildContext context, Screen screen) {
        <span class="hljs-keyword">switch</span> (screen) {
          <span class="hljs-keyword">case</span> Screen.screen1:
            context.router.pushNamed(<span class="hljs-string">'/screen1'</span>);
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> Screen.screen2:
            context.router.pushNamed(<span class="hljs-string">'/screen2'</span>);
            <span class="hljs-keyword">break</span>;
          <span class="hljs-keyword">case</span> Screen.screen3:
            context.router.pushNamed(<span class="hljs-string">'/screen3'</span>);
            <span class="hljs-keyword">break</span>;
        }
      }
    
      <span class="hljs-meta">@override</span>
      Widget build(BuildContext context) {
        <span class="hljs-keyword">return</span> Scaffold(
          appBar: AppBar(title: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Control Screen'</span>)),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton(
                  onPressed: () => navigateToScreen(context, Screen.screen1),
                  child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Navigate to Screen 1'</span>),
                ),
                <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
                ElevatedButton(
                  onPressed: () => navigateToScreen(context, Screen.screen2),
                  child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Navigate to Screen 2'</span>),
                ),
                <span class="hljs-keyword">const</span> SizedBox(height: <span class="hljs-number">10</span>),
                ElevatedButton(
                  onPressed: () => navigateToScreen(context, Screen.screen3),
                  child: <span class="hljs-keyword">const</span> Text(<span class="hljs-string">'Navigate to Screen 3'</span>),
                ),
              ],
            ),
          ),
        );
      }
    }
    

    Step-by-step breakdown of the code:

    1. @RoutePage(name: 'Controlscreen')

      • Marks ControlScreen as a routable page.

      • AutoRoute will generate a Controlscreen.page entry for use in app_route.dart.

    1. enum Screen { screen1, screen2, screen3 }

      • We define an enum for our target screens.

      • This makes the navigation method cleaner and less error-prone than typing raw strings in multiple places.

    1. navigateToScreen(BuildContext context, Screen screen)

      • A helper method that checks which screen we want (based on the enum) and then calls context.router.pushNamed('/screenX').

      • context.router comes from AutoRoute, it gives you access to the app’s navigation stack.

      • pushNamed('/screen1') matches the path we defined earlier in app_route.dart:

          AutoRoute(path: <span class="hljs-string">'/screen1'</span>, page: Screen1.page),
        
      • That’s how the button → path → route connection works.

    1. UI Layout (Scaffold)

      • AppBar(title: Text('Control Screen')) adds a title bar at the top.

      • Column with 3 buttons: each button calls navigateToScreen() with a different enum value.

    Example:

    • Button 1 navigateToScreen(context, Screen.screen1): navigates to /screen1.

    • Button 2: navigates to /screen2.

    • Button 3: navigates to /screen3.

    How It Works Together

    1. App start: The router loads /, which points to Controlscreen.

    2. User taps a button: navigateToScreen() runs and calls context.router.pushNamed('/screenX').

    3. AutoRoute matches path: It looks up /screenX in the route list we defined in app_route.dart.

    4. Generated code takes over: app_route.gr.dart (generated by AutoRoute) creates and pushes the correct screen widget onto the stack.

    The result: navigation works without manually writing Navigator.push boilerplate. AutoRoute handles everything for you.

    Screenshots

    control screen - main menu

    screen one

    screen two

    screen three

    Best Practices When Using AutoRoute

    1. Always organize routes in a dedicated folder (route_config) to separate concerns.

    2. Use strongly typed routes (generated classes) instead of raw strings. For example, use Screen1() instead of '/screen1'.

    3. Leverage nested routes for complex apps (e.g., tabs, authentication flows).

    4. Use guards in AutoRoute to protect routes that require authentication.

    5. Keep screens independent, avoid placing navigation logic inside screen widgets unless necessary.

    Conclusion

    AutoRoute simplifies navigation in Flutter applications by generating boilerplate code, ensuring type safety, and offering advanced features like nested navigation and guards. With a clean project structure and best practices, you can scale your Flutter app with confidence.

    For deeper learning and advanced features, refer to the official documentation:
    AutoRoute on pub.dev

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleHow to Use Postman Scripts to Simplify Your API Authentication Process
    Next Article Stop Duct-Taping AI Agents Together: Meet SmythOS

    Related Posts

    Development

    How AI is Redefining Traditional GCC Cost Models for Peak Efficiency

    September 10, 2025
    Development

    How to Automate API Documentation Updates with GitHub Actions and OpenAPI Specifications

    September 10, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    To catch up with ChatGPT, Google reportedly cribbed OpenAI’s homework to boost Gemini’s AI game

    News & Updates

    iOS and Android juice jacking defenses have been trivial to bypass for years

    Security

    CVE-2014-4114: Details on August BlackEnergy PowerPoint Campaigns

    Development

    Cisco Warns of Credential Vuln on AWS, Azure, Oracle Cloud

    Security

    Highlights

    CVE-2025-36521 – MicroDicom DICOM Viewer Out-of-Bounds Read Vulnerability

    May 1, 2025

    CVE ID : CVE-2025-36521

    Published : May 1, 2025, 7:15 p.m. | 53 minutes ago

    Description : MicroDicom DICOM Viewer is vulnerable to an out-of-bounds read which may allow an attacker to cause memory corruption within the application. The user must open a malicious DCM file for exploitation.

    Severity: 8.8 | HIGH

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    Encrypt and Decrypt String Helpers in Laravel 12.18

    June 11, 2025

    Over 70 Malicious npm and VS Code Packages Found Stealing Data and Crypto

    May 27, 2025

    Smashing Security podcast #426: Choo Choo Choose to ignore the vulnerability

    July 17, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.