, October 02, 2022

0 kết quả được tìm thấy

So sánh StatelessWidget và StatefulWidget

  • Đăng bởi  Vân Lê
  •  Jul 17, 2022

  •   12 min reads
So sánh StatelessWidget và StatefulWidget

1. Intro

Xin chào các bạn, đây là bài viết đầu tiên của mình trên blog của cộng đồng 200lab. Trước đây mình cũng đã có một số bài viết khác về Flutter và được đăng trên github cá nhân của mình. Mọi người có thể follow tại đây. Bây giờ thì bắt đầu thôi nào!

2. Mở đầu

Khi tiếp xúc với Flutter, có lẽ các bạn đều đã quen hoặc nếu không quen thì cũng đã từng nghe câu “Everything is a widget!” – có nghĩa là tất cả mọi thứ đều là widget. Text là một widget, Button là một Widget, các Animation cũng là Widget, thậm chí bản thân app cũng là một Widget. Nghe nhiều là vậy nhưng thực chất các widget này lại được chia thành hai nhóm chính là StalessWidget và StatefulWidget.

Mình cũng khá chắc là trong số những bạn đang đọc bài viết này cũng có một số bạn đã biết về hai loại này nhưng để hiểu về nó thì còn khá mơ hồ. Vậy nên bài viết này mình sẽ cố gắng trình bày về chúng một cách đơn giản nhất có thể để các bạn nắm bắt được.

3. Chuẩn bị

  • IDE:

    • Visual Studio Code version 1.67.0

    • Android Studio Chipmunk 2021.2.1

    • XCode version 13.3.1

  • Flutter SDK version 2.10.5

  • Kiến thức: ngôn ngữ lập trình Dart và Flutter cơ bản

4. Khái niệm State

State trong tiếng Anh có nghĩa là trạng thái. Trong thế giới của mobile dev, nó thể hiện các trạng thái của ứng dụng. Mình ví dụ đơn giản nhé. Khi các bạn check / uncheck check box thì đó là hai trạng thái của check box. Ví dụ rõ ràng hơn nữa là màn hình đăng nhập thể hiện ba trạng thái nhập email / mật khẩu sai định dạng (validate error), đăng nhập thành công hoặc đăng nhập fail (api error).

Nếu các bạn đã từng biết về lập trình mobile với các ngôn ngữ truyền thống như Swift hay Kotlin thì bây giờ chính là lúc chúng ta thay đổi tư duy!

Flutter chuyển đổi cách lập trình truyền thống từ lập trình mệnh lệnh (imperative) sang lập trình khai báo (declarative). Điều này có nghĩa là Flutter UI phản ánh các trạng thái trong app:

1-8

Trong định nghĩa của trang Flutter ở đây có nhắc đến, State có thể được đọc một cách đồng bộ khi widget được build và có thể thay đổi trong suốt vòng đời của widget đó.

Vậy thì Stateless Widget và Stateful Widget là gì? Chúng khác nhau ra sao? Chúng ta cùng tìm hiểu tiếp nhé!

5. StatelessWidget vs StatefulWidget

5.1. StatelessWidget

  • Stateless widget là các widget có trạng thái không thể thay đổi sau khi chúng được build. Chúng chỉ đơn thuần nhận dữ liệu và hiển thị một cách thụ động. Nếu muốn render lại thì ta phải khởi tạo lại chúng.

  • Một ví dụ về stateles widget là Text. Nó chỉ đơn giản hiển thị các văn bản mà chúng
    ta truyền cho nó. Ví dụ khác là IconButton. Nó chỉ khác Text ở phần là chúng ta có thể tương tác với button nhưng điều đó cũng không làm thay đổi bất cứ điều gì ở button.

  • Vòng đời: Vì không thay đổi nên vòng đời của stateless widget khá đơn giản, chỉ xoay quanh hàm build(). Câu hỏi được đặt ra ở đây là hàm build() của chúng sẽ được gọi khi nào? Mình có thể liệt kê ra những thời điểm như sau:

    • Lần đầu tiên widget được khởi tạo và chèn vào trong widget tree. Tìm hiểu thêm về widget tree tại Flutter cơ bản: Widget Tree, Element Tree và Render Tree.

    • Khi widget cha thay đổi cấu hình thì cũng sẽ kích hoạt hàm build() của các widget con và khiến chúng được render lại.

    • Khi InheritedWidget chúng phụ thuộc vào thay đổi. Cái này nghe khá khó hiểu nhỉ? Mình sẽ có một bài viết giải thích cụ thể về InheritedWidget sau nhé!

