openplanning

Hướng dẫn và ví dụ Flutter GetX obs Obx

  1. Cài đặt thư viện
  2. Ví dụ với kiểu cơ bản
  3. Ví dụ với kiểu Object
  4. Ví dụ với kiểu List
Trong Flutter GetX, Obx là một widget quan trọng được sử dụng để vẽ ra một vùng giao diện có khả năng tự động vẽ lại nếu một biến đơn của GetxController mà nó đang quan sát thay đổi.
Lưu ý : Thư viện Rx là trung tâm của khả năng phản ứng của GetX. Thư viện này cung cấp các đối tượng quan sát được, luồng phản ứng (reactive streams) và các phương thức tiện ích để xử lý dữ liệu phản ứng. Obx là một widget nằm trong thư viện này mà bài viết này sẽ đề cập tới.
Khác với Obx, GetBuilder được sử dụng để vẽ ra một vùng giao diện và chỉ tự động vẽ lại khi GetxController thay đổi và bạn chủ động gọi phương thức update() của GetxController.

1. Cài đặt thư viện

pubspec.yaml
dependencies:  
  get: ^5.0.0-release-candidate-9

2. Ví dụ với kiểu cơ bản

Khi bắt đầu học Flutter bạn đã làm quen với ví dụ "Counter" nổi tiếng. Bây giờ chúng ta sẽ trở lại với ví dụ này theo cách của Flutter GetX.
Trong Flutter GetX, GetMaterialApp được sử dụng để thay cho MaterialApp. Về cơ bản chúng có vai trò giống nhau và có một vài khác biệt nhỏ.
main_ex1.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'page_main_ex1.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const GetMaterialApp( // <---------------------
      title: 'Flutter GetX Demo',
      debugShowCheckedModeBanner: false,
      home: MainPageEx1(),
    );
  }
}
Flutter GetX sử dụng Controller để quản lý trạng thái thay cho StatefulWidget. Hãy nhìn vào lớp CounterController dưới đây:
counter_controller.dart
import 'package:get/get.dart';

class CounterController extends GetxController {
  var count = 0.obs; // <--- RxInt (extends Rx<int>)
  increment() => count++;
}
// Rx<int> 
var count = 0.obs;
Trong ví dụ này thuộc tính "count" của CounterController là "thứ có thể quan sát được bởi Obx".
extension IntExtension on int { 
  RxInt get obs => RxInt(this);
}
class RxInt extends Rx<int> { 
   ...
}
// Rx<MyObj>
var myObjRx = myObj.obs;
Về cơ bản, bạn có thể biến bất kỳ đối tượng nào thành "thứ có thể quan sát được" bằng cách gọi thuộc tính mở rộng "obs" của nó.
extension RxT<T extends Object> on T { 
  Rx<T> get obs => Rx<T>(this);
}
GetX sử dụng StatelessWidget kết hợp với một GetxController để thay thế cho StatefulWidget.
main_page_ex1.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'counter_controller.dart';
import 'page2_ex1.dart';

class MainPageEx1 extends StatelessWidget {
  const MainPageEx1({super.key});

  @override
  Widget build(BuildContext context) {
    // Instantiate your class using Get.put()
    // to make it available for all "child" routes there.
    final CounterController controller = Get.put(CounterController());

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Flutter GetX Demo 1"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Obx(() => Text("Clicked: ${controller.count}")), // <---------
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Get.to(() => Page2Ex1());
              },
              child: const Text("Go to Page2"),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.increment(); // <-----------
        },
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}
Đoạn mã Get.put(CounterController()) được sử dụng để tạo ra một đối tượng CounterController và đặt nó vào "Objects Container" của trang. Nếu đã sẵn có đối tượng CounterController trong "Objects Container" thì đối tượng cũ sẽ bị thay thế. Các đối tượng này sẽ sẵn có để sử dụng cho Route hiện tại và tất cả các Route(s) con. Tại Route hiện tại và các Route(s) con, bạn có thể tìm thấy đối tượng này thông qua phương thức Get.find().
// Rx<int>
var count = 0.obs;
Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Obx(() => Text("Clicked: ${controller.count}")), // <-- 
  ],
)
Các thay đổi của ".obs" sẽ được lắng nghe bởi "Obx".
"Obx" sẽ được vẽ lại một cách tự động khi giá trị thuộc tính ".obs" thay đổi.
Các đối tượng được đặt trong "Objects Container" của trang sẽ sẵn có để sử dụng cho Route hiện tại và các Routes con.
page2_ex1.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import 'counter_controller.dart';

