Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Опыт работы с навигацией в Flutter
Навигация — это один из самых важных и сложных аспектов мобильной разработки. За годы я прошёл эволюцию от простого Navigator к современным решениям.
История эволюции навигации
2015-2017: Простой Navigator
└─ push/pop, очень базовый
2017-2019: Именованные routes
└─ named routes, более структурированный
2019-2021: Deep Linking
└─ Поддержка ссылок из браузера
2021+: GetX / Go Router
└─ Декларативная, более мощная
Уровень 1: Простой Navigator (начало)
// ❌ Базовый подход (но работает)
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Простой pushes
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(),
),
);
},
child: Text('Go to Details'),
),
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details'),
leading: BackButton(),
),
body: Center(child: Text('Detail Page')),
);
}
}
Проблемы:
- Сложно читать навигацию
- Нелегко отследить историю
- Deep linking не поддерживается
Уровень 2: Named Routes (лучше)
// ✅ Более структурированный подход
final routes = {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
'/settings': (context) => SettingsScreen(),
'/profile/:id': (context) => ProfileScreen(
userId: ModalRoute.of(context)!.settings.arguments as int,
),
};
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
routes: routes,
initialRoute: '/',
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Используем имя маршрута
Navigator.pushNamed(
context,
'/profile/123',
);
},
child: Text('View Profile'),
),
),
);
}
}
Преимущества:
- Централизованная конфигурация
- Легче следить за маршрутами
- Параметры маршрута явные
Уровень 3: Deep Linking (production-ready)
// ✅ С поддержкой deep links
final route = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => HomeScreen(),
routes: [
GoRoute(
path: 'profile/:id',
builder: (context, state) => ProfileScreen(
userId: state.params['id']!,
),
),
GoRoute(
path: 'details',
builder: (context, state) => DetailScreen(),
),
],
),
],
);
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: route,
);
}
}
// Android: AndroidManifest.xml
/*
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https"
android:host="myapp.com"
android:pathPrefix="/profile" />
</intent-filter>
*/
// iOS: Info.plist
/*
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>myapp</string>
</array>
</dict>
</array>
*/
Уровень 4: GetX (мощный и простой)
// ✅ GetX навигация (очень популярна)
final routes = [
GetPage(
name: '/',
page: () => HomeScreen(),
),
GetPage(
name: '/profile/:id',
page: () => ProfileScreen(),
),
GetPage(
name: '/settings',
page: () => SettingsScreen(),
transition: Transition.cupertino, # iOS transition
),
];
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
getPages: routes,
initialRoute: '/',
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Home')),
body: Column(
children: [
ElevatedButton(
onPressed: () {
// Простая навигация
Get.toNamed('/profile/123');
},
child: Text('Go to Profile'),
),
ElevatedButton(
onPressed: () {
// С аргументами
Get.toNamed('/settings', arguments: {'theme': 'dark'});
},
child: Text('Settings'),
),
ElevatedButton(
onPressed: () {
// Замена маршрута (очистить историю)
Get.offNamed('/details');
},
child: Text('Replace route'),
),
],
),
);
}
}
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final userId = Get.parameters['id']!; # Доступ к параметрам
return Scaffold(
appBar: AppBar(
title: Text('Profile $userId'),
leading: BackButton(
onPressed: () => Get.back(), # Вернуться
),
),
body: Center(child: Text('Profile of user $userId')),
);
}
}
Сложная навигация: Bottom Navigation
// ❌ Неправильно: Создаёт новый стек для каждой вкладки
class BadBottomNav extends StatefulWidget {
@override
State<BadBottomNav> createState() => _BadBottomNavState();
}
class _BadBottomNavState extends State<BadBottomNav> {
int _selectedIndex = 0;
@override
Widget build(BuildContext context) {
return Scaffold(
body: _selectedIndex == 0
? HomeScreen()
: _selectedIndex == 1
? SearchScreen()
: ProfileScreen(),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() => _selectedIndex = index),
items: [...],
),
);
}
}
// ✅ Правильно: Сохраняет стек для каждой вкладки
class GoodBottomNav extends StatefulWidget {
@override
State<GoodBottomNav> createState() => _GoodBottomNavState();
}
class _GoodBottomNavState extends State<GoodBottomNav> {
int _selectedIndex = 0;
final _navigatorKeys = [
GlobalKey<NavigatorState>(), # Home
GlobalKey<NavigatorState>(), # Search
GlobalKey<NavigatorState>(), # Profile
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: IndexedStack(
index: _selectedIndex,
children: [
Navigator(
key: _navigatorKeys[0],
onGenerateRoute: _homeRoutes,
),
Navigator(
key: _navigatorKeys[1],
onGenerateRoute: _searchRoutes,
),
Navigator(
key: _navigatorKeys[2],
onGenerateRoute: _profileRoutes,
),
],
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) {
if (_selectedIndex == index) {
# Двойной тап = вернись на верх стека
_navigatorKeys[index].currentState?.popUntil(
(route) => route.isFirst,
);
} else {
setState(() => _selectedIndex = index);
}
},
items: [...],
),
);
}
}
Реальный пример: Навигация в банковском приложении
// Bank App навигация (сложная!)
class BankingNavigation {
// Разные стеки навигации для разных модулей
final _homeNavigator = GlobalKey<NavigatorState>();
final _transactionsNavigator = GlobalKey<NavigatorState>();
final _transferNavigator = GlobalKey<NavigatorState>();
final _settingsNavigator = GlobalKey<NavigatorState>();
Widget buildApp() {
return MaterialApp(
home: BankingShell(),
);
}
}
class BankingShell extends StatefulWidget {
@override
State<BankingShell> createState() => _BankingShellState();
}
class _BankingShellState extends State<BankingShell> {
int _selectedTab = 0;
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Специальная логика для back button
return !await navigatorKeys[_selectedTab].currentState!.maybePop();
},
child: Scaffold(
body: IndexedStack(
index: _selectedTab,
children: [
_homeTab(),
_transactionsTab(),
_transferTab(),
_settingsTab(),
],
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: _selectedTab,
onTap: (index) {
setState(() => _selectedTab = index);
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Home',
),
BottomNavigationBarItem(
icon: Icon(Icons.history),
label: 'Transactions',
),
BottomNavigationBarItem(
icon: Icon(Icons.send),
label: 'Transfer',
),
BottomNavigationBarItem(
icon: Icon(Icons.settings),
label: 'Settings',
),
],
),
),
);
}
Widget _homeTab() => Navigator(
key: navigatorKeys[0],
onGenerateRoute: (settings) {
return MaterialPageRoute(
builder: (context) => HomeScreen(),
);
},
);
// Other tabs similar...
}
Проблемы которые я решал
Проблема 1: Back button в nested navigation
// Решение: WillPopScope
class MyScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// Твоя логика для back button
print('Back button pressed');
return true; # Продолжи стандартное поведение
},
child: Scaffold(...),
);
}
}
Проблема 2: Deep link with authentication
// Решение: Redirect после login
GoRouter _buildRouter() {
return GoRouter(
redirect: (context, state) {
final isLoggedIn = Get.find<AuthService>().isLoggedIn;
if (!isLoggedIn) {
return '/login'; # Перенаправи на логин
}
return state.location; # Продолжи к целевому маршруту
},
routes: [...],
);
}
Проблема 3: Сохранение состояния при навигации
// Решение: AutomaticKeepAliveClientMixin
class HomeScreen extends StatefulWidget {
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true; # Сохрани состояние
@override
Widget build(BuildContext context) {
super.build(context); # ВАЖНО!
return Scaffold(...); # Состояние сохранится
}
}
Резюме
Навигация в Flutter: эволюция от простого к сложному
- Navigator — базовый, для простых приложений
- Named Routes — лучше структурирован
- GoRouter / GetX — production-ready
- Deep Linking — необходимо для modern apps
- Nested Navigation — для сложных приложений
Best Practice:
- ✅ Централизуй конфигурацию маршрутов
- ✅ Используй типизированные параметры
- ✅ Обрабатывай back button правильно
- ✅ Сохраняй состояние при навигации
- ✅ Поддерживай deep linking
Инструменты:
- GoRouter (официальное, современное)
- GetX (популярное, мощное)
- Navigation 2.0 (низкоуровневое)
Моя рекомендация: Используй GoRouter для новых проектов. Это стандарт Google и будущее навигации в Flutter.