This episode organizes movement between screens: the difference between Navigator 1.0 and Navigator 2.0, named routes and nested routes, deep linking, modular app architecture with feature modules, and code organization and separation of concerns.

When an app has dozens of screens, calling Navigator.push one by one becomes unmanageable. Episode 11 builds a clear navigation system and a code architecture that supports growth: the difference between Navigator 1.0 and 2.0, named routes and nested routes, deep linking, modular architecture with feature modules, and separation of concerns.
Navigator 1.0 is imperative: you call push and pop directly. Navigation 2.0 is declarative: a URL or state determines what's shown, and the framework adjusts the stack automatically.
Navigator 2.0 is more powerful — it supports deep linking and full control over the stack — but it carries heavy boilerplate. The pragmatic solution widely used by the community is the go_router package, which wraps Navigator 2.0 behind a simple API:
flutter pub add go_routerFor apps with simple navigation, Navigator 1.0 with named routes is still valid. For apps that need deep linking, state persistence, or many nested flows, go_router is the most balanced choice.
Register routes once in MaterialApp, then navigate by name:
MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => const HomeScreen(),
'/detail': (context) => const DetailScreen(),
'/profil': (context) => const ProfilScreen(),
},
)Navigate using the route name:
Navigator.pushNamed(context, '/detail');Navigator.pushNamed(context, '/detail') moves the user to the registered route. Route names separate the "where to go" decision from widget details, so screens can be reorganized without touching every call site.
With go_router, routes are defined as paths that can also be opened from outside the app:
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomeScreen(),
),
GoRoute(
path: '/produk/:id',
builder: (context, state) {
return ProdukScreen(id: state.pathParameters['id']!);
},
),
],
);state.pathParameters['id'] takes the parameter from a URL like /produk/123. Because the path is a real URL, the same route can be opened through a deep link from outside the app — an important pattern for notifications and share links.
go_router supports nested routes through ShellRoute, which allows navigation within tabs or a drawer without locking the whole page. This is the standard way to build apps with bottom navigation where each tab has its own stack.
Instead of folders per type (screens/, widgets/, models/), split by feature. Each feature contains the layers it needs:
features/auth/ — the model, repository, controller, and login screens.features/home/ — the home screen and related widgets.features/cart/ — cart state and its views.lib/
features/
auth/
auth_screen.dart
auth_controller.dart
auth_repository.dart
home/
home_screen.dart
core/
theme/
widgets/
network/
main.dartThe structure above separates features (business code) from core (shared code). A change in one feature doesn't touch other features, and onboarding new developers is faster because related context lives together.
Even within a single feature, separate responsibilities:
class AuthRepository {
final http.Client client;
const AuthRepository(this.client);
Future<AuthSession> login(String email, String password) async {
final response = await client.post(
Uri.parse('https://api.example.com/login'),
body: {'email': email, 'password': password},
);
return AuthSession.fromJson(response.body);
}
}AuthRepository hides the HTTP details from the UI. A screen just calls repository.login(...) without knowing how the request is built — this is separation of concerns in practice.
Don't put all functions in one utils.dart file or one giant controller. Every class should have a single reason to change. This discipline feels excessive in small projects, but it becomes decisive at production scale.
Key takeaways:
go_router wraps Navigator 2.0 with a simple API and deep linking support.state.pathParameters reads parameters from the path URL.In the next episode 12 we discuss platform integration and plugins — using platform channels for native functionality, integrating device APIs like camera, location, and sensors, custom plugin development, and managing plugin compatibility and platform-specific code. Your app starts touching hardware capabilities.