Skip to main content

2 posts tagged with "GetX"

View All Tags

5 Phương pháp quản lý state phổ biến trong Flutter

· 8 min read

Flutter State Management

Giới thiệu

Quản lý state là một trong những khía cạnh quan trọng nhất khi phát triển ứng dụng Flutter. Việc chọn đúng phương pháp quản lý state sẽ giúp code của bạn dễ bảo trì, mở rộng và hiệu quả hơn. Trong bài viết này, chúng ta sẽ khám phá 5 phương pháp quản lý state phổ biến nhất trong Flutter: Provider, Riverpod, Bloc, GetX, và MobX.

1. Provider

Tổng quan

Provider là giải pháp quản lý state được Google khuyến nghị và được xây dựng dựa trên InheritedWidget. Nó cung cấp một cách đơn giản và hiệu quả để quản lý state trong ứng dụng Flutter.

Đặc điểm

  • Đơn giản và dễ học: Syntax rõ ràng, dễ hiểu
  • Được Google khuyến nghị: Chính thức được Flutter team hỗ trợ
  • Hiệu suất tốt: Chỉ rebuild các widget cần thiết
  • Tích hợp tốt với Flutter: Sử dụng các widget có sẵn
  • Không cần code generation: Không cần build_runner

Khi nào sử dụng

  • Ứng dụng nhỏ đến trung bình
  • Team mới bắt đầu với Flutter
  • Cần giải pháp đơn giản, ít boilerplate
  • Muốn tuân theo best practices của Flutter

Ví dụ cơ bản

// Counter Provider
class CounterProvider extends ChangeNotifier {
int _count = 0;
int get count => _count;

void increment() {
_count++;
notifyListeners();
}
}

// Sử dụng
Consumer<CounterProvider>(
builder: (context, counter, child) {
return Text('Count: ${counter.count}');
},
)

Ưu điểm

  • Dễ học và sử dụng
  • Tài liệu phong phú
  • Cộng đồng lớn
  • Phù hợp với hầu hết các use case

Nhược điểm

  • Có thể phức tạp với ứng dụng lớn
  • Cần quản lý lifecycle thủ công
  • Không có compile-time safety mạnh

2. Riverpod

Tổng quan

Riverpod là phiên bản cải tiến của Provider, được tạo ra bởi cùng một tác giả. Nó giải quyết nhiều vấn đề của Provider và cung cấp compile-time safety tốt hơn.

Đặc điểm

  • Compile-time safety: Phát hiện lỗi tại compile time
  • Không cần BuildContext: Có thể truy cập providers từ bất kỳ đâu
  • Testable: Dễ dàng test và mock
  • Provider dependencies: Quản lý dependencies tự động
  • Code generation: Hỗ trợ code generation (optional)

Khi nào sử dụng

  • Ứng dụng lớn, phức tạp
  • Cần type safety cao
  • Muốn test dễ dàng
  • Cần quản lý dependencies tốt hơn

Ví dụ cơ bản

// Counter Provider
final counterProvider = StateNotifierProvider<CounterNotifier, int>((ref) {
return CounterNotifier();
});

class CounterNotifier extends StateNotifier<int> {
CounterNotifier() : super(0);

void increment() => state++;
}

// Sử dụng
Consumer(
builder: (context, ref, child) {
final count = ref.watch(counterProvider);
return Text('Count: $count');
},
)

Ưu điểm

  • Type-safe hơn Provider
  • Không cần BuildContext
  • Dễ test
  • Quản lý dependencies tốt
  • Hỗ trợ code generation

Nhược điểm

  • Learning curve cao hơn Provider
  • Syntax phức tạp hơn một chút
  • Cộng đồng nhỏ hơn Provider

3. Bloc

Tổng quan

Bloc (Business Logic Component) là một pattern quản lý state dựa trên các khái niệm từ ReactiveX. Nó tách biệt business logic khỏi UI và sử dụng streams để quản lý state.

