Hướng dẫn và ví dụ Flutter flutter_form_builder
flutter_form_builder là một thư viện Flutter cho phép bạn xây dựng một biểu mẫu thu thập dữ liệu từ người dùng môt cách ngắn gọn và đơn giản, loại bỏ sự rườm rà. Gói flutter_form_builder được kết hợp với gói form_builder_validators để xác thực (validate) các trường và phản hồi các thay đổi.
Nếu bạn thường xuyên phải sử dụng TextField bạn sẽ cảm thấy mệt mỏi với TextEditingController vì chúng luôn phải kết hợp với nhau. Thật may mắn với thư viện này bạn có thể sử dụng FormBuilderTextField để thay thế mà không cần tới Controller.
Thư viện này cung cấp các trường đầu vào thông dụng, ngoài ra bạn cũng thể tạo ra các trường đầu vào tuỳ biến bằng cách sử dụng hoặc mở rộng lớp cơ sở FormBuilderField.
- Tuỳ biến flutter_form_builder FormBuiderField
Các trường đầu vào (input field) thông dụng được cung cấp bởi thư viện này:
FormBuilderCheckbox | Một trường Checkbox đơn. |
FormBuilderCheckboxGroup | Danh sách các Checkbox cho phép đa lựa chọn. |
FormBuilderChoiceChip | Tạo một Chip hoạt động giống như một Radio. |
FormBuilderDateRangePicker | Cho phép lựa chọn một phạm vi ngày tháng. |
FormBuilderDateTimePicker | Cho phép đầu vào Date, Time và DateTime. |
FormBuilderDropdown | Được sử dụng để lựa chọn một giá trị từ một danh sách thả xuống. |
FormBuilderFilterChip | Tạo một nhóm các Chip hoạt động giống như một nhóm các Checkbox. |
FormBuilderRadioGroup | Được sử dụng để lựa chọn một giá trị từ một danh sách các Radio. |
FormBuilderRangeSlider | Được sử dụng để chọn một phạm vi từ một phạm vi giá trị. |
FormBuilderSlider | Để chọn một giá trị số trên một Slider (thanh trượt). |
FormBuilderSwitch | Một trường bật/tắt (On/Off). |
FormBuilderTextField | Một trường văn bản đầu vào hoạt động như một TextField. |
Các gói thư viện liên quan:
- Flutter form_builder_validators
Gói thư viện form_builder_extra_fields cung cấp các trường đầu vào bổ xung như:FormBuilderColorPicker, FormBuilderRating, FormBuilderSearchableDropdown, FormBuilderSignaturePad, FormBuilderTouchSpin, FormBuilderTypeAhead ,...
- Flutter form_builder_extra_fields
- Flutter form_builder_file_picker
- Flutter form_builder_image_picker
1. Cài đặt thư viện
Gói thư viện flutter_form_builder cung cấp các trường đầu vào thông dụng, trong khi form_builder_validators được sử dụng để xác thực (validate) dữ liệu.
pubspec.yaml
dependencies:
flutter_form_builder: ^9.3.0
form_builder_validators: ^11.0.0
2. Ví dụ cơ bản
Chúng ta hãy bắt đầu với một ví dụ cơ bản sử dụng gói flutter_form_builder.
FormBuilder |
Tất cả các trường đầu vào cần được đặt trong một FormBuilder, nó giúp bạn lưu trữ dữ liệu của tất cả các trường, hỗ trợ xác thực dữ liệu ở mức toàn cục và nhận biết sự thay đổi dữ liệu của toàn biểu mẫu (form). |
main_ex1.dart
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:form_builder_validators/form_builder_validators.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
home: MyLoginPage(),
);
}
}
class MyLoginPage extends StatefulWidget {
const MyLoginPage({super.key});
@override
State<MyLoginPage> createState() => _MyLoginPageState();
}
class _MyLoginPageState extends State<MyLoginPage> {
final _formKey = GlobalKey<FormBuilderState>();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text("Flutter FormBuilder Demo"),
),
body: FormBuilder(
key: _formKey,
child: Column(
children: [
FormBuilderTextField(
name: 'email',
decoration: const InputDecoration(labelText: 'Email'),
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(),
FormBuilderValidators.email(),
],
),
),
const SizedBox(height: 10),
FormBuilderTextField(
name: 'password',
decoration: const InputDecoration(labelText: 'Password'),
obscureText: true,
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(),
],
),
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
bool isValid = _formKey.currentState?.validate(
autoScrollWhenFocusOnInvalid: true,
focusOnInvalid: true,
) ??
false;
debugPrint(_formKey.currentState?.instantValue.toString());
//
if (isValid) {
// Submit to Server...
}
},
child: const Text('Login'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: () {
_formKey.currentState?.reset();
},
child: const Text('Reset'),
)
],
)
],
),
),
);
}
}
Thông qua "key" của FormBuider bạn có thể có được dữ liệu hiện thời của Form và kiểm tra trạng thái hợp lệ của nó. Tự động nhẩy tới trường dữ liệu không hợp lệ để người dùng nhập lại.
bool isValid = _formKey.currentState?.validate(
autoScrollWhenFocusOnInvalid: true,
focusOnInvalid: true,
) ??
false;
debugPrint(_formKey.currentState?.instantValue.toString());
//
if (isValid) {
// Submit to Server...
}
Output:
{email: tom@example.com, password: 123}
Bạn cũng có thể lưu trữ các giá trị của biểu mẫu như một trạng thái ban đầu mới, các giá trị này sẽ được khôi phục nếu người dùng nhấn vào nút "Reset" (Thiết lập lại dữ liệu).
// _formKey.currentState?.save(..)
// Save state of data and validate data.
bool isValid = _formKey.currentState?.saveAndValidate(
autoScrollWhenFocusOnInvalid: true,
focusOnInvalid: true,
) ??
false;
3. FormBuilderTextField
TextField là trường đầu vào thông dụng nhất để thu thập dữ liệu từ người dùng, nó cần được sử dụng cùng với TextEditingController khiến việc viết mã trở lên dài dòng. FormBuilderTextField là một sự thay thế hoàn hảo mà không cần tới Controller, nó chắc chắn là widget giá trị nhất của thư viện này.
main_textfield_ex1.dart (*)
FormBuilderTextField(
autovalidateMode: AutovalidateMode.always,
name: 'age',
decoration: InputDecoration(
labelText: 'Age',
suffixIcon: _ageHasError
? const Icon(Icons.error, color: Colors.red)
: const Icon(Icons.check, color: Colors.green),
),
onChanged: (val) {
setState(() {
_ageHasError =
!(_formKey.currentState?.fields['age']?.validate() ??
false);
});
},
// valueTransformer: (text) => num.tryParse(text),
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(),
FormBuilderValidators.numeric(),
FormBuilderValidators.max(70),
],
),
// initialValue: '12',
keyboardType: TextInputType.number,
textInputAction: TextInputAction.next,
),
4. FormBuilderCheckbox
FormBuiderCheckbox là một widget được xây dựng trên CheckboxListTile. Theo mặc định nó hỗ trợ hai trạng thái "checked" & "unchecked".
- Hướng dẫn và ví dụ Flutter CheckboxListTile
main_checkbox_ex1.dart (*)
FormBuilderCheckbox(
name: 'accept_terms',
initialValue: false,
onChanged: (bool? value) {
// Code..
},
title: RichText(
text: TextSpan(
children: [
const TextSpan(
text: 'I have read and agree to the ',
style: TextStyle(color: Colors.black),
),
WidgetSpan(
alignment: PlaceholderAlignment.middle,
child: InkWell(
child: const Text(
"Terms and Conditions",
style: TextStyle(color: Colors.blue),
),
onTap: () {
// Open Link
},
),
),
],
),
),
validator: FormBuilderValidators.equal(
true,
errorText: 'You must accept terms and conditions to continue',
),
),
Ví dụ một FormBuiderCheckbox hỗ trợ 3 trạng thái:
FormBuilderCheckbox(
name: 'checkboxName',
initialValue: false,
tristate: true,
onChanged: (bool? value) {
// Code..
},
title: const Text("Title"),
subtitle: const Text("Subtitle"),
secondary: const Icon(Icons.image),
),
5. FormBuilderSwitch
FormBuilderSwitch cho phép người dùng "On/Off" (Bật/tắt) một thiết lập đơn. Nó được xây dựng dựa trên SwitchListTile sẵn có trong Flutter.
main_switch_ex1.dart (*)
FormBuilderSwitch(
title: const Text('I Accept the terms and conditions'),
name: 'accept_terms_switch',
initialValue: false,
onChanged: (bool? value) {
print("Value: $value");
},
validator: FormBuilderValidators.equal(
true,
errorText: 'You must accept terms and conditions to continue',
),
),
- Hướng dẫn và ví dụ Flutter Switch
- Hướng dẫn và ví dụ Flutter SwitchListTile
6. FormBuilderCheckboxGroup
FormBuilderCheckboxGroup được sử dụng để tạo ra một danh sách các Checkbox cho phép người dùng chọn nhiều tuỳ chọn. Các tuỳ chọn có thể được xắp xếp trên một hàng, một cột hoặc có thể cuộn lại (wrap).
main_checkboxgroup_ex1.dart (*)
FormBuilderCheckboxGroup<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'Select Languages:',
),
name: 'languages',
initialValue: const ['Dart'],
options: const [
FormBuilderFieldOption(value: 'Dart'),
FormBuilderFieldOption(value: 'Kotlin'),
FormBuilderFieldOption(value: 'Java'),
FormBuilderFieldOption(value: 'Swift'),
FormBuilderFieldOption(value: 'Objective-C'),
],
onChanged: (List<String>? langs) {
// Code..
},
separator: const SizedBox(width: 8.0),
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.minLength(1),
FormBuilderValidators.maxLength(3),
],
),
orientation: OptionsOrientation.wrap,
),
FormBuilderCheckboxGroup<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
decoration: const InputDecoration(
labelText: 'Select Languages:',
),
name: 'languages',
initialValue: const ['Dart'],
options: const [
FormBuilderFieldOption(value: 'Dart'),
FormBuilderFieldOption(value: 'Kotlin'),
FormBuilderFieldOption(value: 'Java'),
FormBuilderFieldOption(value: 'Swift'),
FormBuilderFieldOption(value: 'Javascript'),
FormBuilderFieldOption(value: 'Delphi'),
FormBuilderFieldOption(value: 'Pascal'),
FormBuilderFieldOption(value: 'C/C++'),
FormBuilderFieldOption(value: 'Golang'),
FormBuilderFieldOption(value: 'Visual Basic'),
],
orientation: OptionsOrientation.wrap,
separator: const SizedBox(
height: 20,
child: VerticalDivider(),
),
wrapSpacing: 10,
wrapRunSpacing: 20,
wrapRunAlignment: WrapAlignment.end,
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.minLength(1),
FormBuilderValidators.maxLength(3),
],
),
onChanged: (List<String>? langs) {
// Code..
},
),
7. FormBuilderRadioGroup
FormBuilderRadioGroup được sử dụng để tạo ra một danh sách các Radio cho phép người dùng chọn nhiều nhất một tuỳ chọn trong danh sách các tuỳ chọn. Các tuỳ chọn có thể được xắp xếp trên một hàng, một cột hoặc có thể cuộn lại (wrap).
main_radiogroup_ex1.dart (*)
FormBuilderRadioGroup<String>(
decoration: const InputDecoration(
labelText: 'My chosen language',
),
initialValue: null,
name: 'best_language',
onChanged: (String? value) {
print("Value: $value");
},
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(),
],
),
options: ['Dart', 'Kotlin', 'Java', 'Swift', 'Objective-C']
.map(
(lang) => FormBuilderFieldOption(
value: lang,
child: Text(lang),
),
)
.toList(growable: false),
controlAffinity: ControlAffinity.trailing,
)
8. FormBuilderChoiceChip
FormBuilderChoiceChip được sử dụng để tạo ra một danh sách các Chip cho phép người dùng chọn nhiều nhất một tuỳ chọn trong danh sách các tuỳ chọn. Các tuỳ chọn có thể được xắp xếp trên một hàng, một cột hoặc có thể cuộn lại (wrap).
main_choicechip_ex1.dart (*)
FormBuilderChoiceChip<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
spacing: 5,
labelPadding: const EdgeInsets.all(0),
visualDensity: const VisualDensity(
vertical: -3,
horizontal: -3,
),
decoration: const InputDecoration(
labelText: 'Select Languages:',
isDense: true,
contentPadding: EdgeInsets.all(10),
),
name: 'languages_choice',
initialValue: 'Dart',
options: const [
FormBuilderChipOption(
value: 'Dart',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Kotlin',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Java',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Swift',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Objective-C',
avatar: CircleAvatar(),
),
],
onChanged: (String? value) {
print("Value: $value");
},
),
9. FormBuilderFilterChip
FormBuilderFilterChip được sử dụng để tạo ra một danh sách các Chip cho phép người dùng chọn nhiều tuỳ chọn trong danh sách các tuỳ chọn. Các tuỳ chọn có thể được xắp xếp trên một hàng, một cột hoặc có thể cuộn lại (wrap).
Về cơ bản, FormBuilderFilterChip có chức năng giống với FormBuilderCheckboxGroup, nó thường được sử dụng để người dùng lựa chọn các tiêu chí tìm kiếm và lọc dữ liệu.
main_filterchip_ex1.dart (*)
FormBuilderFilterChip<String>(
autovalidateMode: AutovalidateMode.onUserInteraction,
spacing: 10,
decoration: const InputDecoration(
labelText: 'Select Languages',
),
name: 'languages_filter',
selectedColor: Colors.blue.withAlpha(80),
options: const [
FormBuilderChipOption(
value: 'Dart',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Kotlin',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Java',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Swift',
avatar: CircleAvatar(),
),
FormBuilderChipOption(
value: 'Objective-C',
avatar: CircleAvatar(),
),
],
onChanged: (List<String>? values) {
// Code..
},
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.minLength(1),
FormBuilderValidators.maxLength(3),
],
),
),
10. FormBuilderDateRangePicker
FormBuilderDateRangePicker cho phép người dùng lựa chọn một phạm vi ngày tháng (Từ ngày .. đến ngày).
main_daterangepicker_ex1.dart (*)
FormBuilderDateRangePicker(
name: 'date_range',
firstDate: DateTime(1970),
lastDate: DateTime(2030),
format: DateFormat('yyyy-MM-dd'),
initialValue: DateTimeRange(
start: DateTime(2000, 1, 1),
end: DateTime(2020, 2, 1),
),
onChanged: (DateTimeRange? range) {
print("Range: $range");
},
decoration: InputDecoration(
labelText: 'Date Range',
helperText: 'Helper text',
hintText: 'Hint text',
suffixIcon: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_formKey.currentState!.fields['date_range']
?.didChange(null);
},
),
),
),
11. FormBuilderDateTimePicker
FormBuilderDateTimePicker cho phép người dùng lựa chọn ngày tháng và thời gian.
main_datetimepicker_ex1.dart (*)
FormBuilderDateTimePicker(
name: 'date',
initialEntryMode: DatePickerEntryMode.calendar,
initialValue: DateTime.now(),
inputType: InputType.both,
decoration: InputDecoration(
labelText: 'Appointment Time',
suffixIcon: IconButton(
icon: const Icon(Icons.close),
onPressed: () {
_formKey.currentState!.fields['date']?.didChange(null);
},
),
),
initialTime: const TimeOfDay(hour: 8, minute: 0),
),
12. FormBuilderDropdown
FormBuilderDropdown cho phép người dùng lựa chọn một tuỳ chọn từ danh sách thả xuống.
main_dropdown_ex1.dart (*)
FormBuilderDropdown<String>(
name: 'gender',
decoration: InputDecoration(
labelText: 'Gender',
suffix: _genderHasError
? const Icon(Icons.error)
: const Icon(Icons.check),
hintText: 'Select Gender',
),
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.required(),
],
),
items: ['Male', 'Female', 'Other']
.map(
(gender) => DropdownMenuItem(
alignment: AlignmentDirectional.centerStart,
value: gender,
child: Text(gender),
),
)
.toList(),
onChanged: (val) {
bool hasError =
!(_formKey.currentState?.fields['gender']?.validate() ??
false);
setState(() {
_genderHasError = hasError;
});
},
valueTransformer: (val) => val?.toString(),
),
- Flutter DropdownMenu (***)
13. FormBuilderSlider
FormBuilderSlider cho phép người dùng lựa chọn một giá trị số trong một khoảng giá trị. Slider có một cái cần gạt (thumb). Bạn có thể chạm vào cần gạt, sử dụng phím mũi tên hoặc chạm vào thanh bên trái hoặc bên phải để di chuyển cần gạt.
main_slider_ex1.dart (*)
FormBuilderSlider(
name: 'slider',
validator: FormBuilderValidators.compose(
[
FormBuilderValidators.min(6),
],
),
onChanged: (double? value) {
print("Value: $value");
},
min: 0.0,
max: 10.0,
initialValue: 7.0,
divisions: 20,
activeColor: Colors.red,
inactiveColor: Colors.pink[100],
decoration: const InputDecoration(
labelText: 'Number of things',
),
),
14. FormBuilderRangeSlider
FormBuilderRangeSlider cho phép người dùng lựa chọn một khoảng giá trị số.
main_rangeslider_ex1.dart (*)
FormBuilderRangeSlider(
name: 'range_slider',
onChanged: (RangeValues? range) {
print("Range: $range");
},
min: 0.0,
max: 100.0,
initialValue: const RangeValues(4, 7),
divisions: 20,
maxValueWidget: (max) => TextButton(
onPressed: () {
_formKey.currentState?.patchValue(
{'range_slider': const RangeValues(4, 100)},
);
},
child: Text(max),
),
activeColor: Colors.red,
inactiveColor: Colors.pink[100],
decoration: const InputDecoration(labelText: 'Price Range'),
),
- Flutter RangeSlider (***)
Các hướng dẫn lập trình Flutter
- Hướng dẫn và ví dụ Flutter Column
- Hướng dẫn và ví dụ Flutter Stack
- Hướng dẫn và ví dụ Flutter IndexedStack
- Hướng dẫn và ví dụ Flutter Spacer
- Hướng dẫn và ví dụ Flutter Expanded
- Hướng dẫn và ví dụ Flutter SizedBox
- Hướng dẫn và ví dụ Flutter Tween
- Cài đặt Flutter SDK trên Windows
- Cài đặt Flutter Plugin cho Android Studio
- Tạo ứng dụng Flutter đầu tiên của bạn - Hello Flutter
- Hướng dẫn và ví dụ Flutter Scaffold
- Hướng dẫn và ví dụ Flutter AppBar
- Hướng dẫn và ví dụ Flutter BottomAppBar
- Hướng dẫn và ví dụ Flutter SliverAppBar
- Hướng dẫn và ví dụ Flutter TextButton
- Hướng dẫn và ví dụ Flutter ElevatedButton
- Hướng dẫn và ví dụ Flutter ShapeBorder
- Hướng dẫn và ví dụ Flutter EdgeInsetsGeometry
- Hướng dẫn và ví dụ Flutter EdgeInsets
- Hướng dẫn và ví dụ Flutter CircularProgressIndicator
- Hướng dẫn và ví dụ Flutter LinearProgressIndicator
- Hướng dẫn và ví dụ Flutter Center
- Hướng dẫn và ví dụ Flutter Align
- Hướng dẫn và ví dụ Flutter Row
- Hướng dẫn và ví dụ Flutter SplashScreen
- Hướng dẫn và ví dụ Flutter Alignment
- Hướng dẫn và ví dụ Flutter Positioned
- Hướng dẫn và ví dụ Flutter ListTile
- Hướng dẫn và ví dụ Flutter SimpleDialog
- Hướng dẫn và ví dụ Flutter AlertDialog
- Navigation và Routing trong Flutter
- Hướng dẫn và ví dụ Flutter Navigator
- Hướng dẫn và ví dụ Flutter TabBar
- Hướng dẫn và ví dụ Flutter Banner
- Hướng dẫn và ví dụ Flutter BottomNavigationBar
- Hướng dẫn và ví dụ Flutter FancyBottomNavigation
- Hướng dẫn và ví dụ Flutter Card
- Hướng dẫn và ví dụ Flutter Border
- Hướng dẫn và ví dụ Flutter ContinuousRectangleBorder
- Hướng dẫn và ví dụ Flutter RoundedRectangleBorder
- Hướng dẫn và ví dụ Flutter CircleBorder
- Hướng dẫn và ví dụ Flutter StadiumBorder
- Hướng dẫn và ví dụ Flutter Container
- Hướng dẫn và ví dụ Flutter RotatedBox
- Hướng dẫn và ví dụ Flutter CircleAvatar
- Hướng dẫn và ví dụ Flutter TextField
- Hướng dẫn và ví dụ Flutter IconButton
- Hướng dẫn và ví dụ Flutter FlatButton
- Hướng dẫn và ví dụ Flutter SnackBar
- Hướng dẫn và ví dụ Flutter Drawer
- Ví dụ Flutter Navigator pushNamedAndRemoveUntil
- Hiển thị hình ảnh trên Internet trong Flutter
- Hiển thị ảnh Asset trong Flutter
- Flutter TextInputType các kiểu bàn phím
- Hướng dẫn và ví dụ Flutter NumberTextInputFormatter
- Hướng dẫn và ví dụ Flutter Builder
- Làm sao xác định chiều rộng của Widget cha trong Flutter
- Bài thực hành Flutter thiết kế giao diện màn hình đăng nhập
- Bài thực hành Flutter thiết kế giao diện trang (1)
- Khuôn mẫu thiết kế Flutter với các lớp trừu tượng
- Bài thực hành Flutter thiết kế trang Profile với Stack
- Bài thực hành Flutter thiết kế trang profile (2)
- Hướng dẫn và ví dụ Flutter ListView
- Hướng dẫn và ví dụ Flutter GridView
- Bài thực hành Flutter với gói http và dart:convert (2)
- Bài thực hành Flutter với gói http và dart:convert (1)
- Ứng dụng Flutter Responsive với Menu Drawer
- Flutter GridView với SliverGridDelegate tuỳ biến
- Hướng dẫn và ví dụ Flutter image_picker
- Flutter upload ảnh sử dụng http và ImagePicker
- Hướng dẫn và ví dụ Flutter SharedPreferences
- Chỉ định cổng cố định cho Flutter Web trên Android Studio
- Tạo Module trong Flutter
- Hướng dẫn và ví dụ Flutter SkeletonLoader
- Hướng dẫn và ví dụ Flutter Slider
- Hướng dẫn và ví dụ Flutter Radio
- Bài thực hành Flutter SharedPreferences
- Hướng dẫn và ví dụ Flutter InkWell
- Hướng dẫn và ví dụ Flutter GetX GetBuilder
- Hướng dẫn và ví dụ Flutter GetX obs Obx
- Hướng dẫn và ví dụ Flutter flutter_form_builder
- Xử lý lỗi 404 trong Flutter GetX
- Ví dụ đăng nhập và đăng xuất với Flutter Getx
- Hướng dẫn và ví dụ Flutter multi_dropdown
Show More