+4

[Flutter] Hiểu về setState trong lập trình giao diện của Flutter

I. Giới thiệu

Xin chào các bạn, hôm nay chúng ta cùng tìm hiểu và phân tích cách mà giao diện được thay đổi như thế nào nhé. Let’s go. 👉🏻

II. Công thức của giao diện

1. Giao diện và dữ liệu

  • Giao diện là thành phần hiển thị mà người dùng có thể xem, có thể tương tác để nhận biết thông tin và trao đổi thông tin với ứng dụng/hệ thống.
  • Các thành phần cơ bản của giao diện như chữ (Text), hình ảnh (Image), màu sắc (Color), bảng (Table)… rất là nhiều luôn.
  • Các giao diện trên muốn hiển thị cho có ý nghĩa thì nó cần nội dung gọi là dữ liệu hay nói cách khác giao diện phụ thuộc vào giữ liệu.
    • Ví dụ:
      • Text thì cần nội dung, style
      • Image cần đường dẫn
      • CheckBox cần trạng thái đã chọn hoặc bỏ chọn

Vậy chúng ta có thể rút ra được công thức dự trên công thức toán học:

y=f(x) y = f(x)

  • y : Giao diện
  • x: Dữ liệu

**⇒ Giao diện = f(Dữ liệu) **

Dữ liệu biến thiên làm cho Giao diện cũng thay đổi =))

Vậy hàm f ở đây là gì?

Việc sắp xếp các Widget trong Flutter lại với nhau để chúng hiển thị lên màn hình được coi như việc tạo nên các bảng thiết kế, nó đóng vai trò như là hàm f.

2. Cập nhật lại giao diện

  • Như đã biết tại một thời điểm nhất định thì chúng ta có dữ liệu nhất định và có giao diện nhất định.
  • Muốn giao diện thay đổi thì cần phải biết dữ liệu thay đổi.

Câu hỏi đặt ra: Làm sao để biết dữ liệu thay đổi?

Trong lập trình cũng như trong cuộc sống, cách mà để chúng ta nhận biết được có sự vật, sự việc thay đổi thường có 2 cách.

  1. Một là chúng ta tự đi xem tình trạng của việc/vật đó như thế nào.
  2. Hai là có *cái gì đó tới báo động cho chúng ta biết trạng thái của việc/vật đó như thế nào.

Yah đúng vậy, trong các framework sẽ được cung cấp cách để làm mới lại giao diện khi có sự thay đổi. (Mặc dù ko có thay đổi làm mới cũng được, chỉ có điều hơi bị lãng phí tài nguyên 1 chút hehe)

3. Declarative và Imperative programming

📌 Phần này mình xin phép giải thích dựa trên hiểu biết của mình và khía cạnh về lập trình giao diện nhé.

💬 Trong lập trình Flutter thì cách xây dựng UI phụ thuộc vào State được gọi là declarative. Ở một số framework (như Android SDK hoặc iOS UIKit …) được gọi là imperative. (Cá nhân mình gọi là cách lập trình giao diện hồi xưa, để mình giải thích 1 xíu nhé.)

Về imperative:

Hiểu như sau:

Khi bạn lập trình giao diện chẵn hạn như là hiển thị 1 đoạn văn bản Text thì để gán dữ liệu cho Text này chúng ta cần cung cấp cho Text này một id hoặc 1 cách nào đó để định danh được Text này.

Từ id chúng ta sẽ lấy được đối tượng Text này và từ đó dùng hàm ví dụ hàm setText để gán giá trị thì UI sẽ được cập nhật.

/*Đoạn mã này không nằm trong ngôn ngữ, framework nào cả, 
mình chỉ làm mã giả để minh họa cho mọi người dễ hiểu thôi ạ*/

//Ví dụ bằng mã nguồn
var Text text = Text(id: 'my_text', text: 'Hello World'); 
//xài ông này gắn lên giao diện ở đâu đó 
//và nó hiển thì là 'Hello World'

//để lấy được text theo id
var myText= Text.findById('my_text');
myText.setText('Happy New Year');
//bây giờ được cập nhật thành 'Happy New Year'

Về declarative:

image.png

Trong Flutter dữ liệu mà để hiển thị lên giao diện được gọi là state. Khi muốn làm mới lại giao diện thì chúng ta gọi hàm setState().

*note: nếu bạn chưa biết setState() là gì thì có thể vào tài liệu của flutter để đọc nhé: Link

class SampleWidget extends StatefulWidget {
  const SampleWidget({Key? key}) : super(key: key);

  
  _SampleWidgetState createState() => _SampleWidgetState();
}

class _SampleWidgetState extends State<SampleWidget> {
  String text = 'Hello World';

  
  Widget build(BuildContext context) {
    return Column(
      children: [
        //InkWell là một widget có thể tương tác chạm
        //Mình gọi là một cái nút
        InkWell(
          onTap: () {
            text = 'Happy New Year';
            setState(() {});
          },
          child: Text('Nhấn nút'),
        ),
        //Text hiển thị văn bản
        //Lần đầu Text này sẽ hiển thị là Hello World
        //Khi nút InkWell bên trên thì
        //nội dung text bên dưới này đổi lại thành Happy New Year
        Text(text),
      ],
    );
  }
}