Đặc điểm

  • Separation of concerns: Tách biệt logic và UI rõ ràng
  • Predictable: State changes có thể dự đoán được
  • Testable: Dễ test business logic
  • Reactive: Sử dụng streams và reactive programming
  • Time-travel debugging: Hỗ trợ debugging tốt

Khi nào sử dụng

  • Ứng dụng lớn, phức tạp
  • Cần quản lý business logic phức tạp
  • Team có kinh nghiệm với reactive programming
  • Cần test coverage cao

Ví dụ cơ bản

// Events
abstract class CounterEvent {}
class IncrementEvent extends CounterEvent {}

// Bloc
class CounterBloc extends Bloc<CounterEvent, int> {
CounterBloc() : super(0) {
on<IncrementEvent>((event, emit) => emit(state + 1));
}
}

// Sử dụng
BlocBuilder<CounterBloc, int>(
builder: (context, count) {
return Text('Count: $count');
},
)

Ưu điểm

  • Architecture rõ ràng
  • Dễ test business logic
  • Hỗ trợ time-travel debugging
  • Phù hợp với ứng dụng lớn

Nhược điểm

  • Boilerplate code nhiều
  • Learning curve cao
  • Có thể overkill cho ứng dụng nhỏ

4. GetX

Tổng quan

GetX là một giải pháp all-in-one cho Flutter, không chỉ quản lý state mà còn cung cấp routing, dependency injection, và nhiều tính năng khác.

Đặc điểm

  • All-in-one: State management + Routing + DI
  • Hiệu suất cao: Tối ưu rebuild
  • Syntax đơn giản: Code ngắn gọn
  • Không cần BuildContext: Truy cập từ bất kỳ đâu
  • Tích hợp nhiều tính năng: Navigation, dialogs, snackbars

Khi nào sử dụng

  • Muốn giải pháp all-in-one
  • Cần routing đơn giản
  • Ưu tiên code ngắn gọn
  • Ứng dụng nhỏ đến trung bình

Ví dụ cơ bản

// Controller
class CounterController extends GetxController {
var count = 0.obs;

void increment() => count++;
}

// Sử dụng
final controller = Get.put(CounterController());

Obx(() => Text('Count: ${controller.count}'))

Ưu điểm

  • All-in-one solution
  • Code ngắn gọn
  • Hiệu suất tốt
  • Dễ sử dụng

Nhược điểm

  • Không được Flutter team chính thức khuyến nghị
  • Có thể khó maintain với ứng dụng lớn
  • Tight coupling giữa các tính năng

5. MobX

Tổng quan

MobX là một state management library được port từ JavaScript. Nó sử dụng reactive programming và code generation để quản lý state.

Đặc điểm

  • Reactive: Tự động update khi state thay đổi
  • Minimal boilerplate: Ít code lặp lại
  • Observable pattern: Sử dụng observables
  • Code generation: Sử dụng build_runner
  • Familiar: Nếu đã biết MobX từ JS/React

Khi nào sử dụng

  • Đã quen với MobX từ JavaScript/React
  • Cần reactive programming
  • Muốn ít boilerplate
  • Ứng dụng có state phức tạp

Ví dụ cơ bản

// Store
class CounterStore = _CounterStore with _$CounterStore;

abstract class _CounterStore with Store {

int count = 0;


void increment() => count++;
}

// Sử dụng
Observer(
builder: (_) => Text('Count: ${store.count}'),
)

Ưu điểm

  • Reactive và tự động
  • Ít boilerplate
  • Familiar cho dev từ JS background
  • Dễ sử dụng

Nhược điểm

  • Cần code generation
  • Cộng đồng nhỏ hơn
  • Learning curve nếu chưa quen với reactive

So sánh tổng quan

Tiêu chíProviderRiverpodBlocGetXMobX
Độ phổ biến⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Dễ học⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Type Safety⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Boilerplate⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Testability⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Hiệu suất⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Tài liệu⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

Kết luận và khuyến nghị

