1. Form & Validation
class RegisterForm extends StatefulWidget {
const RegisterForm({super.key});
@override
State<RegisterForm> createState() => _RegisterFormState();
}
class _RegisterFormState extends State<RegisterForm> {
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
controller: _emailController,
decoration: const InputDecoration(labelText: 'Email'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value == null || value.isEmpty) return 'Email là bắt buộc';
final emailRegex = RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$');
if (!emailRegex.hasMatch(value)) return 'Email không hợp lệ';
return null;
},
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: const InputDecoration(labelText: 'Mật khẩu'),
validator: (value) {
if (value == null || value.length < 8) return 'Tối thiểu 8 ký tự';
return null;
},
),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// Submit
}
},
child: const Text('Đăng ký'),
),
],
),
);
}
}
2. Flutter Form Builder
FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTextField(
name: 'email',
decoration: const InputDecoration(labelText: 'Email'),
validator: FormBuilderValidators.compose([
FormBuilderValidators.required(),
FormBuilderValidators.email(),
]),
),
FormBuilderDropdown(
name: 'role',
decoration: const InputDecoration(labelText: 'Vai trò'),
items: ['Admin', 'User', 'Editor']
.map((role) => DropdownMenuItem(value: role, child: Text(role)))
.toList(),
),
FormBuilderDateTimePicker(
name: 'dob',
decoration: const InputDecoration(labelText: 'Ngày sinh'),
inputType: InputType.date,
),
],
),
);
3. ThemeData & Material 3
MaterialApp(
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
brightness: Brightness.light,
textTheme: GoogleFonts.interTextTheme(),
inputDecorationTheme: InputDecorationTheme(
border: OutlineInputBorder(borderRadius: BorderRadius.circular(12)),
filled: true,
),
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
),
),
darkTheme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
brightness: Brightness.dark,
),
themeMode: ThemeMode.system,
);
4. Dynamic Theming
// Dùng Riverpod quản lý theme
final themeModeProvider = StateProvider<ThemeMode>((ref) => ThemeMode.system);
class MyApp extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final themeMode = ref.watch(themeModeProvider);
return MaterialApp(
themeMode: themeMode,
theme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo),
darkTheme: ThemeData(useMaterial3: true, colorSchemeSeed: Colors.indigo, brightness: Brightness.dark),
home: const HomePage(),
);
}
}
// Toggle trong Settings
Switch(
value: ref.watch(themeModeProvider) == ThemeMode.dark,
onChanged: (isDark) {
ref.read(themeModeProvider.notifier).state = isDark ? ThemeMode.dark : ThemeMode.light;
},
);
Bài tiếp theo: HTTP & REST API Integration.