Wow nhìn có ví dụ thì chúng ta có thể thấy declarative không quan tâm tới vị trí hay định danh của giao diện, nó chỉ cần biết gọi setState thì giao diện được cập nhật.

Một số nhận xét:

  • 2 cách thiết kế giao diện thuộc 2 trường phái khác nhau.
  • Về việc code thì declarative có vẻ nhàn hơn.
  • Nhưng so về hiệu năng thì imperative tốt hơn vì nó chỉ thay đổi nơi cần thay đổi.

🚀 Yah nhưng các bạn đừng quá lo lắng về phần hiệu năng vì các nhà phát triển của Google đã làm rất tốt trong việc tối ưu hiệu năng rồi. Mặc dù như thế việc setState bừa bãi sẽ làm ảnh hưởng tới hiệu năng của ứng dụng nên mới sinh ra nhiều state managment như hiện nay nào là Provider, Bloc, GetX, RiverPod…

III. State Management

  • Các thư viện state management đều đưa ra các phương pháp/cơ chế lắng nghe dữ liệu thay đổi và cung cấp các hàm Builder để rebuild giao diện. Việc sử dụng các thư viện này giúp lập trình viên Flutter kiểm soát và thu hẹp phạm vi ảnh hưởng của việc rebuild giao diện làm cho hiệu năng của ứng dụng tốt hơn.
  • Đối với người mới bắt đầu hãy tìm hiểu về Provider và Flutter Bloc. Vì tư tưởng lập trình của nó khá đơn giản, dễ tiếp cận đặc biệt rất phổ biến trong cộng đồng.
  • Ngoài ra hiện tại mình thấy khá hứng thú với Riverpod nó là một phiên bản cải tiến của Provider nhưng nó khá phức tạp để người mới tiếp cận. Lib này nó đáp ứng mọi tính năng của Flutter Bloc và còn nhiều điểm nổi bật hơn nữa, các bạn có thể tự tìm hiểu nhé.

📌 Các bạn có thể vào trang https://docs.flutter.dev/development/data-and-backend/state-mgmt/options để xem các thư viện được recommend bởi Google nhé.

Cá nhân mình thấy mỗi thư viện đều có cái hay riêng. Nếu không làm theo công ty/doanh nghiệp/nhóm yêu cầu 1 cái cụ thể thì bạn hãy trãi nghiệm càng nhiều thư viện để tự đưa ra đánh giá nhé.

Cá nhân mình đưa ra một số điểm như sau để đánh giá:

  1. Viết code nhiều hay ít/nhanh hay chậm
  2. Có dễ viết theo tư tưởng/tư duy lập trình của bản thân mình hay không
  3. Code có dễ tách rời và mở rộng hay không
  4. Có dễ bảo trì và sữa lỗi hay không
  5. Thư viện có cộng đồng support nhiều/nhanh chóng hay không

💯 Thực ra các state management nó không chỉ đơn giản là quản lý state để build lại giao diện mà các bạn hãy đọc hiểu cơ chế, mô hình mà thư viện đó xây dựng để có cái nhìn sâu sắc hơn về lập trình nhé.

IV. setState king of state management

The low-level approach to use for widget-specific, ephemeral state.

setState là cách đơn giản nhất để làm mới lại toàn bộ giao diện trong 1 State của StatefullWidget.

Như cơ chế ở phần II. 2 mình đã đề cập, ở các thư viện thì sâu bên trong nó có một nơi để lắng nghe sự thay đổi của dữ liệu, và khi dữ liệu thay đổi nó sẽ gọi hàm setState để cập nhật lại giao diện.

Ứng dụng cơ chế này vào các hàm builder như BlocBuilder (Flutter_Bloc), GetBuilder, Obx (GetX), StreamBuilder, FutureBuilder, ValueListenableBuilder... các class Widget này thường là StatefulWidget. Tại hàm initState (Thường là ở initState) sẽ tạo một sự kiện lắng nghe dữ liệu được truyền vào hoặc *tìm kiếm từ (context, instance được lưu ở đâu đó). Tại đây nếu có dữ liệu mới đến thì hàm setState sẽ được gọi để làm mới lại giao diện của builder này.

🎯 Các bạn hãy đi sâu vào code của các thư viện để kiểm nghiệm điều mình đề cập nhá.

Các class có cơ chế lắng nghe như Stream, ChangeNotifier,... hoặc các bạn hãy tìm hiểu về observer design pattern để hiểu sâu hơn nhé.

V. Tổng kết

Qua bài viết này mình mong muốn giới thiệu đến các bạn mới cái nhìn cơ bản về lập trình giao diện nói chung và Flutter nói riêng. Chúc các bạn lập trình hiệu quả nhé.

Nguồn tham khảo:

https://ui.dev/imperative-vs-declarative-programming

https://docs.flutter.dev/development/data-and-backend/state-mgmt/intro


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí