Truyền dữ liệu giữa các Screens
1. Truyền qua Constructor
Cách đơn giản và type-safe nhất:
// Screen nhận data
class ProfileScreen extends StatelessWidget {
final String userId;
final String userName;
const ProfileScreen({
super.key,
required this.userId,
required this.userName,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(userName)),
body: Text('User ID: $userId'),
);
}
}
// Navigate
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ProfileScreen(
userId: '123',
userName: 'John',
),
),
);2. Truyền qua Named Route Arguments
// Navigate với arguments
Navigator.pushNamed(
context,
'/profile',
arguments: ProfileArguments(userId: '123', userName: 'John'),
);
// Arguments class
class ProfileArguments {
final String userId;
final String userName;
ProfileArguments({required this.userId, required this.userName});
}
// Nhận trong screen
class ProfileScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final args = ModalRoute.of(context)!.settings.arguments as ProfileArguments;
return Text('User: ${args.userName}');
}
}3. Nhận data khi Pop
Sử dụng Navigator.pop với result
// Screen B - Pop với data
Navigator.pop(context, 'Selected value');
// Screen A - Nhận data sau khi push
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (_) => SelectionScreen()),
);
if (result != null) {
print('Selected: $result');
}Ví dụ: Color Picker
// Color picker screen
class ColorPickerScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Pick a Color')),
body: ListView(
children: [
ListTile(
title: Text('Red'),
tileColor: Colors.red,
onTap: () => Navigator.pop(context, Colors.red),
),
ListTile(
title: Text('Blue'),
tileColor: Colors.blue,
onTap: () => Navigator.pop(context, Colors.blue),
),
ListTile(
title: Text('Green'),
tileColor: Colors.green,
onTap: () => Navigator.pop(context, Colors.green),
),
],
),
);
}
}
// Main screen
class MainScreen extends StatefulWidget {
@override
State<MainScreen> createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
Color _selectedColor = Colors.grey;
Future<void> _pickColor() async {
final color = await Navigator.push<Color>(
context,
MaterialPageRoute(builder: (_) => ColorPickerScreen()),
);
if (color != null) {
setState(() => _selectedColor = color);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
width: 100,
height: 100,
color: _selectedColor,
),
),
floatingActionButton: FloatingActionButton(
onPressed: _pickColor,
child: Icon(Icons.color_lens),
),
);
}
}4. Với GoRouter
Path Parameters
// Route
GoRoute(
path: '/user/:userId',
builder: (context, state) {
final userId = state.pathParameters['userId']!;
return UserScreen(userId: userId);
},
),
// Navigate
context.go('/user/123');Extra Data
// Navigate với object
context.go('/product', extra: product);
// Route
GoRoute(
path: '/product',
builder: (context, state) {
final product = state.extra as Product;
return ProductScreen(product: product);
},
),5. Với State Management
Provider
// Provider lưu selected item
class SelectionProvider extends ChangeNotifier {
Product? _selectedProduct;
Product? get selectedProduct => _selectedProduct;
void select(Product product) {
_selectedProduct = product;
notifyListeners();
}
}
// Screen A - Select
context.read<SelectionProvider>().select(product);
Navigator.pushNamed(context, '/detail');
// Screen B - Use
final product = context.watch<SelectionProvider>().selectedProduct;6. Callback Function
Truyền callback để nhận data:
// Parent screen
class ParentScreen extends StatefulWidget {
@override
State<ParentScreen> createState() => _ParentScreenState();
}
class _ParentScreenState extends State<ParentScreen> {
String _result = '';
void _handleResult(String value) {
setState(() => _result = value);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('Result: $_result'),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => ChildScreen(onSelect: _handleResult),
),
);
},
child: Text('Open Child'),
),
],
);
}
}
// Child screen
class ChildScreen extends StatelessWidget {
final Function(String) onSelect;
const ChildScreen({super.key, required this.onSelect});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
onSelect('Selected value');
Navigator.pop(context);
},
child: Text('Select'),
);
}
}7. So sánh các cách
| Cách | Pros | Cons |
|---|---|---|
| Constructor | Type-safe, simple | Chỉ một chiều |
| Arguments | Dùng với named routes | Không type-safe |
| Pop result | Hai chiều | Async, cần await |
| State Management | Chia sẻ giữa nhiều screens | Setup overhead |
| Callback | Type-safe, hai chiều | Coupling |
8. Best Practices
- Ưu tiên constructor cho one-way data
- Dùng pop result cho selection screens
- Dùng state management cho shared state
- Type-safe arguments với custom classes
// Good: Type-safe argument class
class ProductDetailArgs {
final String productId;
final String productName;
final bool isEditing;
ProductDetailArgs({
required this.productId,
required this.productName,
this.isEditing = false,
});
}📝 Tóm tắt
| Method | Direction | Type-safe |
|---|---|---|
| Constructor | → | ✅ |
| Route arguments | → | ⚠️ (cần cast) |
| Pop result | ← | ✅ |
| State management | ↔ | ✅ |
| Callback | ↔ | ✅ |
Last updated on