ํ๋ฉด ์ด๋ - Navigator์ go_router
์ง๊ธ๊น์ง๋ ํ๋ฉด ํ๋ ์์์ ์์ ฏ์ ๋ง๋ค๊ณ ์ํ๋ฅผ ๋ค๋ค๋ค. ์ค์ ์ฑ์ ๋ชฉ๋ก ํ๋ฉด์์ ํญ๋ชฉ์ ๋๋ฅด๋ฉด ์์ธ ํ๋ฉด์ผ๋ก ๋์ด๊ฐ๋ ์์ผ๋ก ํ๋ฉด์ด ์ฌ๋ฌ ๊ฐ๋ค. ๊ฐ์ฅ ๋จผ์ ์ฐ๊ฒ ๋๋ ๊ฑด Flutter๊ฐ ๊ธฐ๋ณธ์ผ๋ก ์ฃผ๋ Navigator๋ค. ๋ฒํผ์ ๋๋ฅด๋ฉด ์์ธ ํ๋ฉด์ผ๋ก ๊ฐ๋ ์ฝ๋๋ฅผ ์ด๋ ๊ฒ ์ง๊ฒ ๋๋ค.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push( // ์ฌ๊ธฐ์ ์๋ฌ๋จ
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
},
child: const Text('์์ธ๋ก ์ด๋'),
),
),
),
);
}
}๋ฒํผ์ ๋๋ฅด๋ฉด ์ด๋ฐ ์๋ฌ๊ฐ ๋๋ค.
Navigator operation requested with a context that does not include a Navigator.Navigator๋ ๋ถ๋ช
ํ ์ฐ๊ณ ์๋๋ฐ "Navigator๊ฐ ์๋ context"๋ผ๊ณ ํ๋ค. InheritedWidget, Provider์์ ๋ณธ ๊ฒ๊ณผ ๊ฐ์ ์ ํ์ ์๋ฌ๋ค.
์์ธ: ์ด context๊ฐ Navigator๋ณด๋ค ์์ ์๋ค
Navigator.push(context, ...)๋ Theme.of(context)์ฒ๋ผ ์๋ก ์ฌ๋ผ๊ฐ๋ฉด์ ๊ฐ์ฅ ๊ฐ๊น์ด Navigator๋ฅผ ์ฐพ๋๋ค. ๊ทธ๋ฐ๋ฐ Navigator๋ฅผ ๋ง๋ค์ด์ฃผ๋ ๊ฑด MaterialApp์ด๋ค. ์ ์ฝ๋์ context๋ MaterialApp์ ๋ง๋๋ build์ ๊ฒ์ด๋ผ MaterialApp๋ณด๋ค ์์ ์๊ณ , ์ฌ๋ผ๊ฐ๋ Navigator๊ฐ ์๋ค.
ํด๊ฒฐ์ ์ด๋ฏธ ์๋ ๋ฐฉ๋ฒ์ด๋ค. ๋ฒํผ์ MaterialApp๋ณด๋ค ์๋์ ๋ณ๋ ์์ ฏ์ผ๋ก ๋บ๋ค.
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: HomePage());
}
}
class HomePage extends StatelessWidget {
const HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
},
child: const Text('์์ธ๋ก ์ด๋'),
),
),
);
}
}HomePage์ context๋ MaterialApp ์๋์ ์์ด์ Navigator๋ฅผ ์ฐพ๋๋ค.
Navigator์ ๊ธฐ๋ณธ: ํ๋ฉด์ ์๊ณ ๊บผ๋ธ๋ค
Navigator๋ ํ๋ฉด์ ์คํ์ฒ๋ผ ์๋๋ค. push๋ ํ๋ฉด์ ์์ ์ฌ๋ฆฌ๊ณ , pop์ ๋งจ ์ ํ๋ฉด์ ๊บผ๋ธ๋ค. ์๋๋ก์ด๋์ ๋ฐฑ์คํ๊ณผ ๊ฐ๋ค.
// ์์ธ ํ๋ฉด์ผ๋ก ์ด๋
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const DetailPage()),
);
// ์ด์ ํ๋ฉด์ผ๋ก ๋์๊ฐ๊ธฐ
Navigator.pop(context);ํ๋ฉด์ ๊ฐ์ ๋๊ธธ ๋ ์์ฑ์๋ก ๋๊ธด๋ค.
class DetailPage extends StatelessWidget {
final int id;
const DetailPage({super.key, required this.id});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('์์ธ $id')),
body: ElevatedButton(
onPressed: () => Navigator.pop(context, '์ญ์ ๋จ'), // ๋์๊ฐ๋ฉฐ ๊ฒฐ๊ณผ๋ฅผ ๋๋ ค์ค
child: const Text('์ญ์ '),
),
);
}
}
// ํธ์ถํ๋ ์ชฝ
final result = await Navigator.push<String>(
context,
MaterialPageRoute(builder: (context) => const DetailPage(id: 3)),
);
print(result); // ์ญ์ ๋จpush๋ Future๋ฅผ ๋๋ ค์ฃผ๊ณ , ์์ธ ํ๋ฉด์์ pop(context, ๊ฐ)์ผ๋ก ๋ซ์ผ๋ฉด ๊ทธ ๊ฐ์ด await ๊ฒฐ๊ณผ๋ก ์จ๋ค. ๋น๋๊ธฐ ๊ธ์์ ๋ณธ ํจํด์ด ๊ทธ๋๋ก๋ค.
Navigator์ ํ๊ณ: ํ๋ฉด์ด ๋์ด๋๋ฉด ์ฃผ์๊ฐ ์๋ค
์ด ๋ฐฉ์์ ํ๋ฉด ๋์ธ ๊ฐ๊น์ง๋ ํธํ๋ค. ๊ทธ๋ฐ๋ฐ ์ด๋ฐ ์๊ตฌ๊ฐ ์๊ธฐ๋ฉด ๋ถํธํด์ง๋ค.
- ํ๋ฉด๋ง๋ค
MaterialPageRoute(builder: ...)๋ฅผ ๋งค๋ฒ ์ฐ๋, ์ด๋ค ํ๋ฉด์ด ์๋์ง ํ๋์ ์ ๋ณด์ธ๋ค - ์๋ฆผ์ ๋๋ ์ ๋ ํน์ ์์ธ ํ๋ฉด์ผ๋ก ๋ฐ๋ก ๋ค์ด๊ฐ๊ธฐ(๋ฅ๋งํฌ)๋, ์น์์ ์ฃผ์๋ก ์ง์ ๋ค์ด๊ฐ๊ธฐ๊ฐ ์ด๋ ต๋ค
- ๋ก๊ทธ์ธ ์ ํ ์ฌ์ฉ์๋ฅผ ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ๋ณด๋ด๋ ๊ท์น์ ํ ๊ณณ์ ๋ชจ์ผ๊ธฐ ์ด๋ ต๋ค
์ด๋ด ๋ ํ๋ฉด๋ง๋ค ์ฃผ์๋ฅผ ๋ถ์ฌ์ ๊ด๋ฆฌํ๋ go_router๋ฅผ ์ด๋ค.
ํด๊ฒฐ: go_router๋ก ํ๋ฉด์ ์ฃผ์ ๋ถ์ด๊ธฐ
flutter pub add go_routerimport 'package:go_router/go_router.dart';
final router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
),
GoRoute(
path: '/detail/:id', // :id ์๋ฆฌ์ ๊ฐ์ด ๋ค์ด์ด
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(id: int.parse(id));
},
),
],
);
void main() {
runApp(MaterialApp.router(routerConfig: router));
}GoRouter(routes: [...]): ์ฑ์ ํ๋ฉด ๋ชฉ๋ก์ ์ฃผ์์ ํจ๊ป ํ๊ณณ์ ์ ๋๋ค. ์ด๋ค ํ๋ฉด์ด ์๋์ง ์ด ์ฝ๋ ํ๋๋ก ๋ณด์ธ๋คMaterialApp.router(routerConfig: router):MaterialApp๋์MaterialApp.router๋ก ๋ผ์ฐํฐ๋ฅผ ์ฐ๊ฒฐํ๋ค:id: ์ฃผ์์ ์ผ๋ถ๊ฐ ๊ฐ์ด ๋๋ ์๋ฆฌ๋ค.state.pathParameters['id']!๋ก ๊บผ๋ธ๋ค. ์ฃผ์๋ ํญ์ ๋ฌธ์์ด์ด๋ผ ์ซ์๊ฐ ํ์ํ๋ฉดint.parse๋ฅผ ๊ฑฐ์น๋ค.!๋ null ์๋์ ๋จ์ธํ๋, null safety ๊ธ์ ๊ทธ!๋ค
์ด๋ํ ๋ ์ฃผ์๋ก ๋ถ๋ฅธ๋ค.
context.go('/detail/3'); // ์ฃผ์๋ก ์ด๋
context.push('/detail/3'); // ํ๋ฉด์ ์์ ์์ผ๋ฉฐ ์ด๋
context.pop(); // ์ด์ ํ๋ฉด์ผ๋กNavigator.push(context, MaterialPageRoute(...)) ํ ๋ฉ์ด๋ฆฌ๊ฐ context.push('/detail/3') ํ ์ค๋ก ์ค์ด๋ ๋ค. context.go ๊ฐ์ ๊ฑด context์ ๋ถ์ ํ์ฅ ๋ฉ์๋๋ค. extension ๊ธ์์ ๋ณธ ๊ทธ ๋ฌธ๋ฒ์ด ์ฌ๊ธฐ์ ์ฐ์ธ๋ค.
์ด๋ฆ์ผ๋ก ์ด๋ํ ์๋ ์๋ค. GoRoute์ name: 'detail'์ ์ฃผ๋ฉด ์ฃผ์๋ฅผ ๋ฌธ์์ด๋ก ์ฐ๋ค๊ฐ ์คํ ๋ด๋ ์ผ์ ์ค์ธ๋ค.
context.goNamed('detail', pathParameters: {'id': '3'});์ฝ์ง ํฌ์ธํธ: go๋ก ์ด๋ํ๋๋ ๋ค๋ก ๊ฐ๊ธฐ๊ฐ ์๋ค
go์ push๋ ๋น์ทํด ๋ณด์ด์ง๋ง ๋ค๋ฅด๋ค.
context.push: ์ง๊ธ ํ๋ฉด ์์ ์๋๋ค. ๋ค๋ก ๊ฐ๊ธฐ๋ฅผ ๋๋ฅด๋ฉด ์ด์ ํ๋ฉด์ด ๋์จ๋คcontext.go: ์ฃผ์์ ๋ง๊ฒ ํ๋ฉด ์คํ์ ๋ค์ ๊ตฌ์ฑํ๋ค. ์ ๋ผ์ฐํธ ์ค์ ์ฒ๋ผ/์/detail/:id๊ฐ ๋๋ํ ์์ผ๋ฉด,go('/detail/3')๋ค์๋ ์์ธ ํ๋ฉด๋ง ๋จ์์ ์ฑ๋ฐ์ ๋ค๋ก ๊ฐ๊ธฐ ํ์ดํ๊ฐ ์ ์๊ธด๋ค
๋ชฉ๋ก์์ ์์ธ๋ก ๊ฐ๋ ์ด๋์ด๋ผ๋ฉด push๋ฅผ ์ฐ๊ฑฐ๋, ์์ธ๋ฅผ ๋ชฉ๋ก ์๋์ ์ค์ฒฉํด์ go๋ก๋ ๋ค๋ก ๊ฐ ์ ์๊ฒ ํ๋ค.
GoRoute(
path: '/',
builder: (context, state) => const HomePage(),
routes: [
GoRoute(
path: 'detail/:id', // ์์ /๊ฐ ์์. ๋ถ๋ชจ ์ฃผ์ '/' ์๋์ '/detail/:id'๊ฐ ๋๋ค
builder: (context, state) {
final id = state.pathParameters['id']!;
return DetailPage(id: int.parse(id));
},
),
],
),์ค์ฒฉํ๋ฉด go('/detail/3')๋ฅผ ํด๋ ์คํ์ด ํ โ ์์ธ๋ก ์์ฌ์ ๋ค๋ก ๊ฐ ์ ์๋ค. ํ์ ๋ผ์ฐํธ์ path๋ / ์์ด ์ด๋ค๋ ๊ฒ๋ง ์ฃผ์ํ๋ฉด ๋๋ค.
ํ๋ฉด ์ฌ์ด์๋ ๊ฐ์ฒด๊ฐ ์๋๋ผ id๋ง ๋๊ธด๋ค
์์์ DetailPage์ Product ๊ฐ์ฒด ๋์ id๋ง ๋๊ธด ์ด์ ๊ฐ ์๋ค. go_router๋ context.push('/detail/3', extra: product)์ฒ๋ผ ๊ฐ์ฒด๋ฅผ extra๋ก ๋๊ธธ ์ ์๋ค. ํ์ง๋ง extra๋ ์ฃผ์์ ๋ด๊ธฐ์ง ์์์, ์๋ฆผ์ด๋ ์น ์๋ก๊ณ ์นจ์ฒ๋ผ ์ฃผ์๋ง์ผ๋ก ๋ค์ด์ค๋ ๊ฒฝ์ฐ์๋ ๊ฐ์ด ์์ ์ ์๋ค. ๋ ๋ชฉ๋ก์์ ๋๊ธด ๊ฐ์ฒด์ ์๋ฒ์ ์ต์ ๋ฐ์ดํฐ๊ฐ ์ด๊ธ๋ ์๋ ์๋ค. ๊ทธ๋์ id๋ง ๋๊ธฐ๊ณ , ์์ธ ํ๋ฉด์ด id๋ก ๋ฐ์ดํฐ๋ฅผ ๋ค์ ์ฝ๋ ๊ตฌ์กฐ๊ฐ ์์ ํ๋ค. Compose์์ Navigation์ผ๋ก id๋ง ๋๊ธฐ๋ ๊ฒ๊ณผ ๊ฐ์ ์ด์ ๋ค.
๋ก๊ทธ์ธ ์ฌ๋ถ๋ก ํ๋ฉด ๋ง๊ธฐ: redirect
final router = GoRouter(
redirect: (context, state) {
final loggedIn = false; // ์ค์ ๋ก๋ ๋ก๊ทธ์ธ ์ํ๋ฅผ ์ฝ์
if (!loggedIn && state.matchedLocation != '/login') {
return '/login'; // ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ๋ณด๋
}
return null; // null์ด๋ฉด ์๋ ๊ฐ๋ ค๋ ๊ณณ์ผ๋ก ์งํ
},
routes: [ ... ],
);๋ชจ๋ ์ด๋์ด redirect๋ฅผ ๊ฑฐ์น๋ค. ๋๋ ค์ฃผ๋ ์ฃผ์๊ฐ ์์ผ๋ฉด ๊ทธ๋ฆฌ๋ก ๋ณด๋ด๊ณ , null์ด๋ฉด ๊ทธ๋๋ก ์งํํ๋ค. ํผ ๊ฒ์ฆ์ validator์ฒ๋ผ null์ด "๋ฌธ์ ์์"์ด๋ค. ํ๋ฉด๋ง๋ค ๋ก๊ทธ์ธ ์ฒดํฌ๋ฅผ ํฉ์ด ์ฐ์ง ์๊ณ ํ ๊ณณ์ ๋ชจ์ ์ ์๋ค.
Compose, SwiftUI์ ๋น๊ตํ๋ฉด
// Compose - Navigation Compose
NavHost(navController, startDestination = "home") {
composable("home") { HomeScreen(onClick = { navController.navigate("detail/3") }) }
composable("detail/{id}") { entry ->
val id = entry.arguments?.getString("id")
DetailScreen(id)
}
}// SwiftUI - NavigationStack
NavigationStack(path: $path) {
HomeView()
.navigationDestination(for: Int.self) { id in
DetailView(id: id)
}
}
// ์ด๋: path.append(3)- ํ๋ฉด ๋ชฉ๋ก ํ๊ณณ์ ์ ๊ธฐ: Flutter
GoRouter(routes:)/ ComposeNavHost/ SwiftUInavigationDestination - ์ฃผ์ ํํ: Flutter
/detail/:id/ Composedetail/{id}/ SwiftUI๋ ์ฃผ์ ๋์ ๊ฐ(ํ์ )์ผ๋ก ๊ตฌ๋ถ - ์ด๋: Flutter
context.push('/detail/3')/ ComposenavController.navigate("detail/3")/ SwiftUIpath.append(3) - ๋ค๋ก: Flutter
context.pop()/ ComposepopBackStack()/ SwiftUIpath.removeLast() - ์ธ์ ์ ๋ฌ: ์ธ ์ชฝ ๋ชจ๋ ๊ฐ์ฒด๋ณด๋ค id๋ฅผ ๊ถํ๋ค
Compose์ NavHost์ GoRouter๊ฐ ๊ฐ์ฅ ๋ฎ์๋ค. ๋ ๋ค ์ฃผ์ ๋ฌธ์์ด์ {id}, :id ์๋ฆฌ๋ฅผ ๋๊ณ ํ๋ฉด์ ์ฐ๊ฒฐํ๋ค.
์ ๋ฆฌ
Navigator.push(context, ...)๋ ์๋ก ์ฌ๋ผ๊ฐNavigator๋ฅผ ์ฐพ์ผ๋ฏ๋ก,MaterialApp์ ๋ง๋๋build์context๋ก ๋ถ๋ฅด๋ฉด ์๋ฌ๊ฐ ๋๋ค. ๋ฒํผ์MaterialApp์๋์ ์์ ฏ์ผ๋ก ๋ถ๋ฆฌํ๋คNavigator๋ ํ๋ฉด์ ์คํ์ฒ๋ผpushํ๊ณpopํ๋ฉฐ,pop(context, ๊ฐ)์ผ๋ก ์ด์ ํ๋ฉด์ ๊ฒฐ๊ณผ๋ฅผ ๋๋ ค์ค ์ ์๋ค- ํ๋ฉด์ด ๋์ด๋๊ฑฐ๋ ๋ฅ๋งํฌ, ์น ์ฃผ์, ๋ก๊ทธ์ธ ๊ท์น์ด ํ์ํ๋ฉด
go_router๋ก ํ๋ฉด์ ์ฃผ์๋ฅผ ๋ถ์ฌ ํ๊ณณ์์ ๊ด๋ฆฌํ๋ค MaterialApp.router(routerConfig: router)๋ก ์ฐ๊ฒฐํ๊ณ ,context.go,context.push,context.pop์ผ๋ก ์ด๋ํ๋คgo๋ ์คํ์ ์ฃผ์์ ๋ง๊ฒ ๋ค์ ๊ตฌ์ฑํ๊ณpush๋ ์์ ์์ผ๋ฏ๋ก, ๋ค๋ก ๊ฐ๊ธฐ๊ฐ ํ์ํ๋ฉดpush๋ฅผ ์ฐ๊ฑฐ๋ ๋ผ์ฐํธ๋ฅผ ์ค์ฒฉํ๋ค- ํ๋ฉด ์ฌ์ด์๋ ๊ฐ์ฒด ๋์ id๋ง ๋๊ธฐ๊ณ , ๋ก๊ทธ์ธ ๊ฐ์ ๊ณตํต ๊ท์น์
redirect์ ๋ชจ์๋ค
๋ค์ ๊ธ์์๋ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ dio์ HTTP ํต์ ์ ๋ค๋ค๋ณด๊ฒ ๋ค.