5.2. StatefulWidget

  • Stateful widget là các widget có thể thay đổi trạng thái. Khác với stateless widget, chúng ta không cần thiết phải khởi tạo lại stateful widget để thay đổi chúng mà chỉ cần thay đổi state của chúng và Flutter sẽ gọi hàm build() để render lại UI cho phù hợp với state đó.

  • Nghe hơi mơ hồ phải không? Mình ví dụ như nút like của facebook. Khi user bấm like thì nó sẽ đổi thành màu xanh phải không? Và khi user click thêm một lần nữa thì nó chuyển lại màu xám. Đó chính xác là một stateful widget.

  • Vòng đời: Vòng đời của Stateful widget thì khá phức tạp, các bạn hãy theo dõi hình sau đây nhé!

    2-6

Giải thích:

  • Trước khi đi vào giải thích trên từng con số ở ảnh thì mình muốn mọi người lưu ý một chút. Một statefut widget sẽ bao gồm hai phần là một widget và một state object. Bản thân đối tượng StatefulWidget là bất biến (immutable) và lưu trữ trạng thái có thể thay đổi (mutable) của chúng trong State object.

  • Trên hình mình chia ra làm 3 phần là:

    • Time to trigger: thời điểm kích hoạt

    • StatefulWidget: phần widget mà mình nhắc đến của StatefulWidget

    • State object: phần trạng thái có thể thay đổi được của StatefulWidget

  • Bây giờ cùng phân tích những con số trên hình nào!!!

    • (1): Thời điểm đầu tiên widget được khởi tạo và chèn vào trong widget tree.

    • (2) và (3): Sau khi gọi constructor, Flutter sẽ gọi hàm createState() để tạo ra đối tượng của State cho widget. Kể từ đó về sau, Flutter đa số chỉ làm việc State object mà thôi (vì đối tượng của StatefulWidget là bất biến, như mình đã nói ở trên).

    • (4): Trên State object, sau khi gọi hàm createState() thì hàm initState() sẽ được gọi. Và hàm này chỉ được gọi chỉ một lần duy nhất cho mỗi 1 đối tượng của State. Bạn có thể override phương thức này để khởi tạo các thuộc tính, khởi tạo dữ liệu, đăng ký Streams, ChangeNotifiers, vân vân và mây mây....

    • (5): Phương thức didChangeDependencies() được gọi ngay sau initState lần đầu tiên.

    • (6): Hàm build() được gọi để render UI. Vậy là hoàn thành các bước kể từ lần đầu tiên widget được khởi tạo. Sau đây sẽ là những bước thực hiện khi có bất kỳ sự thay đổi nào.

    • (7): Trường hợp InheritedWidget mà widget có dữ liệu phụ thuộc vào thay đổi thì sẽ kích hoạt hàm didChangeDependencies(), sau đó Flutter sẽ gọi build() để render lại UI.

    • (8) và (9): Giả sử như user tác động lên UI để thay đổi state của widget (ví dụ như like / unlike) thì khi đó việc của các developer chúng ta sẽ là gọi hàm setState() để thay đổi trạng thái của object. Việc này sẽ giúp cho framework được thông báo về sự thay đổi này và sẽ gọi hàm build() để render lại UI tương ứng.

    • (10) và (11): Khi widget cha của nó build lại và yêu cầu các widget con build cập nhật lại, Flutter sẽ thực hiện việc gọi hàm didUpdateWidget() . Sau đó, hàm build() cũng sẽ được gọi.

    • (12): Hàm dispose() được gọi khi state object bị xoá khỏi widget tree vĩnh viễn.

    Tóm lại, như bạn đã thấy ở trên hàm build() là cái hàm được gọi liên tục. Bởi vì sao? Bởi vì nó là nơi quyết định UI, nên khi có bất kỳ sự thay đổi cần thiết nào trên UI, nó sẽ được gọi lại để render tương ứng.

6. Cách làm việc với StatlessWidget và StatefulWidget

Ý tưởng của mình như sau:

Đầu tiên mình sẽ tạo một button là StalessWidget, sau đó convert button đó sang StatefulWidget. Mục đích của button này sẽ là like / unlike một cái gì đó giống như nút like trên facebook. Okay! Bắt tay vào thôi nào.

6.1. StatelessWidget

Các bạn tạo mới Flutter project và file like_button.dart nhé!

Bước 1: Tạo mới một StatelessWidget