Chọn Provider nếu:

  • Bạn mới bắt đầu với Flutter
  • Ứng dụng nhỏ đến trung bình
  • Muốn giải pháp đơn giản, được Google khuyến nghị

Chọn Riverpod nếu:

  • Bạn đã quen với Provider
  • Cần type safety cao
  • Ứng dụng lớn, phức tạp
  • Ưu tiên testability

Chọn Bloc nếu:

  • Ứng dụng lớn, phức tạp
  • Cần separation of concerns rõ ràng
  • Team có kinh nghiệm với reactive programming
  • Cần test coverage cao

Chọn GetX nếu:

  • Muốn giải pháp all-in-one
  • Ưu tiên code ngắn gọn
  • Ứng dụng nhỏ đến trung bình
  • Cần routing đơn giản

Chọn MobX nếu:

  • Đã quen với MobX từ JavaScript
  • Cần reactive programming
  • Muốn ít boilerplate
  • State phức tạp

Lời khuyên cuối cùng

  1. Bắt đầu đơn giản: Nếu mới bắt đầu, hãy thử Provider trước
  2. Xem xét dự án: Chọn phương pháp phù hợp với quy mô và độ phức tạp
  3. Team experience: Xem xét kinh nghiệm của team
  4. Long-term: Nghĩ về khả năng maintain và scale
  5. Thử nghiệm: Có thể thử nhiều phương pháp để tìm ra cái phù hợp nhất

Quản lý state là một phần quan trọng của Flutter development. Chọn đúng phương pháp sẽ giúp bạn xây dựng ứng dụng tốt hơn!

GetX: Quản lý state + Routing + Dependency Injection

· 10 min read

Flutter GetX Complete Guide

Giới thiệu

GetX là một giải pháp all-in-one mạnh mẽ cho Flutter, cung cấp không chỉ state management mà còn routing, dependency injection, và nhiều tính năng khác. Trong bài viết này, chúng ta sẽ khám phá toàn bộ sức mạnh của GetX.

Tổng quan về GetX

GetX bao gồm 3 phần chính:

  1. State Management: Quản lý state reactive
  2. Route Management: Navigation và routing
  3. Dependency Management: Dependency injection

Ưu điểm

  • All-in-one: Một package cho nhiều tính năng
  • Hiệu suất cao: Tối ưu rebuild
  • Syntax đơn giản: Code ngắn gọn
  • Không cần BuildContext: Truy cập từ bất kỳ đâu
  • Tích hợp nhiều tính năng: Dialogs, snackbars, bottom sheets

Nhược điểm

  • ⚠️ Không được Flutter team chính thức khuyến nghị
  • ⚠️ Tight coupling giữa các tính năng
  • ⚠️ Có thể khó maintain với ứng dụng lớn

Phần 1: State Management với GetX

Setup

Thêm vào pubspec.yaml:

dependencies:
get: ^4.6.6

Reactive State Management

1. Sử dụng .obs (Observable)

import 'package:get/get.dart';

class CounterController extends GetxController {
var count = 0.obs; // Observable variable

void increment() => count++;
void decrement() => count--;
}

2. Sử dụng trong Widget

class CounterScreen extends StatelessWidget {
final CounterController controller = Get.put(CounterController());


Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Obx(() => Text('Count: ${controller.count}')),
),
floatingActionButton: FloatingActionButton(
onPressed: () => controller.increment(),
child: Icon(Icons.add),
),
);
}
}

State Management Patterns

Pattern 1: Simple State với .obs

class UserController extends GetxController {
var name = ''.obs;
var age = 0.obs;
var isLoggedIn = false.obs;

void login(String userName, int userAge) {
name.value = userName;
age.value = userAge;
isLoggedIn.value = true;
}

void logout() {
name.value = '';
age.value = 0;
isLoggedIn.value = false;
}
}

Pattern 2: Custom Class với Rx

class User {
final String name;
final int age;

User({required this.name, required this.age});
}

