Back to Posts

How to Navigate to New Pages in Flutter: Complete Guide

8 min read

Navigation is a fundamental aspect of any mobile application. In this comprehensive guide, we'll explore different ways to navigate between pages in Flutter.

Basic Navigation

1. Simple Push Navigation

The most basic way to navigate to a new page:

class FirstPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('First Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
          child: Text('Go to Second Page'),
        ),
      ),
    );
  }
}

class SecondPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Page'),
        // Back button is automatically added
      ),
      body: Center(
        child: Text('Welcome to the Second Page!'),
      ),
    );
  }
}

2. Navigation with Data

Pass data between pages:

class Product {
  final String name;
  final double price;

  Product(this.name, this.price);
}

class ProductListPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Products')),
      body: ListView(
        children: [
          ListTile(
            title: Text('Product 1'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProductDetailPage(
                    product: Product('Product 1', 99.99),
                  ),
                ),
              );
            },
          ),
        ],
      ),
    );
  }
}

class ProductDetailPage extends StatelessWidget {
  final Product product;

  const ProductDetailPage({Key? key, required this.product}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(product.name)),
      body: Center(
        child: Text('Price: \$${product.price}'),
      ),
    );
  }
}

Named Routes

1. Setting Up Named Routes

In your main.dart:

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      initialRoute: '/',
      routes: {
        '/': (context) => HomePage(),
        '/settings': (context) => SettingsPage(),
        '/profile': (context) => ProfilePage(),
      },
    );
  }
}

2. Using Named Routes

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          ElevatedButton(
            onPressed: () => Navigator.pushNamed(context, '/settings'),
            child: Text('Go to Settings'),
          ),
          ElevatedButton(
            onPressed: () => Navigator.pushNamed(context, '/profile'),
            child: Text('Go to Profile'),
          ),
        ],
      ),
    );
  }
}

3. Passing Arguments with Named Routes

// Define route settings
MaterialApp(
  onGenerateRoute: (settings) {
    if (settings.name == '/product') {
      final args = settings.arguments as Product;
      return MaterialPageRoute(
        builder: (context) => ProductDetailPage(product: args),
      );
    }
    return null;
  },
)

// Navigate with arguments
Navigator.pushNamed(
  context,
  '/product',
  arguments: Product('Product 1', 99.99),
);

Advanced Navigation Patterns

1. Replace Current Page

Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => NewPage()),
);

2. Clear Stack and Add New Page

Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => HomePage()),
  (route) => false, // Removes all previous routes
);

3. Pop Until Specific Route

Navigator.popUntil(context, ModalRoute.withName('/home'));

4. Custom Page Transitions

class SlideRightRoute extends PageRouteBuilder {
  final Widget page;
  
  SlideRightRoute({required this.page})
      : super(
          pageBuilder: (context, animation, secondaryAnimation) => page,
          transitionsBuilder: (context, animation, secondaryAnimation, child) {
            const begin = Offset(-1.0, 0.0);
            const end = Offset.zero;
            const curve = Curves.easeInOut;
            
            var tween = Tween(begin: begin, end: end)
                .chain(CurveTween(curve: curve));
            
            return SlideTransition(
              position: animation.drive(tween),
              child: child,
            );
          },
        );
}

// Usage
Navigator.push(
  context,
  SlideRightRoute(page: NewPage()),
);

Best Practices

  1. Route Organization

    • Keep routes organized in a separate file
    • Use constants for route names
    • Document route parameters
  2. Error Handling

    • Handle unknown routes
    • Validate route arguments
    • Provide fallback routes
  3. State Management

    • Consider state management solutions
    • Pass minimal data through routes
    • Use proper scoping for shared state

Common Issues and Solutions

1. Context Errors

// Wrong: Using context after async gap
onPressed: () async {
  await Future.delayed(Duration(seconds: 1));
  Navigator.push(context, ...); // Context might be invalid
}

// Correct: Store context or use mounted check
onPressed: () async {
  final currentContext = context;
  await Future.delayed(Duration(seconds: 1));
  if (mounted) {
    Navigator.push(currentContext, ...);
  }
}

2. Navigation State Loss

// Preserve navigation state
WillPopScope(
  onWillPop: () async {
    // Handle back navigation
    return true; // Allow back navigation
  },
  child: Scaffold(...),
)

Testing Navigation

testWidgets('Navigation test', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  
  // Find and tap navigation button
  await tester.tap(find.byType(ElevatedButton));
  await tester.pumpAndSettle();
  
  // Verify navigation occurred
  expect(find.text('Second Page'), findsOneWidget);
});

Conclusion

Effective navigation is crucial for a good user experience in Flutter apps. By following these patterns and best practices, you can create intuitive and maintainable navigation flows. Remember to:

  • Choose appropriate navigation patterns for your use case
  • Handle edge cases and errors gracefully
  • Test navigation flows thoroughly
  • Consider state management implications
  • Use consistent navigation patterns throughout your app