Trên VS Code, để tạo mới một StalessWidget bạn chỉ cần gõ stl thì trình biên dịch sẽ đề xuất cho bạn

3-7

Bạn đặt tên class là LikeButton và nhớ import package:flutter/material.dart nhé!

import 'package:flutter/material.dart';

class LikeButton extends StatelessWidget {
  const LikeButton({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return IconButton(
        onPressed: () {
          print('Liked');
        },
        iconSize: 20,
        icon: const Icon(Icons.thumb_up_off_alt));
  }
}

Action của mình hiện tại chỉ đơn giản là print. Vậy là xong phần LikeButton. Bạn tiếp tục vào file main.dart để sử dụng button này nhé!

Bước 2: Sử dụng StatelessWidget đã tạo ở bước 1

Bạn hãy xoá phần class MyHomePage do framework cung cấp sẵn và tự tạo MyHomePage của mình. Vẫn là một StatelessWidget nhé!

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('StatelessWidget Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: const [
            LikeButton(),
            Text('You have not like yet!'),
          ],
        ),
      ),
    );
  }
}

*Lưu ý nho nhỏ: Ở đây mình có thêm 1 Text để thông báo user đã like hay chưa nhé!

Rồi chúng ta cùng build và xem kết quả nhé!

Kết quả:

Khi ấn nút like thì trên console sẽ log ra cho ta text "Liked".

6.2. StatefulWidget

Các bạn có thể dùng lại project trước đó hoặc tạo mới project tương tự như vậy.
5-7

Bước 1: Tạo mới một StatefulWidget

Trên VS Code, để tạo mới một StalefulWidget bạn chỉ cần gõ stf thì trình biên dịch sẽ đề xuất cho bạn. Nếu bạn đang sử dụng project trước thì bạn có thể convert LikeButton sang StatefulWidget bằng cách di chuyển con trỏ về đầu dòng class LikeButton -> click vào bóng đèn màu vàng chọn Convert to StatefulWidget.

6-6

Sau khi convert thì LikeButton đã trở thành như thế này:

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

  @override
  State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  @override
  Widget build(BuildContext context) {
    return IconButton(
        onPressed: () {
          print('Liked');
        },
        iconSize: 50,
        icon: const Icon(Icons.thumb_up_off_alt));
  }
}

Bước 2: Xử lý State object

Để biết được user đã like hay chưa thì mình cần có một biến lưu lại trạng thái này.

class _LikeButtonState extends State<LikeButton> {
  // 1
  bool isLiked = false;

  @override
  Widget build(BuildContext context) {
    return IconButton(
        onPressed: () {
          setState(() {
            // 3
            isLiked = !isLiked;
          });
        },
        iconSize: 50,
        // 2
        icon: Icon(isLiked ? Icons.thumb_up_alt : Icons.thumb_up_off_alt));
  }
}

Giải thích:

  1. Mình sẽ tạo ra biến isLiked kiểu bool với giá trị true nghĩa là user đã like và false là chưa like. Ở đây mình để giá trị default là false.

  2. Thay đổi ảnh của button tương ứng với trạng thái isLiked.

  3. Khi user tương tác với button, mình sẽ thực hiện việc đảo trạng thái của biến isLiked và thông báo cho framework thông qua việc gọi hàm setState()

Vậy là xong! Chúng ta cùng build lên xem kết quả nhé! Hiện tại, mình vẫn giữ MyHomePage giống như phần StatelessWidget nha!

Các bạn có để ý thấy phần Text ở dưới vẫn chưa khớp với trạng thái của button không? Đây là lúc chúng ta sẽ biến MyHomePage thành một StatefulWidget luôn!

Bước 3: Chuyển đổi MyHomePage thành StatefulWidget.

Tương tự như mình đã nhắc ở bước 1, các bạn có thể convert MyHomePage thành StatefulWidget bằng cách di chuyển con trỏ về đầu dòng class MyHomePage -> click vào bóng đèn màu vàng chọn Convert to StatefulWidget.

Tiếp theo, các bạn quay trở lại class LikeButton để thêm callback về nhằm thông báo cho MyHomePage biết trạng thái isLiked

import 'package:flutter/material.dart';

class LikeButton extends StatefulWidget {
  // 1
  final Function(bool) didTapButton;
  // 2
  const LikeButton(this.didTapButton, {Key? key}) : super(key: key);

  @override
  State<LikeButton> createState() => _LikeButtonState();
}

class _LikeButtonState extends State<LikeButton> {
  bool isLiked = false;