class UserController extends GetxController {
var user = User(name: '', age: 0).obs;

void updateUser(String name, int age) {
user.value = User(name: name, age: age);
}
}

Pattern 3: GetxController với Lifecycle

class ProductController extends GetxController {
var products = <Product>[].obs;
var isLoading = false.obs;


void onInit() {
super.onInit();
fetchProducts();
}


void onReady() {
super.onReady();
// Called after widget is rendered
}


void onClose() {
super.onClose();
// Clean up resources
}

Future<void> fetchProducts() async {
isLoading.value = true;
try {
// API call
await Future.delayed(Duration(seconds: 2));
products.value = [/* products */];
} finally {
isLoading.value = false;
}
}
}

Workers: Lắng nghe thay đổi

class CounterController extends GetxController {
var count = 0.obs;


void onInit() {
super.onInit();

// Lắng nghe mọi thay đổi
ever(count, (value) {
print('Count changed to: $value');
});

// Lắng nghe lần đầu tiên
once(count, (value) {
print('Count changed for the first time: $value');
});

// Debounce: Chờ 1 giây sau khi thay đổi cuối cùng
debounce(count, (value) {
print('Count debounced: $value');
}, time: Duration(seconds: 1));

// Interval: Chạy mỗi 1 giây nếu có thay đổi
interval(count, (value) {
print('Count interval: $value');
}, time: Duration(seconds: 1));
}

void increment() => count++;
}

Phần 2: Route Management với GetX

Setup

Thay vì MaterialApp, sử dụng GetMaterialApp:

void main() {
runApp(MyApp());
}

class MyApp extends StatelessWidget {

Widget build(BuildContext context) {
return GetMaterialApp(
title: 'GetX App',
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => HomeScreen()),
GetPage(name: '/details', page: () => DetailsScreen()),
GetPage(
name: '/profile/:userId',
page: () => ProfileScreen(),
),
],
);
}
}

1. Navigate to Screen

// Navigate to named route
Get.toNamed('/details');

// Navigate with arguments
Get.toNamed('/details', arguments: {'id': 123});

// Navigate and remove current screen
Get.offNamed('/details');

// Navigate and remove all previous screens
Get.offAllNamed('/details');

// Navigate and get result back
final result = await Get.toNamed('/details');

2. Navigate with Parameters

// Navigate with path parameters
Get.toNamed('/profile/123'); // userId = 123

// In ProfileScreen
class ProfileScreen extends StatelessWidget {

Widget build(BuildContext context) {
final userId = Get.parameters['userId'];
return Scaffold(
body: Text('User ID: $userId'),
);
}
}

3. Navigate with Arguments

// Pass arguments
Get.toNamed('/details', arguments: {
'title': 'Product Details',
'price': 99.99,
});

// Receive arguments
class DetailsScreen extends StatelessWidget {

Widget build(BuildContext context) {
final args = Get.arguments;
final title = args['title'];
final price = args['price'];

return Scaffold(
appBar: AppBar(title: Text(title)),
body: Text('Price: \$$price'),
);
}
}

4. Go Back

// Go back
Get.back();

// Go back with result
Get.back(result: 'some result');

// Go back to specific route
Get.until((route) => route.settings.name == '/home');

Advanced Routing

Named Routes với Bindings

class DetailsBinding extends Bindings {

void dependencies() {
Get.lazyPut(() => DetailsController());
}
}

GetPage(
name: '/details',
page: () => DetailsScreen(),
binding: DetailsBinding(),
),

Middleware (Route Guards)

class AuthMiddleware extends GetMiddleware {

RouteSettings? redirect(String? route) {
// Check if user is authenticated
if (!AuthService.isAuthenticated) {
return RouteSettings(name: '/login');
}
return null;
}
}

GetPage(
name: '/profile',
page: () => ProfileScreen(),
middlewares: [AuthMiddleware()],
),

Transition Animations

Get.to(
DetailsScreen(),
transition: Transition.fade,
duration: Duration(milliseconds: 300),
);

