Tự học Flutter 2023

New Flutter cơ bản

SetState

Ở bài viết trước, chúng ta đã tìm hiểu qua về Stateless và Stateful widget. Trong bài viết này mình sẽ đi tiếp một chủ đề quan trọng liên quan đến Stateful widget, đó là setState().
  • SetState là gì?
  • Khi nào thì gọi SetState?
  • SetState là gì

    Trong bài viết trước, bạn đã biết stateless widget là gì, stateful widget là gì, và quan trọng hơn đó là state là gì. Mình nhắc lại một chút rằng Stateful widget có nghĩa là widget có state, và state đơn giản đó là thông tin / dữ liệu. Và dữ liệu này sẽ ảnh hưởng đến cách hiển thị của widget (diễn đạt theo một cách khác thì dữ liệu này sẽ được sử dụng trong widget để hiển thị theo cách bạn muốn).

    Vậy thì nếu chúng ta muốn widget của mình thay đổi thì chúng ta phải thay đổi dữ liệu đó (state) đúng không?

    Lấy ví dụ: ứng dụng của bạn có một trang để hiển thị bài viết, chúng ta sẽ tạo ra trang này bằng cách tạo ra một stateful widget và đặt tên cho nó là ArticleScreen. Trên trang này có một widget hiển thị số lần được like của bài viết. State của bạn sẽ có một vài biến khác nhau, có biến thì dùng để chứa nội dung bài biết, biến chứa thông tin tác giả,.. quan trọng là sẽ có một biến tên là likeCounter để lưu số lượng like. Và bạn dùng một Text widget để hiển thị số lượt like như thế này:

    
    Text("Lượt like: $likeCounter")
                          

    Bạn có thể xem full code ở phía cuối bài cho dễ hình dung nhé.

    Bây giờ người dùng bấm nút like bài viết thì bạn sẽ muốn widget kia hiển thị số like mới (tăng lên). Tự suy nghĩ logic một chút chúng ta sẽ thấy rằng, Text widget hiển thị lượt like ở trên hiển thị giá trị của biến likeCounter. Vậy chúng ta chỉ cần thay đổi giá trị của biến likeCounter là được.

    Vậy thì khi người dùng like bài viết (giả sử bằng cách bấm vào nút like), chúng ta sẽ cập nhật lại giá trị của likeCounter để tăng giá trị lên 1. Nói là làm, chúng ta sẽ làm như sau:

    
    ElevatedButton( // đây là nút like để người dùng bấm
      onPressed: () { // hàm được gọi thì người dùng bấm like
          ++likeCounter // tăng giá trị của likeCounter lên 1
      },
      child: const Text("Like"),
    ),
                            

    Pefect! vậy là xong, giờ chạy chương trình lên, bấm nút like, kiểu gì số lượng like cũng sẽ tăng lên mỗi lần bấm.

    Đó là bạn nghĩ vậy thôi, chứ bạn bấm gãy tay, mòn màn hình, bại con mouse thì trên màn hình số lượng like vẫn như cũ. Cái quái gì vậy nhỉ, suy nghĩ đúng lý thuyết mà ta, dữ liệu state thay đổi thì widget phải thay đổi theo chứ. Sai chỗ nào??

    Có thể bạn sẽ muốn kêu gào với Flutter rằng "tao đã thay đổi giá trị của state, sao widget của tao không cập nhật??" Và nếu mà Flutter biết nói nó sẽ hỏi lại "mày thay đổi cái gì thì có mày biết chứ sao tao biết?".

    Tới lúc này có lẽ bạn sẽ nhận ra, rằng sẽ có cách nào đó để báo cho framework biết rằng bạn đã thay đổi giá trị của state và nó cần phải build lại widget. Đây chính là lúc mà setState() đến với cuộc đời bạn.

    setState là cách mà chúng ta báo với framework rằng state của widget đã thay đổi, từ đó framework sẽ rebuild lại widget đó và hậu duệ của nó (widget con).

    Vậy với vấn đề vừa gặp ở trên, chúng ta sẽ phải sửa lại như thế này:

    
    ElevatedButton( // đây là nút like để người dùng bấm
      onPressed: () { // hàm được gọi thì người dùng bấm like
        setState(() {
          ++likeCounter // tăng giá trị của likeCounter lên 1
        });
      },
      child: const Text("Like"),
    ),
                            

    Bạn có thấy sự khác biệt? Hành động làm thay đổi giá trị của state sẽ được đặt bên trong setState (chính xác là đặt bên trong hàm được đưa vào cho hàm setState). Và bây giờ nếu chạy lại chương trình, bấm like bạn sẽ thấy số lượng like sẽ tăng lên theo mỗi lần bạn bấm.

    Trích từ document của Flutter: "Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.""

    Khi nào thì gọi setState

    Giờ thì bạn đã biết setState là cái gì, dùng để làm gì rồi. Nên là khi nào gọi setState mình nghĩ là bạn sẽ tự trả lời được.

    Tuy nhiên có một khía cạnh nho nhỏ ở đây mà mình thấy là đáng nhắc tới, đó là: bạn không cần phải luôn luôn sử dụng setState mỗi khi mà bạn thay đổi giá trị nào đó của state. Mà bạn chỉ nên sử dụng setState khi mà bạn muốn UI được cập nhật lại.

    Nói thì hơi khó hiểu vì state được dùng để build UI, nếu state thay đổi mà không dùng setState để cập nhật lại UI thì nghe có vẻ mâu thuẫn. Tuy nhiên là trên thực tế sẽ có những lúc như vậy, mình sẽ lấy ví dụ sau, giờ thì đi tắm đã, nóng quá nóng!

    Full ví dụ nãy giờ mình đang nói tới ở đây nhé.

                            
    import 'package:flutter/material.dart';
    void main() => runApp(const ArticleScreen());
    
    class ArticleScreen extends StatefulWidget {
      const ArticleScreen({super.key});
    
      @override
      State<ArticleScreen> createState() => _ArticleScreenState();
    }
    
    class _ArticleScreenState extends State<ArticleScreen> {
      int likeCounter = 0;
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text("Tiêu đề bài viết"),
            ),
            body: Column(
              children: [
                const Text("Nội dung bài viết"),
                const Divider(),
                Text("Lượt like: $likeCounter"),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      ++likeCounter;
                    });
                  },
                  child: const Text("Like"),
                ),
              ],
            ),
          ),
        );
      }
    }