  @override
  Widget build(BuildContext context) {
    return IconButton(
        onPressed: () {
          setState(() {
            isLiked = !isLiked;
            // 3
            widget.didTapButton(isLiked);
          });
        },
        iconSize: 50,
        icon: Icon(isLiked ? Icons.thumb_up_alt : Icons.thumb_up_off_alt));
  }
}

Giải thích:

  1. didTapButton là 1 biến kiểu function và trả về kiểu bool. Biến được khai báo là final vì nó sẽ không thay đổi.

  2. Tạo constructor để init biến didTapButton.

  3. Sau khi isLiked được đảo trạng thái, chúng ta đồng thời callback trạng thái này ra để MyHomePage nhận biết sự thay đổi.

Sau khi sửa lại LikeButton, bạn quay lại MyHomePage và tiếp tục xử lý phần thay đổi Text:

class _MyHomePageState extends State<MyHomePage> {
  // 1
  String likedStatusText = 'You have not like yet!';

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('StatefulWidget Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: [
            // 3
            LikeButton((isLiked) {
              // 4
              setState(() {
                likedStatusText =
                  isLiked ? 'You liked!' : 'You have not like yet!';
              });
            }),
            // 2
            Text(likedStatusText),
          ],
        ),
      ),
    );
  }
}

Giải thích:

  1. Khai báo biến kiểu String trong State object của MyHomePage. Đây sẽ là nơi nhận sự thay đổi text.

  2. Lúc này Text sẽ sử dụng biến state chúng ta vừa tạo thay vì text set cứng như lúc đầu.

  3. Truyền vào đối số cho callback function của LikeButton.

  4. Khi nhận được callback trả về state của LikeButton thì MyHomePage sẽ gọi hàm setState() để set text tương ứng cho likedStatusText từ đó kích hoạt Flutter render lại UI.

Kết quả

Demo source code

7. Tổng kết

Tóm lại sự khác nhau rõ rệt và có thể dễ dàng thấy được nhất giữa StalessWidget và StatefulWidget chính là khả năng reload của widget ở runtime. Nếu như StalessWidget không thể thay đổi chỉ trừ khi được khởi tạo lại thì StatefulWidget có các trạng thái có thể thay đổi được và Flutter sẽ render lại UI cho phù hợp với từng trạng thái.

Bài viết cũng khá dài nên mình xin tạm gác bút lại ở đây. Hẹn gặp các bạn vào các bài viết tiếp theo của mình nhé!

Tài liệu tham khảo

Bài viết liên quan

Flutter Tutorial 2022: Giới thiệu Flutter Travel App

Trong series này, chúng ta sẽ cùng nhau thực hiện ứng dụng Travel App với một bản UI Design vô cùng đẹp mắt, thu hút có sẵn....

Flutter Tutorial 2022: Giới thiệu Flutter Travel App
Tổng hợp các Shortcuts, Extensions & Settings trong VSCode khi lập trình Flutter

Trong bài viết này, mình sẽ liệt kê cho bạn các shortcuts, extensions, setting mà bạn nên sử dụng để lập trình Flutter hàng ngày....

Tổng hợp các Shortcuts, Extensions & Settings trong VSCode khi lập trình Flutter
[Phần 1] Những extension cần thiết khi làm việc với Flutter trên VS Code

VS Code là 1 text editor tuyệt vời được phát triển bởi Mircosoft. Đây là một trong các công cụ được developer trên toàn thế giới yêu thích vì sự tiện lợi, dễ dùng và nó chứa hàng ngàn, thậm chí trăm ngàn các extension phục vụ cho bạn trong quá trình bạn phát triển phần mềm....

[Phần 1] Những extension cần thiết khi làm việc với Flutter trên VS Code
Fix lỗi Flutter 3 không thể build app trên iOS

Cách fix lỗi Flutter 3 không thể build và run được app trên iOS với video hướng dẫn chia tiết...

Fix lỗi Flutter 3 không thể build app trên iOS
Flutter Coding UI Speed Code

Nhận bản UI siêu đẹp nhưng làm sao để phân tích rồi code ra một cách chính xác nhất? Series này 200Lab sẽ cho bạn một góc nhìn thực tế về quá trình code UI cho app Movie Ticket....

Flutter Coding UI Speed Code
You've successfully subscribed to 200Lab Blog
Great! Next, complete checkout for full access to 200Lab Blog
Xin chào mừng bạn đã quay trở lại
OK! Tài khoản của bạn đã kích hoạt thành công.
Success! Your billing info is updated.
Billing info update failed.
Your link has expired.