class Page2Ex1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    CounterController controller = Get.find();

    return Scaffold(
      body: Center(
        child: Column(
          children: [
            Text("Clicked: ${controller.count}"),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Get.back();
              },
              child: const Text("Back"),
            ),
          ],
        ),
      ),
    );
  }
}

3. Ví dụ với kiểu Object

Trong ví dụ trên chúng ta đã biến một giá trị nguyên thuỷ (int, double, bool, num, String) thành thứ có thể quan sát được, sự thay đổi giá trị của nó được nhận biết bởi Obx. Mọi thứ có vẻ rất dễ dàng. Tuy nhiên, với một Object sẽ có một chút khác biệt và một vài điều cần lưu ý.
late Rx<Product> productRx;

@override
void onInit() {
  super.onInit();
  Product product = provider.findExampleProduct();
  productRx = product.obs;
}
Trong ví dụ này chúng ta sẽ biến một đối tượng Product thành thứ có thể quan sát được bởi Obx.
_product_model.dart
class Product {
  int id;
  String name;
  double price;

  Product({
    required this.id,
    required this.name,
    required this.price,
  });

  Product copy() {
    return Product(id: id, name: name, price: price);
  }
}
_product_provider.dart
import '_product_model.dart';

class ProductProvider {
  // Query from Database and return a Product object.
  Product findExampleProduct() {
    return Product(
      id: 1,
      name: "Samsung Galaxy S24 Ultra",
      price: 1799.99,
    );
  }

  // Save to Database ...
  // Query product and return new instance of Product...
  Product updateProduct(Product product) {
    Product copied = product.copy();
    copied.price = copied.price + 1;
    return copied;
  }
}
ProductController là một lớp mở rộng từ GetxController. Thuộc tính productRx của nó là thứ có thể quan sát được bởi Obx.
_product_controller.dart
import 'package:get/get.dart';

import '_product_model.dart';
import '_product_provider.dart';

class ProductController extends GetxController {
  ProductProvider provider = Get.put(ProductProvider());

  late Rx<Product> productRx;

  @override
  void onInit() {
    super.onInit();
    Product product = provider.findExampleProduct();
    productRx = product.obs;
  }

  void updateProduct() {
    // Use way 1 or way 2 or way 3:
    updateProduct_way1();
    // updateProduct_way2();
    // updateProduct_way3();
  }

  // Way 1
  void updateProduct_way1() {
    Product current = productRx.value;
    current.price = current.price + 1;

    // Call Rx<>.refresh()
    productRx.refresh(); // IMPORTANT!!!
  }

  // Way 2
  void updateProduct_way2() {
    Product current = productRx.value;
    // Update current Product and return new instance.
    Product newInstance = provider.updateProduct(current);
    // Replace value of "Rx" with the new instance.
    productRx.value = newInstance; // IMPORTANT!!!
  }

  // Way 3
  void updateProduct_way3() {
    productRx.update(
      (Product? currentProduct) {
        // IMPORTANT!!!
        // Update current product and return new instance.
        Product newInstance = provider.updateProduct(currentProduct!);
        return newInstance;
      },
    );
  }
}
main_ex2.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '_product_controller.dart';

class MainPageEx2 extends StatelessWidget {
  const MainPageEx2({super.key});

  @override
  Widget build(BuildContext context) {
    final ProductController controller = Get.put(ProductController());

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Flutter GetX Obx Demo 2"),
      ),
      body: Center(
        child: Obx(
          () => Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text("Product Name: ${controller.productRx.value.name}"),
              const SizedBox(height: 10),
              Text("Product Price: ${controller.productRx.value.price}"),
              const SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  controller.updateProduct();
                },
                child: const Text("Update Product"),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const GetMaterialApp(
      title: 'Flutter GetX Demo 2',
      debugShowCheckedModeBanner: false,
      home: MainPageEx2(),
    );
  }
}

4. Ví dụ với kiểu List

Một trường hợp phổ biến trong Flutter GetX đó là biến một đối tượng List thành thứ có thể quan sát được bởi Obx:
late RxList<Employee> employeeRxList;

@override
void onInit() {
  super.onInit();
  List<Employee> employeeList = provider.queryEmployee();
  employeeRxList = employeeList.obs;
}
Khi bạn cập nhập danh sách bạn cần gọi phương thức rxList.refresh() để chắc chắn rằng các thay đổi này được thông báo tới Obx.
void updateFirstEmployee() {
  Employee first = employeeRxList[0];
  first.salary = first.salary + 1;

  employeeRxList.refresh(); // <--------
}
Chú ý: Trong một vài tình huống được liệt kê dưới đây sự thay đổi của List sẽ được thông báo tới Obx mà không cần gọi phương thức rxList.update().
Thay thế rxList.value bởi một đối tượng List mới.
List<Employee> newList = [
  Employee(id: 4, name: "Mickey", salary: 1200),
  Employee(id: 5, name: "Minnie", salary: 1300),
  Employee(id: 6, name: "Goofy", salary: 1300),
];
employeeRxList.value = newList;
Thay thế một phần tử nào đó trong RxList bởi một phần tử khác.
Random r = Random();
Employee e0 = Employee(
  id: 4,
  name: "Mickey",
  salary: r.nextInt(1000),
);
employeeRxList[0] = e0;
Thay đổi vị trí một phần tử nào đó của RxList.
employeeRxList.shuffle();
Số lượng các phần tử trong RxList thay đổi do gọi một trong các phương thức của RxList như:
  • rxList.removeAt(index)
  • rxList.employeeRxList.addAll(..);
  • rxList.employeeRxList.addIf(..)
  • ...
employeeRxList.removeAt(0);
Xem một ví dụ đầy đủ:
_employee_model.dart
class Employee {
  int id;
  String name;
  int salary;

  Employee({
    required this.id,
    required this.name,
    required this.salary,
  });

  Employee copy() {
    return Employee(id: id, name: name, salary: salary);
  }
}
_employee_provider.dart
import '_employee_model.dart';

class EmployeeProvider {
  // Query from Database and return a Product object.
  List<Employee> queryEmployee() {
    return [
      Employee(id: 1, name: "Tom", salary: 1100),
      Employee(id: 2, name: "Jerry", salary: 1000),
      Employee(id: 3, name: "Donald", salary: 1200),
    ];
  }
}
_employee_controller.dart
import 'package:get/get.dart';

import '_employee_model.dart';
import '_employee_provider.dart';

class EmployeeController extends GetxController {
  final EmployeeProvider provider = Get.put(EmployeeProvider());

  late RxList<Employee> employeeRxList;

  @override
  void onInit() {
    super.onInit();
    List<Employee> employeeList = provider.queryEmployee();
    employeeRxList = employeeList.obs;
  }

  void updateFirstEmployee() {
    Employee first = employeeRxList[0];
    first.salary = first.salary + 1;

    employeeRxList.refresh(); // <--------
  }
}
main_ex3.dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

import '_employee_controller.dart';

class MainPageEx3 extends StatelessWidget {
  const MainPageEx3({super.key});

  @override
  Widget build(BuildContext context) {
    final EmployeeController controller = Get.put(EmployeeController());

    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text("Flutter GetX Obx Demo 3"),
      ),
      body: Column(
        children: [
          SizedBox(
            height: 200,
            child: Obx(
              () => ListView(
                children: controller.employeeRxList
                    .map(
                      (e) => Card(
                        child: ListTile(
                          title: Text(e.name),
                          subtitle: Text("Salary: ${e.salary}"),
                        ),
                      ),
                    )
                    .toList(),
              ),
            ),
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: () {
              controller.updateFirstEmployee();
            },
            child: const Text("Update Salary for first Employee"),
          ),
        ],
      ),
    );
  }
}

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const GetMaterialApp(
      title: 'Flutter GetX Demo 2',
      debugShowCheckedModeBanner: false,
      home: MainPageEx3(),
    );
  }
}

Các hướng dẫn lập trình Flutter

Show More