Category: Uncategorized

  • Dependency Injection

    Dependency Injection

    Dependency Injection là một mẫu thiết kế được sử dụng để triển khai Inversion of Control. Nó cho phép tạo các đối tượng phụ thuộc bên ngoài một lớp và cung cấp các đối tượng đó cho một lớp thông qua các cách khác nhau. Sử dụng DI, chúng ta di chuyển việc tạo và ràng buộc các đối tượng phụ thuộc ra bên ngoài lớp phụ thuộc vào chúng. Điều này mang lại mức độ linh hoạt cao hơn, phân tách và kiểm tra dễ dàng hơn.

    Khi mà class A sử dụng một số chức năng của class class B, thì có thể nói là class A có quan hệ phụ thuộc với class B.

    Trong java, trước khi ta có thể sử dụng method của class khác, ta phải khởi tạo một đối tượng của class đấy.

    Tại sao cần sử dụng Dependency Injection?

    Ví dụ chúng ta có một class Car, trong đó có chứa đối tượng khác như Wheel.

    Ở đây, class Car chịu trách nhiệm khởi tạo tất cả các dependency object. Nhưng chuyện gì sẽ xảy ra nếu chúng ta muốn bỏ Wheel và thay thế bằng SteelWheel hoặc PlasticWheel.

    Để giải quyết vấn đề trên thì chúng ta phải tạo một class Car mới với SteelWheel hoặc PlasticWheel. Tuy nhiên khi sử dụng dependency injection, chúng ta có thể đổi Wheel trong thời gian chương trình chạy (Runtime) vì dependency có thể được đẩy vào ở Runtime thay vì Compiletime.

    Có 3 loại Dependency Injection

    1. Constructor injection: Các dependency được cung cấp thông qua constructor của class.
    2. Setter Injection: Khách hàng tạo ra một setter method để các class khác có thể sử dụng chúng để cấp dependency.
    3. Interface injection: Dependency sẽ cung cấp một hàm injector để inject nó vào bất kỳ khách hàng nào được truyền vào. Các khách hàng phải implement một interface mà có một setter method dành cho việc nhận dependency.

    Trách nhiệm của dependency injection là:

    1. Tạo ra các object.
    2. Biết class nào cần object đấy.
    3. Cung cấp cho những class đó object chúng cần.

    Bằng cách này, nếu trong tương lai object đó có sự thay đổi thì dependency injection có nhiệm vụ cấp lại những object cần thiết cho class.

    Điểm mạnh

    1. Giúp unit test dễ hơn.
    2. Giảm thiểu được code mẫu (boilerplate code) vì việc khởi tạo dependency được làm bởi một component khác.
    3. Mở dụng dự án dễ dàng hơn.
    4. Giúp ích trong việc liên kết lỏng giữa các thành phần trong dự án

    Điểm yếu

    1. Khá phức tạp để học.
    2. Rất nhiều các lỗi ở compile time có thể đẩy sang runtime.
    3. Có thể làm ảnh hường tới chức năng auto-complete hay Find references của một số IDE.

    Nguồn tham khảo:

    1. https://viblo.asia/p/dependency-injection-la-gi-va-khi-nao-thi-nen-su-dung-no-LzD5d0d05jY
  • Extension trong Dart và Flutter

    Với những bạn nào đã từng sử dụng Kotlin khi lập trình Android và có tìm hiểu sâu một chút, chắc hẳn các bạn đã biết và cũng sẽ rất ấn tượng với extension của Kotlin. Mình cũng đã từng sử dụng và thấy nó giúp ích rất nhiều trong quá trình code bởi chúng ta có thể viết thêm các phương thức cho các class mà không phải sửa trực tiếp vào class. Việc này rất hữu dụng trong trường hợp bạn muốn viết thêm vào một class nào đó nhưng class đó lại nằm trong một thư viện nào đó hoặc thậm chí là class trong core của Kotlin như String, List,… Một ví dụ (viết bằng dart để cho mọi người dễ nắm bắt):

    Theo cách truyền thống :

    Giả sử bài toán cho một list số nguyên mà tìm trung bình cộng :

    Áp dụng extension :

    Cú pháp ở đây là: extension <TênCủaExtension> on <ClassMuốnExtend> {}.

    Trong đó tên của extension là do bạn chọn, miễn là không trùng với các class đã có sẵn để tránh nhầm lẫn khi sử dụng.

    Nhiều bạn sẽ thắc mắc, this ở đây là gì, tại sao lại có this này nhỉ? Thì this ở đây chính là instance của class String mà chúng ta dùng function này (ở đây là [1,2,3,4,5]). Khi viết function trong extension, chúng ta có thể access trực tiếp vào các member variable của class mà chúng ta đang extend, ví dụ như String thì chúng ta sẽ có thể dùng this.length, this.substring(), this.split() (có thể bỏ chữ this nếu như không bị trùng tên với params truyền vào trong function) , list thì có thể list.leght , list. , list.isEmpty

    Cá nhân mình thì hiểu extention dùng để custom các phương thức mà mình muốn khi không có ở mặc định giúp code gọn gàng .

    Một số extension mà mình áp dụng extention

    Xử lý onclick liên tục

    Mình đã giới thiệu qua về extension trong Dart và Flutter. Đây là một tính năng rất hay mà mình muốn mọi người nên biết và sử dụng, hi vọng các bạn thấy hữu ích và áp dụng tính năng này một cách hiệu quả

  • Cách viết Unit Test trong Flutter

    [FLUTTER] Cách viết Unit Test trong Flutter (Phần 2)

    Lời ngỏ

    Mỗi ngôn ngữ, hay framework đều có các cách để triển khai Unit Test khác nhau. Tuy nhiên trong bài viết này mình sẽ chú trọng vào Unit Test trong Flutter.

    B. Cách triển khai Unit Test trong Flutter

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

    Để có thể viết Unit Test trong Flutter, chúng ta cần thêm thư viện test, nhớ thêm vào phần dev_dependencies nhé.

    2. Cấu hình và giải thích

    • Đầu tiên chúng ta tạo một hàm xử lý để giả định cho một chức năng trong app
    class Calculation{
      int add(int a, int b){
        return a+b;
      }
      int subtract(int a, int b){
        return a-b;
      }
    }
    
    
    • Trong class Calculation, chúng ta có hàm xử lý là hàm “add”,”subtract”, đây chính là hàm chúng ta cần viết unit test. Bước tiếp theo là tạo một class để viết unit test.
    • Ở trong phần package tree của project chúng ta sẽ thấy một package tên là “test”.

    Đây sẽ là nơi chúng ta viết các unit test cho ứng dụng.

    Có thể thấy ở bên trong folder này đã có một class được viết sẵn có tên là “widget_test.dart”, đây sẽ là class dùng trong việc test các widget của ứng dụng.

    • Chúng ta tạo thêm một file đặt tên là “calculation_test” (Hãy đặt tên của file theo những chức năng, lớp mà chúng ta muốn test để dễ trong việc phân biệt và tìm kiếm.
    • Chúng ta tạo một class “main”, bên trong sẽ viết các hàm unit test
    import 'package:flutter_test/flutter_test.dart';
    import 'package:unit_test_sample/calculation.dart';
    
    void main(){
      /// Init class which needs tested
      Calculation calculation = Calculation();
      /// Test function add
      test("Sum of two integer ", () {
        int result = calculation.add(5, 4);
        expect(result, 9);
      });
    }
    • Ở đây ta sử dụng hàm “test” để viết các unit test cho từng chức năng.

    Hàm “test” này có 2 tham số cần truyền vào đó là:

    • Mô tả của test, nơi chúng ta có thể mô tả xem hàm test này đang test chức năng nào, hoặc có thể bổ sung thêm một vài mô ta cho input chúng ta truyền vào (ví dụ như input đó bị lỗi, sai, hay đúng).
    • Tham số còn lại chính là một hàm, ở đây chúng ta sẽ viết các bước để có thể gọi được đến hàm cần test (ở đây là hàm “add”), cuối cùng chúng ta sử dụng hàm “expect” để kiểm tra kết quả của hàm test với kết quả mà chúng ta mong đợi. Tham số đầu tiên sẽ là kết quả của hàm được test, tham số còn lại là kết quả mà chúng ta mong muốn.

    Như vậy phần chuẩn bị hàm test đã xong, giờ việc chúng ta cần làm là chạy thôi:

    • Ấn chuột phải rồi chọn “run test in calculation” hoặc ấn tổ hợp phím Ctrl + Shift + F10:
    • Sau một lúc chạy thì ở phía dưới sẽ hiển thị một màn hình kết quả test:

    Nếu có tick xanh ở trước mô tả “Sum of two integer”, có nghĩa là test của chúng ta đã chạy đúng như mong đợi.

    Thử thay đổi kết quả mong đợi để xem chuyện gì xảy ra:

    test("Sum of two integer ", () {
        int result = calculation.add(5, 4);
        expect(result, 8);
      });

    Ngay lập tức test của chúng ta đã chạy sai, trong phần log lỗi cũng đã chỉ ra cho chúng ta biết chúng ta sai ở đâu.

    • Expected (kết quả mong đợi) là 8 trong khi Actual (kết quả được tính toán) là 9, từ đó chúng ta sẽ rất dễ dàng trong việc fix lỗi.

    Khi có nhiều hàm test cùng liên quan đến một vấn đề, chúng ta có thể cho chúng vào trong một “group” để dễ quản lý hơn.

    void main() {
      /// Init class which needs tested
      Calculation calculation = Calculation();
      group(
        "Calculate with integer",
        () {
          /// Test function add
          test("Sum of two integer ", () {
            int result = calculation.add(5, 4);
            expect(result, 9);
          });
    
          /// Test function subtract
          test("Subtraction of two integer ", () {
            int result = calculation.subtract(5, 4);
            expect(result, 1);
          });
        },
      );
    }

    Và khi chạy lên chúng ta sẽ được kết quả như sau:

    3. Tài liệu tham khảo

    Chúc các bạn luôn nhìn thấy một màu xanh lá khi chạy Unit Test

    Tác giả: LamNT59

  • KIẾN THỨC CƠ BẢN VỀ DART (PHẦN 3)

    KIẾN THỨC CƠ BẢN VỀ DART (PHẦN 3)

    Sang phần 3 này, chúng ta sẽ tìm hiểu về Control Flow.

    Control flow cho phép bạn ra lệnh khi nào thực thi, bỏ qua hoặc lặp lại một số dòng mã nhất định. Bạn sử dụng các điều kiện (conditionals ) và vòng lặp (loops ) để xử lý luồng điều khiển trong Dart.

    Trong phần này, bạn sẽ tìm hiểu thêm về:

    • Conditionals
    • While Loops
    • Continue and Break
    • For Loops

    Đây là những gì bạn cần biết về các phần tử luồng điều khiển trong Dart.

    Conditionals

    Hình thức cơ bản nhất của luồng điều khiển là quyết định thực thi hay bỏ qua các phần nhất định của mã của bạn, tùy thuộc vào các điều kiện xảy ra khi chương trình của bạn chạy.

    Cấu trúc ngôn ngữ để xử lý các điều kiện là câu lệnh ifelseifelse trong Dart trông gần giống với việc sử dụng nó trong các ngôn ngữ giống C khác.

    Câu lệnh If

    Giả sử bạn có một biến animal đó hiện là một con cáo. Nó trông như thế này:

    var animal = 'fox';
    

    Bạn có thể sử dụng một if câu lệnh để kiểm tra xem đó animal là mèo hay chó, sau đó chạy một số mã tương ứng.

    if (animal == 'cat' || animal == 'dog') {
      print('Animal is a house pet.');
    }

    Ở đây, bạn đã sử dụng các toán tử equality OR để tạo bool điều kiện bên trong cho if câu lệnh.

    Câu lệnh else

    Với một else mệnh đề, bạn có thể chạy mã thay thế nếu điều kiện sai:

    else {
      print('Animal is NOT a house pet.');
    }
    // Animal is NOT a house pet.

    Bạn cũng có thể kết hợp nhiều câu lệnh ifelse thành một hàm ifelse ifelse:

    if (animal == 'cat' || animal == 'dog') {
      print('Animal is a house pet.');
    } else if (animal == 'rhino') {
      print('That\'s a big animal.');
    } else {
      print('Animal is NOT a house pet.');
    }
    // Animal is NOT a house pet.

    Bạn có thể có nhiều else if nhánh giữa if và else tùy theo nhu cầu.

    While Loops

    Vòng lặp cho phép bạn lặp lại mã một số lần nhất định hoặc dựa trên các điều kiện nhất định. Bạn xử lý sự lặp lại dựa trên điều kiện bằng cách sử dụng vòng lặp while .

    Có hai dạng vòng lặp while trong Dart: while và do-while. Sự khác biệt là với while, điều kiện lặp xảy ra trước khối mã. Trong khi do-while, điều kiện xảy ra sau. Điều đó có nghĩa là do-while các vòng lặp đảm bảo khối mã chạy ít nhất một lần.

    Ví dụ While Loop

    Để thử điều này, hãy tạo một biến được i khởi tạo thành 1:

    var i = 1 ;
    

    Tiếp theo, sử dụng một while vòng lặp để in i trong khi tăng dần. Đặt điều kiện thành i nhỏ hơn 10:

    while (i < 10) {
      print(i);
      i++;
    }
    // 1
    // 2
    // 3
    // 4
    // 5
    // 6
    // 7
    // 8
    // 9

    Chạy mã và bạn sẽ thấy rằng while vòng lặp in các số từ 1 đến 9.

    Ví dụ Do-While

    Đặt lại i trong DartPad, sau đó thêm vòng lặp do-while:

    i = 1;
    do {
      print(i);
      i++;
    } while (i < 10);
    // 1
    // 2
    // 3
    // 4
    // 5
    // 6
    // 7
    // 8
    // 9
    

    Kết quả vẫn giống như trước đây. Tuy nhiên, lần này, phần thân của vòng lặp đã chạy một lần trước khi kiểm tra điều kiện thoát khỏi vòng lặp.

    Continue and Break

    Dart sử dụng continue và break từ khóa trong các vòng lặp và các nơi khác. Đây là những gì họ làm:

    • continue : Bỏ qua mã còn lại bên trong một vòng lặp và ngay lập tức chuyển sang lần lặp tiếp theo.
    • break : Dừng vòng lặp và tiếp tục thực hiện sau phần thân của vòng lặp.

    Hãy cẩn thận khi sử dụng continue trong mã của bạn. Ví dụ: nếu bạn thực hiện do-while vòng lặp từ trên và bạn muốn tiếp tục khi i bằng 5, điều đó có thể dẫn đến một vòng lặp vô hạn tùy thuộc vào vị trí bạn đặt câu lệnh continue :

    i = 1;
    do {
      print(i);
      if (i == 5) {
        continue;
      }            
      ++i;
    } while (i < 10);
    // 1
    // 2
    // 3
    // 4
    // 5
    // 5
    // 5
    // 5
    // 5
    // 5
    // 5
    // 5
    // 5
    // 5
    // ...

    Vòng lặp vô hạn xảy ra bởi vì, một khi i là 5, bạn không bao giờ tăng nó nữa, vì vậy điều kiện luôn đúng.

    Nếu bạn chạy điều này trong DartPad, vòng lặp vô hạn sẽ khiến trình duyệt bị treo. Thay vào đó, hãy sử dụng break, để vòng lặp kết thúc sau khi i đạt đến 5:

    i = 1;
    do {
      print(i);
      if (i == 5) {
        break;
      }
      ++i;
    } while (i < 10);
    // 1
    // 2
    // 3
    // 4
    // 5

    Chạy mã. Bây giờ, vòng lặp kết thúc sau năm lần lặp.

    For Loops

    Trong Dart, bạn sử dụng các vòng lặp for để lặp lại một số lần được xác định trước. Vòng lặp for bao gồm khởi tạo, một điều kiện vòng lặp và một hành động. Chúng tương tự như for các vòng lặp trong các ngôn ngữ khác.

    Dart cũng cung cấp một for-in vòng lặp, lặp lại trên một tập hợp các đối tượng. Bạn sẽ tìm hiểu thêm về những điều này sau.

    Để xem cách for hoạt động của một vòng lặp, hãy tạo một biến cho sum:

    var sum = 0 ;
    

    Tiếp theo, sử dụng một vòng lặp for để khởi tạo bộ đếm vòng lặp từ i đến 1. Sau đó, bạn sẽ kiểm tra xem i nhỏ hơn hoặc bằng 10 và tăng dần i sau mỗi vòng lặp.

    Bên trong vòng lặp, sử dụng phép gán ghép để thêm i vào tổng đang chạy:

    for (var i = 1; i <= 10; i++) {
      sum += i;  
    }
    print("The sum is $sum"); // The sum is 55
    
    

    Chạy mã của bạn trong DartPad để xem tổng.

    Kết thúc phần 3!

  • [FLUTTER] Một số widget hữu ích trong Flutter (Phần 1)

    [FLUTTER] Một số widget hữu ích trong Flutter (Phần 1)

    Giới thiệu

    Khi làm việc với Flutter có lẽ ai cũng biết câu nói:

    Trong Flutter, (gần như) mọi thứ đều là widget.

    Nhưng cũng chính vì có quá nhiều widget, lập trình viên đôi khi cảm thấy khó khăn trong việc lựa chọn, thậm chí là không biết đến sự tồn tại của một widget đã được viết sẵn phục vụ cho những yêu cầu thường gặp, thành ra phải tự code lại tốn nhiều thời gian mà chưa chắc đã thực sự tốt.

    Vì vậy, trong bài viết này, mình sẽ giới thiệu một số widget có thể sẽ giúp ích trong quá trình anh em làm việc với Flutter.

    À, nhưng trước hết, mình vẫn muốn recommend anh em kênh youtube chính thức của Flutter, có playlist Widgets of the Week (có lẽ nhiều người đều biết đến list này), mình thường xuyên vào đây xem khi có thời gian rảnh 😀

    1. FittedBox

    Chắc hẳn khi đọc tên thì anh em cũng đã tưởng tượng được ra tác dụng của widget này. Cũng giống như khi sử dụng các widget liên quan tới image, chúng ta có thuộc tính fit để thể hiện cách vẽ widget con (điều này chắc ai cũng biết rồi).

    Nhưng có một cách sử dụng khá hay mà mình mới được biết gần đây là làm cho các text trở nên responsive, khi độ lớn của widget cha thay đổi, text bên trong cũng thay đổi theo, ví dụ mình hoạ tại đây.

    FittedBox(
        fit: BoxFit.fitWidth,
        child: Text("Fitted Box"),
    )
    Text Responsive với FittedBox

    2. Wrap

    Widget này gần tương tự với Flex (lớp cha của Row và Column), nhưng khác ở điểm có thể tự sắp xếp các widget con sang cột / hàng mới tuỳ vào kích thước còn lại. Ví dụ

    Ví dụ về Wrap

    Một số thuộc tính trong Wrap như sau:

    • direction: xác định trục chính (vertical / horizontal)
    • spacing: khoảng cách giữa các widget trong trục chính (Main Axis).
    • runSpacing: khoảng cách giữa các widget trong trục phụ (Cross Axis).
    • alignment: vị trí theo trục chính của các widget con khi được wrap sang một hàng / cột mới (start, center, spaceBetween, …)
    • runAlignment: vị trí theo trục phụ của các widget con khi được wrap sang một hàng / cột mới.

    Ngoài ra còn một số thuộc tính nữa anh em có thể đọc thêm tại link doc của Flutter.

    3. Flexible, Expanded và Spacer

    Tại sao mình lại đưa ra một lúc 3 widget? Bởi vì 3 widget này khá tương đồng nhau về cách sử dụng, để mình giải thích cho nhé.

    3.1. Flexible

    Hãy tưởng tượng, anh em muốn làm một giao diện có các widget con bên trong có thể thay đổi kích thước theo widget cha (không nên nhầm lẫn với việc size màn hình thay đổi nhé), như vậy thì không thể sử dụng các widget có size cố định, chúng ta hãy nghĩ ngay đến việc sử dụng 3 widget trên xem sao ^^

    Ví dụ, chúng ta có 1 Row, bên trong gồm có 4 Container chứa Text, vậy làm thế nào để:

    • Container 1 chiếm 10%
    • Container 2 chiếm 20%
    • Container 3 chiếm 30%
    • Container 4 chiếm 40%

    Rất đơn giản, anh em chỉ cần wrap từng widget con vào trong widget Flexible và cho chúng hệ số flex như sau:

    SizedBox(
              height: 100,
              child: Row(
                children: [
                  Flexible(
                      flex: 1, child: Container(color: Colors.red.shade100)),
                  Flexible(
                      flex: 2,
                      child: Container(
                        color: Colors.red.shade200,
                      )),
                  Flexible(
                      flex: 3,
                      child: Container(
                        color: Colors.red.shade300,
                      )),
                  Flexible(
                      flex: 4,
                      child: Container(
                        color: Colors.red.shade400,
                      )),
                ],
              ),
            ),

    Kích thước của children sẽ được tính như sau:

    • Tính tổng hệ số flex của các widget có trong parent (Bao gồm Flexible, Spacer và Expanded): ví dụ ở trên sẽ là 1 + 2 + 3 + 4 = 10
    • Đặt kích thước của widget theo tỉ lệ hệ số flex / tổng, Ví dụ Container 1 sẽ chiếm 1/10 tức 10% width của row

    Vậy là chúng ta đã có một giao diện động mà không cần sử dụng các kích thước cố định 😀 :D.

    Nhưng nếu giữa các widget Flexible mà có widget kích thước cố định thì sao? Sẽ không có vấn đề gì cả, flutter sẽ trừ đi phần cố định chia theo hệ số flex như trên

    3.2. Expanded

    Expanded cũng giống như Flexible, chỉ khác là nó sẽ luôn muốn chiếm hết không gian còn lại nếu có thể, còn Flexible thì mặc định sẽ chỉ chiếm vừa đủ không gian bằng widget con của nó, và có một điều cần lưu ý:

    Flexible(
    
          fit: FlexFit.tight,
          ...
    ),

    sẽ tương đương với:

    Expanded(
          ...
    )

    Chỉ là đặt tên như vậy sẽ tường minh hơn, dễ trong việc sử dụng hơn 😀 😀

    3.3. Spacer

    Đúng như cái tên của nó, widget này đơn giản là chỉ tạo ra khoảng trắng, được sử dụng giống 2 widget trên, cũng có hệ số flex

    Lưu ý:

    • Các widget này phải được sử dụng trực tiếp dưới widget cha là các widget flex như Flex, Row, Column.
    • Mặc định hệ số flex sẽ luôn là 1

    (Còn tiếp…)

    Tham khảo

    widgets library – Dart API (flutter.dev)

    My 10 Most Favorite Flutter Widgets – YouTube

  • Flutter vs React Native vs Native: So sánh chi tiết hiệu năng

    Hãy so sánh hiệu năng FPS, CPU, Memory và GPU của các công cụ phát triển thiết bị di động phổ biến.

    Câu chuyện đằng sau việc nghiên cứu

    inVerita và nhóm phát triển mobile của mình liên tục nghiên cứu hiệu năng của các giải pháp mobile đa nền tảng hiện có để trả lời câu hỏi công nghệ nào tốt nhất Flutter hoặc React Native (hoặc Native) cho sản phẩm của bạn, đó là cách Flutter vs React Native vs Native Part I nổi lên. Điều đó gây ra nhiều tranh cãi vì người ta nói rằng không sử dụng React Native để thực hiện phép tính (perform multiple calculations) hàng ngày – có thể đúng như vậy – nhưng trong trường hợp này, các task nặng của CPU được ứng dụng Flutter hoặc Native thực hiện tốt hơn.

    Đó là lý do tại sao trong bài viết này, chúng tôi quyết định nghiên cứu hiệu năng của UI có tác động lớn hơn nhiều đến daily user của mobile app.

    Việc đo lường hiệu năng UI rất phức tạp và yêu cầu kỹ sư triển khai cùng chức năng theo cùng một cách trên mọi nền tảng. Chúng tôi đã sử dụng GameBench, công cụ kiểm tra toàn cầu để đảm bảo sự khách quan (nó không thay đổi sự thật là chúng tôi thực sự yêu thích Flutter ở nhiều khía cạnh 🙂 và vẫn chạy rất nhiều dự án React Native và Native). GameBench có rất nhiều không gian để cải tiến, nhưng chúng tôi đã cố gắng đưa mọi ứng dụng vào một môi trường single testing với sự trợ giúp của nó, đó là mục tiêu của chúng tôi.

    Source code mở vì vậy hãy thử nghiệm và chia sẻ suy nghĩ của bạn với chúng tôi nếu bạn muốn. UI animation chủ yếu sử dụng các công cụ khác nhau trên các nền tảng khác nhau, vì vậy chúng tôi thu hẹp mọi thứ vào các thư viện được hỗ trợ bởi mọi nền tảng (trừ một trường hợp) hoặc ít nhất chúng tôi đã làm mọi thứ để hoàn thành điều đó. Kết quả test có thể khác nhau và tùy thuộc vào phương pháp triển khai, chúng tôi tin rằng bạn có thể đẩy bộ tool đến giới hạn mà nó vượt trội so với các con số của chúng tôi. Bây giờ, chúng ta hãy xem xét các trường hợp.

    Thông tin thiết bị phần cứng:

    Đối với mục đích thử nghiệm của chúng tôi, chúng tôi đã sử dụng một chiếc Xiaomi Redmi Note 5 và iPhone 6s giá cả phải chăng.

    Repo link:

    Source code

    Use case 1 — List view benchmarking

    Chúng tôi đã triển khai cùng một UI trên cả Android và iOS ,sử dụng Native, React Native và Flutter. Chúng tôi cũng tự động hóa tốc độ scroll bằng cách sử dụng RecyclerView.SmoothScroller trên Android. Trên iOS và React Native, chúng tôi đã sử dụng cách tiếp cận với timer và lập trình scroll đến vị trí. Trên Flutter, chúng tôi đã sử dụng ScrollController để scroll qua danh sách một cách trơn tru. Trong mỗi trường hợp, chúng tôi có 1000 phần tử trong list view và cùng một thời gian scroll để đến element cuối cùng. Trong mỗi trường hợp này, chúng tôi đã sử dụng hình ảnh trong bộ nhớ đệm (image caching) với các lib khác nhau trên mỗi nền tảng. Xem thông tin chi tiết trong source code.

    iOS

    • Tải và lưu hình ảnh vào bộ nhớ đệm – Nuke

    Android

    • Tải và lưu hình ảnh vào bộ nhớ đệm – Glide

    React Native

    Android — GPU tests results are not supported by the benchmark (unfortunately, with the devices we have, and we have many:)) )

    Kết quả kiểm tra Android – GPU không được hỗ trợ bởi benchmark

    Kết quả kiểm tra
    1. Tất cả các thử nghiệm đều cho thấy FPS xấp xỉ như nhau.
    2. Android Native sử dụng một nửa memory so với Flutter và React Native.
    3. React Native yêu cầu khai thác CPU nhiều nhất. Lý do là việc sử dụng JSBridge giữa mã JS và Native kích động sự lãng phí tài nguyên khi serialization và deserialization.
    4. Về khai thác pin, Android Native có kết quả tốt nhất. React-native đang tụt hậu so với cả Android và Flutter. Chạy các animation liên tục sẽ tiêu tốn nhiều pin hơn trên React Native.

    Test trên iPhone 6s

    Kết quả kiểm tra
    1. FPS: Kết quả của React Native kém hơn so với Flutter và Swift. Lý do là không thể sử dụng biên dịch (compilation) IoT trên iOS.
    2. Memory: Flutter gần như khớp với nguyên bản (native) về mức tiêu thụ Memory nhưng vẫn nặng hơn trên CPU. React Native thua xa Flutter và native trong thử nghiệm này.
    3. Sự khác biệt giữa Flutter và Swift. Flutter đang tích cực sử dụng CPU khi iOS Native đang tích cực sử dụng GPU. Đối chiếu trong Flutter làm tăng tải trên CPU.

    Use case 2 — Heavy animations test

    Ngày nay hầu hết các điện thoại chạy trên Android và iOS đều có phần cứng rất mạnh. Trong hầu hết các trường hợp sử dụng các ứng dụng kinh doanh, có thể thấy không có sự sụt giảm số khung hình/giây nào. Đó là lý do tại sao chúng tôi quyết định thực hiện một số thử nghiệm với animation nặng. Đủ nặng để giảm số khung hình/giây. Chúng tôi đã sử dụng animation animated vector với Lottie trên Android, iOS, React Native và sử dụng các animation tương tự để sử dụng với Flare on Flutter.

    Thử nghiệm animation với Lottie cho Android, iOS, React Native và Flare cho Flutter.

    Lottie cho Android

    Android

    Kết quả kiểm tra
    1. Android và React Native có những điểm tương đồng về hiệu năng của chúng. Đó là điều hiển nhiên vì Lottie cho React Native sử dụng phương tiện Native (16–19% CPU, 30–29 FPS).
    2. Kết quả của Flutter là một bất ngờ, mặc dù nó có một chút trục trặc trong một performance. (12% CPU và 9 FPS).
    3. Android yêu cầu ít bộ nhớ nhất (205 Mb); React Native cần 280 Mb và Flutter cần 266 Mb.
    4. Khởi động lại app. Theo chỉ số này, Flutter là người dẫn đầu (2 giây). Đối với Android Native và React Native, mất khoảng 4 giây.

    Chúng tôi phát hiện ra rằng việc xóa một animation cụ thể khỏi lưới (grid) sẽ tăng FPS lên đến 40% trên Flutter. Chúng tôi cho rằng Flare nặng hơn và không được tối ưu hóa cho loại task này, đó là lý do tại sao Flutter lại bị sụt FPS như vậy.

    IOS

    Kết quả kiểm tra
    1. Kết quả của iOS và React Native trong bài kiểm tra này gần giống như Lottie đối với React Native.
    2. Flare và Flutter sẽ không ngừng khiến bạn ngạc nhiên. Flare chắc chắn có một con đường để đi 😀
    3. iOS Native yêu cầu ít bộ nhớ nhất (48 Mb). React Native cần 135 Mb và Flutter cần 117 Mb.
    4. Khởi động cold app. Theo chỉ số này, Flutter là người dẫn đầu (2 giây). Đối với iOS và React Native, mất khoảng 10 giây.

    Lưu ý: chúng tôi đã sử dụng một thư viện khác cho trường hợp này với Flutter nặng hơn nhiều so với những thư viện đã sử dụng cho các nền tảng khác và nó có thể là lý do khiến fps giảm.

    Use case 3 — Kiểm tra animation thậm chí còn nặng hơn với các rotation, scaling và fade.

    Trong thử nghiệm này, chúng tôi đã so sánh hiệu năng trong khi tạo animation cho 200 hình ảnh. Các animation xoay tỷ lệ và mờ dần được thực hiện cùng một lúc.

    200 hình ảnh

    Android

    Kết quả kiểm tra
    1. Native cho thấy hiệu năng cao nhất và tiêu thụ bộ nhớ hiệu quả nhất.
    2. Flutter cho thấy hiệu năng vừa đủ để làm việc thoải mái nhưng chi phí bộ nhớ cao hơn gấp đôi so với Native.
    3. React Native đã cho thấy hiệu năng thấp trong trường hợp này.

    IOS

    Kết quả kiểm tra
    1. iPhone 6s đủ mạnh để không giảm fps trong cả 3 trường hợp.
    2. Native sử dụng ít tài nguyên hơn và GPU được sử dụng gần hết.
    3. React Native chủ yếu sử dụng CPU để hiển thị trong khi Flutter sử dụng GPU.
    4. React Native đã sử dụng nhiều bộ nhớ hơn một chút.

    Tóm lại

    Đối với các ứng dụng thông thường có animation nhỏ và vẻ ngoài lấp lánh, công nghệ không thành vấn đề. Nhưng nếu bạn sẽ thực hiện một số animation nặng, hãy nhớ rằng shiny Native có sức mạnh hiệu năng cao nhất để làm điều đó. Tiếp theo, hãy đến với Flutter và React Native. Chúng tôi chắc chắn không khuyên bạn nên sử dụng React Native trong một hoạt động quá nặng về CPU, trong khi Flutter rất phù hợp cho các task như vậy từ cả quan điểm CPU và Memory.

    Công cụ bạn chọn tùy thuộc vào sản phẩm và business case cụ thể của bạn. Trong trường hợp bạn đang tìm cách phát triển MVP một nền tảng – hãy sử dụng các phương tiện gốc, nhưng hãy nhớ rằng các ứng dụng Flutter có thể được xây dựng cho cả môi trường mobile, web, desktop và có vẻ như Flutter có thể trở thành Vua phát triển đa nền tảng trong tương lai không xa, vì hiện tại Flutter đã tạo ra một cuộc cạnh tranh cho các công cụ phát triển native, đặc biệt nếu ngân sách phát triển không hạn chế mà bạn vẫn đang tìm kiếm hiệu năng tốt cho ứng dụng của mình trên các nền tảng khác nhau.

    Chúng tôi phải đối mặt với thực tế là có thể có nhiều yếu tố ảnh hưởng đến việc triển khai và benchmark của từng công nghệ và nhiều người trong số các bạn có thể là chuyên gia thực sự của một nền tảng cụ thể có thể khai thác nhiều hơn nữa bộ tool yêu thích. Chúng tôi đã cố gắng giải thích bằng cách tạo ra một môi trường duy nhất cho mỗi ứng dụng để thử nghiệm và một bộ công cụ duy nhất để đo lường hiệu năng và tôi hy vọng bạn thích kết quả này.

    Bài viết này được dịch từ đây.

  • Flutter – Life cycle

    Flutter – Life cycle

    Statefulwidget cần sử dụng state object để xử lý tương tác của người dùng hoặc thay đổi dữ liệu nội bộ của nó trong một giai đoạn cụ thể, được thể hiện trong giao diện người dùng. Giai đoạn cụ thể này bao gồm toàn bộ quá trình từ loading đến unloading một component, cụ thể là vòng đời. widget trong fltter cũng có vòng đời, được biểu thị bằng state

    App là một widget đặc biệt. Ngoài việc xử lý các giai đoạn của view hiển thị (tức là vòng đời của view), bạn cũng cần phải xử lý các state (vòng đời của app) mà app trải qua từ đầu đến khi thoát.

    State Life cycle

    Vòng đời của state đề cập đến các giai đoạn của widget được liên kết, từ khởi tạo đến hiển thị, cập nhật, dừng, hủy, v.v. Vòng đời của state có thể được chia thành ba giai đoạn: create (chèn view vào cây), update (tồn tại trong cây view), destroy (xóa khỏi cây view).

    Thiết lập

    Khởi tạo state sẽ được thực hiện liên tiếp: phương thức khởi tạo(contructor) -> initstate -> dischangedependencies -> build, và sau đó kết xuất trang sẽ hoàn tất.

    Ý nghĩa của mỗi phương thức trong quá trình khởi tạo như sau:

    • Contructor là điểm bắt đầu của vòng đời state, và flutter sẽ tạo ra một state bằng cách gọi Createstate() của statefullwidget. Bạn có thể nhận dữ liệu cấu hình giao diện người dùng khởi tạo được chuyển bởi widget thông qua contructor. Các dữ liệu cấu hình này xác định hiệu ứng hiển thị ban đầu của widget.
    • Initstate, được gọi khi state object được chèn vào cây view. Hàm này sẽ chỉ được gọi một lần trong vòng đời state, vì vậy bạn có thể thực hiện một số công việc khởi tạo ở đây, chẳng hạn như đặt giá trị mặc định cho các biến trạng thái.
    • Didchangedependencies được sử dụng đặc biệt để xử lý các thay đổi phụ thuộc của state object. Nó sẽ được gọi bởi flutter sau khi initstate() kết thúc.
    • Build được sử dụng để xây dựng các view. Sau các bước trên, framework cho rằng state đó đã sẵn sàng, vì vậy nó gọi là Build. Trong hàm này, bạn có thể tạo một widget và trả lại nó theo dữ liệu cấu hình khởi tạo được truyền bởi widget cha và trạng thái hiện tại của state.

    Cập nhật

    Cập nhật state của widget được kích hoạt bởi ba phương pháp: setstate, didchangedependencies và didupdatewidget.

    Ý nghĩa của 3 phương thức trên:

    • Setstate: khi dữ liệu state thay đổi, hãy gọi phương thức này để nói với flutter: “dữ liệu ở đây đã thay đổi, vui lòng sử dụng dữ liệu cập nhật để xây dựng lại UI!”.
    • Didchangedependencies: sau khi sự phụ thuộc của state object thay đổi, flutter sẽ gọi lại phương thức này và sau đó kích hoạt Build. Một tình huống điển hình là khi ngôn ngữ hệ thống hoặc chủ đề ứng dụng thay đổi, hệ thống sẽ thông báo state để thực thi gọi lại phương thức didchangedependencies.
    • Didupdatewidget: khi cấu hình của một widget thay đổi, chẳng hạn như widget cha kích hoạt rebuild (nghĩa là khi state của widget cha thay đổi), hệ thống sẽ gọi hàm này.

    Sau khi ba phương thức được gọi, flutter sẽ destroy widget cũ và gọi phương thức Build để xây dựng lại widget.

    Hủy

    Việc phá hủy widget tương đối đơn giản. Ví dụ, khi một widget bị xóa hoặc một trang bị hủy, hệ thống sẽ gọi hai phương thức, deactivate và dispose, để xóa hoặc hủy widget đó.

    Cách thức gọi cụ thể như sau:

    • Khi state hiển thị của widget thay đổi, function deactivate được gọi và state tạm thời bị xóa khỏi cây view. Cần lưu ý rằng trong quá trình chuyển đổi trang, do vị trí của state object trong cây view đã thay đổi, nó cần được tạm thời loại bỏ và sau đó thêm lại để kích hoạt cấu tạo widget một lần nữa, vì vậy hàm này cũng sẽ được gọi.
    • Khi sate bị loại bỏ vĩnh viễn khỏi view, flutter gọi hàm dispose. Khi chúng ta đạt đến giai đoạn này, các widget sẽ bị phá hủy, vì vậy chúng ta có thể giải phóng tài nguyên, giải phóng bộ nhớ, v.v.

    Các phương thức này được tóm tắt trong bảng sau:

    Tên phương thứcchức năngThời gian gọiSố lần gọi
    Construction methodNhận dữ liệu cấu hình giao diện người dùng khởi tạo được chuyển bởi widgetKhi tạo state1
    initStateKết xuất khởi tạo liên quanKhi state được chèn vào cây view1
    didChangeDependenciesXử lý các thay đổi phụ thuộc hình thành stateSau initstate và khi sự phụ thuộc của đối state object thay đổi> = 1
    buildChế độ xem buildKhi state sẵn sàng để hiển thị dữ liệu> = 1
    setStateTái tạo lại viewKhi nào giao diện người dùng cần được làm mới> = 1
    didUpdateWidgetXử lý các thay đổi cấu hình widgetWidget cha setstate kích hoạt build lại widget con> = 1
    deactivateWidget đã bị xóaWidget không hiển thị> = 1
    disposeWidget bị hủyWidget bị xóa vĩnh viễn1

    Vòng đời của app

    Vòng đời của view xác định toàn bộ quá trình từ khi view loading đến khi building. Cơ chế gọi lại của nó có thể đảm bảo rằng chúng ta có thể chọn đúng thời điểm để làm đúng theo trạng thái của view. Vòng đời của một ứng dụng xác định toàn bộ quá trình từ lúc bắt đầu đến khi thoát.

    Trong quá trình phát triển của Android và IOS native, đôi khi cần thực hiện xử lý các sự kiện tương ứng với vòng đời của ứng dụng, chẳng hạn như ứng dụng đi vào foreground từ background, thoát từ foreground sang background hoặc thực hiện một số xử lý sau khi UI được vẽ hoàn tất.

    Trong bản phát triển native, bạn có thể lắng nghe vòng đời của ứng dụng và thực hiện các xử lý tương ứng bằng cách override activity, phương thức gọi lại vòng đời của viewcontroller hoặc đăng ký các thông báo liên quan của ứng dụng. Trong flutter, chúng ta có thể sử dụng lớp widgetsbindingobserver để thực hiện các yêu cầu tương tự.

    Các hàm gọi lại cụ thể trong widgetsbindingobserver như sau:

    Hãy thử điều này để chuyển đổi giữa các giai đoạn trước và sau và quan sát trạng thái của đầu ra ứng dụng từ bảng console. Bạn có thể thấy:

    • Từ background đến foreground, vòng đời của ứng dụng được in trên bảng console thay đổi như sau: appfecyclestate.paid -> appfecyclestate.inactive -> appfecyclestate.resumed;
    • Khi quay trở lại từ foreground trở lại background, vòng đời của ứng dụng được in trên console sẽ thay đổi thành: appfecyclestate.resumed -> appfecyclestate.inactive -> appfecyclestate.paused

    Frame draw callback

    Ngoài việc lắng nghe gọi lại vòng đời của ứng dụng để thực hiện xử lý tương ứng, đôi khi bạn cần thực hiện một số thao tác liên quan đến bảo mật hiển thị sau khi kết xuất thành phần.

    Widgetsbinding cung cấp hai cơ chế: single frame drawing callbackreal-time frame drawing callback để đáp ứng cho các nhu cầu khác nhau:

    • A single frame drawing callback được thực hiện thông qua addpostframecallback. Nó sẽ gọi lại sau khi frame hiện tại được vẽ và chỉ một lần. Nếu muốn lắng nghe lại, bạn cần thiết lập lại.
    • Real time frame drawing callback được thực hiện thông qua addpersistentframecallback. Chức năng này sẽ gọi lại sau mỗi bản vẽ khung hình, có thể được sử dụng như phát hiện FPS.
  • Concurrency in Dart

    1. Tổng quan.

                Dart hỗ trợ chương trình chạy đồng thời với async-await, isolates and các class như Future và Stream.

                Với một ứng dựng, tất cả code của Dart chạy trong main isolate. Mỗi isolate có một thread và sẽ không chia sẻ các đối tương có thể biến đổi với các isolates khác. Các isolate truyền đạt thông tin với nhau thông qua message.

                Nhiều Dart app chỉ sử dụng main isolate, nhưng chúng ta có thể tạo thêm các isolate cho phép thực thi đồng thời nhiều event trên nhiều lõi bộ xử lý.

    2. Các loại và cú pháp của bất đồng bộ.

    2.1. Future and Stream types

                Ngôn ngữ và các thư viện Dart sử dụng các đối tượng Future và Stream để các giá trị tương ứng sẽ được cung cấp trong tương lai. Ví dụ, tương ứng với giá trị kiểu int là Future<int> và Stream<int>.

                Tại sao quan trọng phương thức là đồng bộ hay bất đồng bộ? Điều này quan trọng bởi vì hầu hết các app sẽ cần thực hiện nhiều công việc trong cùng một thời gian. Ví dụ: một ứng dụng có thể bắt đầu một yêu cầu HTTP, nhưng cần cập nhật màn hình của ứng dụng đó hoặc phản hồi thông tin đầu vào của người dùng trước khi yêu cầu HTTP hoàn tất. Phương thức bất đông bộ sẽ giúp ứng dụng luôn phản hồi.

    2.2. Async-await

                Async-await cung cấp một các khai báo để xác định các hàm không đồng bộ và sử dụng kết quả của chúng.

    Trong ví dụ trên, hàm main () sử dụng từ khóa await phía trước _readFileAsync() để cho phép mã Dart khác (chẳng hạn như trình xử lý sự kiện) sử dụng CPU trong khi native code (file I/O ) thực thi. Việc sử dụng await cũng có tác dụng chuyển đổi Future < String > được trả về bởi _readFileAsync() thành String. Kết quả là, biến nội dung có kiểu ngầm định là String.

    3. Cách các isolate hoat động.

    Bằng cách sử dụng các isolate, code Dart có thể thực hiện nhiều tác vụ độc lập cùng một lúc, sử dụng các lõi xử lý bổ sung nếu chúng có sẵn. Các isolate giống như các luồng hoặc quy trình, nhưng mỗi isolate có bộ nhớ riêng, một luồng duy nhất chạy một vòng lặp.

    3.1. Main isolate

    Một ứng dụng Dart điển hình thực thi tất cả mã của nó trong main isolate của ứng dụng, như thể hiện trong hình sau:

    Ngay cả các chương trình single-isolate cũng có thể thực thi trơn tru bằng cách sử dụng async-await để đợi các hoạt động không đồng bộ hoàn tất trước khi tiếp tục sang công việc tiếp theo. Một ứng dụng hoạt động tốt sẽ bắt đầu nhanh chóng, truy cập vào vòng lặp sự kiện càng sớm càng tốt. Sau đó, ứng dụng sẽ phản hồi nhanh chóng với từng sự kiện được xếp hàng đợi, sử dụng các hoạt động không đồng bộ nếu cần.

    3.2. Vòng đời của isolate

    Như hình trên cho thấy, mọi isolate bắt đầu bằng cách chạy một số code Dart, chẳng hạn như hàm main(). Code Dart này có thể đăng ký một số trình xử lý sự kiện – để phản hồi thông tin đầu vào của người dùng hoặc file I/O như hình. Khi function khởi tạo của isolate quay trả lại, isolate vẫn tồn tại nếu nó cần xử lý các sự kiện tiếp theo. Sau khi xử lý các sự kiện, isolate sẽ thoát ra.

    3.3. Xử lý sự kiện.

    Trong ứng dụng, hàng đợi sự kiện của main isolate có thể chứa các yêu cầu repaint và các thông báo của tap và các sự kiện giao diện người dùng khác. Ví dụ: hình sau cho thấy một sự kiện repaint, tiếp theo là tap event, tiếp theo là hai sự kiện repaint. Vòng lặp sự kiện nhận các sự kiện từ hàng đợi theo thứ tự xuất trước, nhập trước.

    Nếu một hoạt động đồng bộ mất quá nhiều thời gian xử lý, ứng dụng có thể không phản hồi. Trong hình dưới, sự kiện tap mất quá nhiều thời gian, vì vậy các sự kiện tiếp theo được xử lý quá muộn. Ứng dụng có vẻ như bị đóng băng và bất kỳ hoạt ảnh nào mà nó thực hiện có thể bị giật (jerky). Tệ hơn, giao diện người dùng có thể trở nên hoàn toàn không phản hồi.

    3.4. Background workers

                Nếu giao diện người dùng của ứng dụng của bạn không phản hồi do tính toán tốn nhiều thời gian như: parsing a large JSON file, hãy xem xét tách ra sử dụng worker isolate, thường được gọi là background worker. Một trường hợp phổ biến, được hiển thị trong hình sau, là sinh ra worker isolate đơn thực hiện tính toán và sau đó thoát ra. Worker isolate trả về kết quả của nó trong một thông báo khi isolate thoát ra.

    Mỗi isolate message có thể gửi một đối tượng, bao gồm bất kỳ thứ gì có thể truy cập chuyển tiếp từ đối tượng đó. Không phải tất cả các loại đối tượng đều có thể gửi được và việc gửi không thành công nếu bất kỳ đối tượng có thể truy cập chuyển tiếp nào không thể gửi được. Ví dụ: bạn chỉ có thể gửi một đối tượng kiểu List<Object> nếu không có đối tượng nào trong danh sách là không thể điều chỉnh được. Nếu một trong các đối tượng là Socket, thì quá trình gửi không thành công vì các socket không thể gửi được.

    Note: Trong Flutter chúng ta có thể sử dụng function compute() để lấy giá trị trong tương lai tương ứng, tuy nhiên cần lưu ý là  đối số gọi lại phải là một hàm cấp cao nhất, không phải là một closure hoặc một instance hoặc phương thức tĩnh của một lớp.

    4. Ví dụ:

    Hàm _parseInBackground() chứa code sinh ra (tạo và bắt đầu) vùng worker isolate, sau đó trả về kết quả:

    1. Trước khi tạo ra worker isolate, khởi tạo ra một cổng nhận (Receiver), cho phép worker isolate gửi tin nhắn đến main isolate.
    2. Tiếp theo là lệnh gọi Isolate.spawn(), tạo và bắt đầu isolate cho background worker. Đối số đầu tiên của Isolate.spawn() là hàm mà worker isolate thực thi: _readAndParseJson. Đối số thứ hai là SendPort mà worker isolate có thể sử dụng để gửi tin nhắn đến main isolate. Không khởi tạo SendPort; nó sử dụng thuộc tính sendPort của ReceivePort.
    3. Sau khi isolate được sinh sản, main isolate sẽ đợi kết quả. Bởi vì lớp GetPort triển khai Stream, thuộc tính đầu tiên là con đường dễ dàng để lấy một giá từ worker isolate.

    Code:

    Mô hình làm việc của ví dụ trên:

    5. Tổng kết.

    Trên đây là một số chia sẻ về việc sử dụng Concurrency trong Dart nhằm tăng hiệu suất cho ứng dựng. Mong rằng qua bài viết sẽ giúp ích cho các bạn phần nào đó.

    Cảm ơn các bạn đã đọc bài viết của mình.

    Author: DuongVT19

  • [FLUTTER] Tổng quan về ListView

    Nếu bạn muốn có một giải pháp để hiển thị một danh sách các Widget và thậm chí có thể cuộn được theo chiều ngang hay dọc thì ListView chính là một lựa chọn vô cùng hiệu quả.

    Dưới đây là một số cách để xây dựng một ListView:

    Sử dụng List<Widget> cho thuộc tính children

    Đây là cách xây dựng dựng mặc định của một ListView. Bằng cách xây dựng từng Widget cụ thể và đặt trong children của ListView, các Widget sẽ được hiển thị lần lượt theo trên giao diện người dùng.

    Cách xây dựng này phù hợp với việc hiển thị một số lượng ít các Widget vì việc xây dựng một List yêu cầu cần phải làm việc với tất cả các thành phần con có thể được hiển thị kể cả khi các Widget chưa hiển thị lên màn hình.

    Sử dụng ListView.builder

    Đây là cách xây dựng ListView được áp dụng cho trường hợp cần hiển thị một lượng lớn (hay vô hạn) các Widget con vì builder chỉ được gọi cho những Widget thực sự được hiển thị lên màn hình.

    Giả sử ta có một mảng dữ liệu 100 phần tử từ 1 đến 100
    Ta có thể thay đổi số lượng item được hiển thị qua thuộc tính itemCount

    Sử dụng ListView.separated

    Đây là cách xây dựng ListView được áp dụng cho trường hợp khi cần hiển thị một số lượng lớn các Widget con và các Widget dùng để ngăn cách giữa các Widget đó vì builder chỉ được gọi cho những Widget thực sự được hiển thị lên màn hình.

    Giả sử ta có một mảng dữ liệu 100 phần tử từ 1 đến 100
    Ta có thể thêm Widget dùng để tách các Widget con qua separatorBuilder

    Sử dụng ListView.custom

    Đây là cách xây dựng ListView giúp bạn có thể tùy chỉnh nhiều hơn cho các model con. Ví dụ: một model con tùy chỉnh có thể kiểm soát thuật toán được sử dụng để ước tính kích thước của các mô hình con không thực sự hiển thị.

    Giả sử ta có một mảng dữ liệu 100 phần tử từ 1 đến 100
    Sử dụng SliverChildBuilderDelegate để xây dựng Widget con

    Một số thuộc tính thường được sử dụng

    padding

    ListView có khoảng cách với Widget cha. Ví dụ: padding: const EdgeInsets.all(8).

    scrollDirection

    Thuộc tính scrollDirection xác định hướng cuộn của ListView, mặc định của ListView sẽ là vertical. Ví dụ: scrollDirection: Axis.horizontal.

    reverse

    ListView sẽ được hiển thị ngược chiều, đây là thuộc tính có kiểu bool, mặc định là false.

    physics

    Thuộc tính physics giúp bạn cài đặt ListView được cuộn như thế nào. Ví dụ: physics: const NeverScrollableScrollPhysics(),

    Tài liệu tham khảo

    ListView class trong Flutter: ListView class – widgets library – Dart API (flutter.dev)

  • SỬ DỤNG INPUT DECORATION CHO FLUTTER TEXTFIELD – PHẦN 3

    Phần này sẽ nói về các kiểu border, enabled và combined effects bên trong Input Decoration của TextField Widget.

    Border

    None

    Underline

    Tạo đường viền gạch dưới cho TextField. Ta có thể sử dụng thuộc tính borderRadius bên trong thuộc tính UnderlineInputBorder này.

    Outline with border radius

    Tạo một đường viền hình chữ nhật được bo các góc cho TextField và thuộc tính borderRadius bên trong OutlineInputBorder có chức năng thay đổi độ bo tròn viền của hình chữ nhật.

    Specific border behavior

    Border là một trường hợp chung nhưng bạn cũng có thể sửa đổi điều đó cho các tình huống cụ thể thiết lập focusedBorder, enabledBorder, disabledBorder, errorBorder và focusedBorder.

    Trường hợp enabledBorder.

    Trường hợp focusedBorder.

    Filled with color

    Thuộc tính filled giúp lấp đầy vùng trang trí bằng fillColor nếu filled có giá trị là true.

    Filled with color and no underline

    Thuộc tính BorderSide.none dùng để xóa hết Border khỏi TextField.

    Ta có thể sử dụng “decoration:null” hoặc InputDecoration.collapsed để loại bỏ underline mặc định của TextField.

    Hover color

    Thuộc tính này sẽ thay đổi màu của TextField khi mà người dùng di chuyển chuột hoặc con trỏ qua TextField.

    Enabled

    Nếu là false thì helperText, errorText và counterText sẽ không được hiển thị và độ mờ của các thành phần hình ảnh còn sẽ lại sẽ giảm.

    Thuộc tính này mặc định là true.

    True

    False

    Combined effects

    None

    Dense

    Giúp cho TextField sử dụng ít khoảng trắng hơn nếu là true.

    Thuộc tính isDense có giá trị mặc định là false.

    Content padding

    Là phần khoảng cách để nội dung không bị dí sát vào viền của TextField.

    Nếu isCollapsed là True thì thuộc tính contentPadding sẽ mặc định là zero (EdgeInsets.zero).

    Nếu thuộc tính isOutline là false và thuộc tính filled là true thì thuộc tính contentPadding sẽ có giá trị là EdgeInsets.fromLTRB(12, 8, 12, 8) khi isDense là true và có giá trị là EdgeInsets.fromLTRB(12, 8, 12, 8) khi isDense là false.

    Nếu isOutline và filled là false thì contentPadding sẽ có giá trị là EdgeInsets.fromLTRB(0, 8, 0, 8) khi isDense là true và có giá trị là EdgeInsets.fromLTRB(0, 12, 0, 12) khi isDense là false.

    Tác giả: DangDH9