// Available transitions:
// - Transition.fade
// - Transition.rightToLeft
// - Transition.leftToRight
// - Transition.upToDown
// - Transition.downToUp
// - Transition.zoom
// - Transition.cupertino

Phần 3: Dependency Injection với GetX

Các phương thức Dependency Injection

1. Get.put() - Immediate initialization

// Tạo instance ngay lập tức
final controller = Get.put(CounterController());

// Với tag để phân biệt
final controller1 = Get.put(CounterController(), tag: 'counter1');
final controller2 = Get.put(CounterController(), tag: 'counter2');

2. Get.lazyPut() - Lazy initialization

// Tạo instance khi cần
Get.lazyPut(() => CounterController());

// Sử dụng
final controller = Get.find<CounterController>();

3. Get.putAsync() - Async initialization

// Tạo instance async
Get.putAsync(() async {
final prefs = await SharedPreferences.getInstance();
return SettingsController(prefs);
});

4. Get.create() - Create new instance mỗi lần

// Tạo instance mới mỗi lần gọi
Get.create(() => CounterController());

Dependency Management Patterns

Pattern 1: Sử dụng trong Controller

class ProductController extends GetxController {
final ProductService productService;

ProductController({required this.productService});

var products = <Product>[].obs;

Future<void> fetchProducts() async {
products.value = await productService.getProducts();
}
}

// Setup
Get.put(ProductService());
Get.put(ProductController(productService: Get.find()));

Pattern 2: Sử dụng Bindings

class ProductBinding extends Bindings {

void dependencies() {
Get.lazyPut(() => ProductService());
Get.lazyPut(() => ProductController(productService: Get.find()));
}
}

// Sử dụng
GetPage(
name: '/products',
page: () => ProductsScreen(),
binding: ProductBinding(),
),

Pattern 3: Global Bindings

class AppBindings extends Bindings {

void dependencies() {
// Global dependencies
Get.put(AuthService(), permanent: true);
Get.put(UserService(), permanent: true);
}
}

// Trong main.dart
GetMaterialApp(
initialBinding: AppBindings(),
// ...
)

Dependency Lifecycle

// Permanent: Không bị xóa khi không dùng
Get.put(Service(), permanent: true);

// Remove dependency
Get.delete<CounterController>();

// Remove all dependencies
Get.reset();

// Check if exists
if (Get.isRegistered<CounterController>()) {
final controller = Get.find<CounterController>();
}

Ví dụ hoàn chỉnh: Todo App với GetX

1. Model

class Todo {
final String id;
final String title;
final bool isCompleted;

Todo({
required this.id,
required this.title,
this.isCompleted = false,
});
}

2. Controller

class TodoController extends GetxController {
var todos = <Todo>[].obs;
var filter = 'all'.obs; // all, active, completed

List<Todo> get filteredTodos {
switch (filter.value) {
case 'active':
return todos.where((todo) => !todo.isCompleted).toList();
case 'completed':
return todos.where((todo) => todo.isCompleted).toList();
default:
return todos;
}
}

void addTodo(String title) {
todos.add(Todo(
id: DateTime.now().millisecondsSinceEpoch.toString(),
title: title,
));
}

void toggleTodo(String id) {
final index = todos.indexWhere((todo) => todo.id == id);
if (index != -1) {
todos[index] = Todo(
id: todos[index].id,
title: todos[index].title,
isCompleted: !todos[index].isCompleted,
);
}
}

void deleteTodo(String id) {
todos.removeWhere((todo) => todo.id == id);
}

void setFilter(String newFilter) {
filter.value = newFilter;
}
}

3. Screen

class TodoScreen extends StatelessWidget {
final TodoController controller = Get.put(TodoController());
final textController = TextEditingController();


Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Todo App')),
body: Column(
children: [
// Filter buttons
Obx(() => Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildFilterButton('all', controller.filter.value == 'all'),
_buildFilterButton('active', controller.filter.value == 'active'),
_buildFilterButton('completed', controller.filter.value == 'completed'),
],
)),

// Todo list
Expanded(
child: Obx(() => ListView.builder(
itemCount: controller.filteredTodos.length,
itemBuilder: (context, index) {
final todo = controller.filteredTodos[index];
return ListTile(
leading: Checkbox(
value: todo.isCompleted,
onChanged: (_) => controller.toggleTodo(todo.id),
),
title: Text(todo.title),
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () => controller.deleteTodo(todo.id),
),
);
},
)),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: () => _showAddDialog(),
child: Icon(Icons.add),
),
);
}

void _showAddDialog() {
Get.dialog(
AlertDialog(
title: Text('Thêm Todo'),
content: TextField(
controller: textController,
autofocus: true,
),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('Hủy'),
),
TextButton(
onPressed: () {
if (textController.text.isNotEmpty) {
controller.addTodo(textController.text);
textController.clear();
Get.back();
}
},
child: Text('Thêm'),
),
],
),
);
}

Widget _buildFilterButton(String label, bool isSelected) {
return ElevatedButton(
onPressed: () => controller.setFilter(label),
style: ElevatedButton.styleFrom(
backgroundColor: isSelected ? Colors.blue : Colors.grey,
),
child: Text(label),
);
}
}

GetX Utilities

Snackbars

Get.snackbar(
'Title',
'Message',
snackPosition: SnackPosition.BOTTOM,
duration: Duration(seconds: 3),
backgroundColor: Colors.blue,
colorText: Colors.white,
);

Dialogs

Get.dialog(
AlertDialog(
title: Text('Title'),
content: Text('Content'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('OK'),
),
],
),
);

Bottom Sheets

Get.bottomSheet(
Container(
height: 200,
child: Column(
children: [
ListTile(
leading: Icon(Icons.share),
title: Text('Share'),
onTap: () => Get.back(),
),
],
),
),
);

Internationalization

// translations.dart
class Messages extends Translations {

Map<String, Map<String, String>> get keys => {
'en_US': {
'hello': 'Hello',
},
'vi_VN': {
'hello': 'Xin chào',
},
};
}

// Usage
GetMaterialApp(
translations: Messages(),
locale: Locale('vi', 'VN'),
// ...
)

Text('hello'.tr) // 'Xin chào'

Best Practices

1. Tổ chức code

lib/
controllers/
todo_controller.dart
user_controller.dart
models/
todo.dart
user.dart
views/
todo_screen.dart
user_screen.dart
bindings/
todo_binding.dart
routes/
app_routes.dart

2. Sử dụng Bindings

Luôn sử dụng Bindings để quản lý dependencies:

class TodoBinding extends Bindings {

void dependencies() {
Get.lazyPut(() => TodoController());
}
}

3. Tách biệt logic và UI

  • Controllers: Business logic
  • Views: UI only
  • Models: Data structures

4. Sử dụng Workers hợp lý


void onInit() {
super.onInit();
// Chỉ lắng nghe những gì cần thiết
ever(count, (value) {
// Important logic
});
}

5. Clean up resources


void onClose() {
// Dispose controllers, streams, etc.
super.onClose();
}

Kết luận

GetX là một giải pháp mạnh mẽ và toàn diện cho Flutter development. Các điểm chính:

  1. State Management: Reactive và hiệu quả
  2. Routing: Đơn giản và mạnh mẽ
  3. Dependency Injection: Dễ sử dụng
  4. Utilities: Snackbars, dialogs, bottom sheets
  5. Internationalization: Hỗ trợ đa ngôn ngữ

GetX phù hợp cho:

  • Ứng dụng nhỏ đến trung bình
  • Muốn giải pháp all-in-one
  • Ưu tiên code ngắn gọn
  • Cần routing đơn giản

GetX cung cấp một cách tiếp cận đơn giản và mạnh mẽ để xây dựng ứng dụng Flutter. Hãy thử nghiệm và khám phá sức mạnh của nó!