Tag: Flutter

  • Khác biệt giữa Future và Stream

    [FLUTTER] Sự khác biệt giữa Future và Stream trong Flutter

    Lập trình không đồng bộ trong Flutter được đặc trưng bởi hai lớp Future và Stream

    1. Future

    Khi một hàm bất đồng bộ được thực hiện xong nó sẽ trả về một Future.

    • Một hàm Future có thể trả về một giá trị.
    • Một hàm Future cũng có thể trả về một lỗi nếu có bất kì ngoại lệ nào xảy ra.
    Future<void> fetchUserOrder() {
      // Imagine that this function is fetching user info from another service or database.
      return Future.delayed(const Duration(seconds: 2), () => print('Large Latte'));
    }
    
    void main() {
      fetchUserOrder();
      print('Fetching user order...');
    }

    2. Stream

    Định nghĩa: Stream là một chuỗi các sự kiện không đồng bộ. Nó giống như một Lặp lại không đồng bộ – trong đó, thay vì nhận được sự kiện tiếp theo khi bạn yêu cầu, luồng cho bạn biết rằng có một sự kiện khi nó sẵn sàng.

    Future<int> sumStream(Stream<int> stream) async {
      var sum = 0;
      await for (final value in stream) {
        sum += value;
      }
      return sum;
    }
    
    Stream<int> countStream(int to) async* {
      for (int i = 1; i <= to; i++) {
        yield i;
      }
    }
    
    void main() async {
      var stream = countStream(10);
      var sum = await sumStream(stream);
      print(sum); // 55
    }

    3. Khác biệt giữa Future và Stream

    Như chúng ta có thể thấy ở trên thì điểm khác biệt rõ rệt nhất của Future và Stream là:

    • Trong quá trình xử lý Future sẽ chờ đợi đến khi hoàn thành và chỉ trả lại kết quả tại thời điểm đó.
    • Stream thì sẽ trả về dữ liệu liên tục nếu nó vẫn tiếp tục được chuyển về, tạo thành một luồng.

    Từ những sự khác biệt này chúng ta có thể suy luận ra các trường hợp nào nên sử dụng Future, trường hợp nào nên sử dụng Stream.

    Future:

    • Chụp một bức ảnh từ camera, hoặc lấy từ trong bộ nhớ.
    • Lẩy các thông tin về file.
    • Tạo các https request.

    Stream:

    • Lắng nghe sự thay đổi của vị trí.
    • Chơi nhạc.
    • Đồng hồ bấm giờ.
    • Làm việc với web-socket.

    Hãy chọn cho mình những cách phù hợp nhất với các vấn đề chúng ta cần giải quyết.

    Tài liệu tham khảo:

    Author: LamNT59

  • Widget Tree & Element Tree & Render Tree trong Flutter

    Widget Tree & Element Tree & Render Tree trong Flutter

    Flutter không vẽ lại hay tạo lại toàn bộ giao diện người dùng mỗi khi ta phương thức build(){...} được gọi.

    • Flutter cố gắng đáp ứng để ứng dụng chạy ở 60 FPS. Vì vậy, nó cập nhật màn hình 60 lần mỗi giây. Có nghĩa là màn hình được Flutter repaint lại 60 lần mỗi giây. Điều này dễ hiểu vì tất cả các ứng dụng và trò chơi chạy ở tốc độ 60 FPS trở lên theo mặc định.‌‌‌‌
    • Điều này sẽ chỉ trở nên kém hiệu quả nếu Flutter cứ phải tính toán lại toàn bộ bố cục 60 lần mỗi giây.
    • Nếu Flutter vẽ thứ gì đó lên màn hình lần đầu tiên, nó cần tính toán ra vị trí, màu sắc, văn bản, v.v. của mọi phần tử trên màn hình.
    • Đối với các lần sửa/vẽ tiếp theo, để làm mới giao diện người dùng nếu không có gì thay đổi thì Flutter sẽ lấy thông tin cũ mà nó đã có từ trước đó và vẽ thông tin đó lên màn hình rất nhanh và rất hiệu quả.‌‌ Từ đó, tốc độ vẽ lại không phải là vấn đề, sẽ chỉ là vấn đề nếu Flutter phải tính toán lại mọi thứ trên màn hình với mỗi lần làm mới mà thôi.

    Đây là những gì chúng ta sẽ thảo luận chi tiết ở bài viết này: liệu Flutter có tính toán lại mọi thứ mỗi khi phương thức build(){...} được gọi hay không?

    Widget Tree

    • Widget Tree chỉ đơn giản là tất cả các Widget mà ta đang dùng để xây dựng ứng dụng, tức là tất cả code mà ta viết sẽ tạo nên widget tree.‌‌
    • Nó hoàn toàn do ta kiểm soát. Bạn khai báo các widget lồng ghép chúng lại với nhau để tạo nên giao diện mong muốn.‌‌
    • Widget tree được xây dựng bởi Flutter khi call phương thức build(){...} từ code của chúng ta, chúng chỉ là một loạt các cài đặt cấu hình mà Flutter sẽ xử lý.‌‌
    • Nó không chỉ đơn giản xuất hiện ra trên màn hình rồi thôi. Thay vào đó, nó sẽ cho Flutter biết những gì sẽ vẽ lên màn hình ở lần tiếp theo. Widget tree được rebuild rất thường xuyên.‌‌

    Element Tree

    • Element Tree liên kết vào Widget Tree, là thông tin được thiết lập với các đối tượng/phần tử thực sự được hiển thị. Nó rất hiếm khi rebuild.‌‌
    • Element Tree được quản lý theo một cách khác và sẽ không rebuild khi phương thức build(){...} được gọi. ‌‌
    • Ở mỗi Widget trong Widget Tree, Flutter sẽ tự động tạo một element cho nó. Nó được thực hiện ngay khi Flutter xử lý Widget ở lần đầu tiên.‌‌
    • Ở đây chúng ta có thể nói rằng một element là một đối tượng được quản lý trong bộ nhớ bởi Flutter, nó có liên quan đến Widget trong Widget Tree.‌‌
    • Element chỉ giữ một tham chiếu tới Widget (trong Widget Tree) đang giữ các thông số giao diện đầu cuối.‌‌‌‌

    Tóm lại

    Khi Flutter nhìn thấy stateful widget, nó sẽ tạo element và sau đó cũng gọi phương thức createState() để tạo một state object mới dựa trên state class.‌‌ Do đó, state là một đối tượng độc lập trong một Stateful Widget được kết nối với cả hai là element trong Element Tree và Widget trong Widget Tree.‌‌

    Render Tree

    • Render Tree đại diện của các element/đối tượng thực sự được hiển thị trên màn hình.‌‌
    • Render Tree cũng không rebuild thường xuyên.
    • Element Tree cũng được liên kết với Render Tree. Element trong Element Tree trỏ đến render object mà chúng ta thực sự thấy trên màn hình.‌‌
    • Bất cứ khi nào Flutter thấy một element chưa được render trước đó thì nó sẽ tham chiếu đến Widget trong Widget Tree để thiết lập, sau đó tạo một element trong element tree.
    • Flutter cũng có một layout phase, giai đoạn mà nó tính toán và lấy không gian diện tích có sẵn trên màn hình, chiều, kích thước, hướng, ….‌‌
    • Nó cũng có một phase khác để thiết lập các listeners với các Widget để chúng ta có thể thao tác các sự kiện, ….

    Tóm lại

    Một cách đơn giản, chúng ta có thể thấy rằng phần tử chưa được render thì sẽ được render ra màn hình. Element (trong element tree) sau đó có có một con trỏ đến Render Object (trong Render Tree) trên màn hình. Nó cũng có một con trỏ tới Widget (trong Widget Tree) mang theo các thông tin cấu hình cho phần tử này.

    Cách Flutter thực thi phương thức build(){...}‌‌

    Phương thức build(){...} được Flutter gọi bất cứ khi nào state thay đổi. Về cơ bản, có hai kích hoạt quan trọng có thể dẫn đến việc rebuild.‌‌

    • Một là khi phương thức setState(){...} được gọi trong một Stateful Widget. Việc call setState(){...} khi được gọi tự động sẽ dẫn đến phương thức build(){...} được gọi ngay sau đó.‌‌
    • Thứ hai, bất cứ khi nào MediaQuery hoặc lệnh Theme.of(...) được gọi, bàn phím ảo xuất hiện hoặc biến mất, v.v. Bất cứ khi nào dữ liệu của những thứ này thay đổi, nó sẽ tự động kích hoạt phương thức build(){...}.‌‌

    Chính xác là việc gọi setState(){...} sẽ đánh dấu phần tử tương ứng là dirty. Đối với lần render tiếp theo, diễn ra 60 lần mỗi giây, Flutter sau đó sẽ xem xét đến các thông tin thiết lập mới được tạo bởi phương thức build(){…} và sau đó cập nhật màn hình.‌‌

    Tất cả các Widget lồng vào nhau bên trong Widget được đánh dấu là dirty sẽ được tạo các đối tượng mới widget/dart cho chúng. Do đó, một Widget Tree mới sẽ được tạo ra tương ứng các phiên bản mới của tất cả các Widget này.‌‌

    Chú thích

    Một số Widget sẽ không bao giờ thay đổi ngay cả khi rebuild Widget Tree nên bạn có thể tối ưu hóa quá trình build hơn. Bạn có thể sử dụng từ khóa const phía trước chúng để cho Flutter biết rằng Widget này sẽ không bao giờ thay đổi, do đó làm cho Flutter bỏ qua việc rebuild hoàn toàn widget đó.

    Ví dụ trên ta chỉ định const bỏ qua việc rebuild Padding Widget.

    Tác giá: DangDH9

  • Làm thế nào để chuyển màn hình mà không cần context trong Flutter?

    Làm thế nào để chuyển màn hình mà không cần context trong Flutter?

    Làm thế nào để chuyển màn hình mà không cần context trong Flutter?

    Chúng ta sẽ học cách loại bỏ context khi navigate trong Flutter nhé.

    Navigate là một phần không thể thiếu trong bất kì ứng dụng nào. Flutter sẽ hỗ trợ bạn navigate đến bất cứ màn hình nào một cách dễ dàng hơn chỉ với việc sử dụng các chức năng navigate đơn giản như Push và Pop.

    Để Push:

    Để Pop:

    Điều này hoạt động khá tốt cho đến khi ứng dụng của bạn mở rộng quy mô và bạn tách logic nghiệp vụ của mình với logic UI. Và bây giờ bạn phải chuyển BuildContext từ một function này sang một function khác. Đôi khi việc này sẽ trở nên rắc rối khiến bạn muốn tránh việc chuyển context.

    Đừng lo lắng nhé vì NavigatorKey sẽ giải cứu bạn.

    1. Đầu tiên, hãy tạo Navigator Key

    2. Tiếp đến, hãy chuyển Navigator Key trong MaterialApp

    3. Cuối cùng, hãy push bằng Navigator Key

    Vậy là xong, chỉ với 3 bước đơn giản, bạn đã có thể loại bỏ context ra khỏi Navigation của mình. Bạn có thể kiểm tra code đầy đủ trên Github.

    Bài viết được tham khảo từ Divyanshu Bhargava.

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

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

    Ở số trước mình đã giới thiệu một vài widget hữu ích, anh em có thể tìm đọc lại tại đây.

    Sang phần 2 này, mình sẽ tiếp tục giới thiệu đến anh em một vài widget ít phổ biến nhưng cũng khá thú vị. Bắt đầu ngay nhé 😀

    1. Chip widgets

    Đây là một loạt các widget có hình dạng mặc định là hình chữ nhật được bo tròn bốn góc (Stadium shape), có thể có avatar đằng trước để thể hiện hình ảnh. Vì có khá nhiều loại nên mình sẽ không đi sâu cụ thể mà chỉ giới thiệu qua cách sử dụng của chúng.

    Chip widget
    • Chip: Đây là loại cơ bản nhất, chỉ đơn giản thể hiện thông tin, kèm theo avatar đằng trước nếu cần.
    • ActionChip: Widget này có thêm thuộc tính onPressed nên có thể click được. Đây có thể là một lựa chọn thay thế cho ElevatedButton, TextButton hay OutlinedButton. Nhưng cần lưu ý là widget này sẽ không có trạng thái disabled (tức thuộc tính onPressed không nhận giá trị null), cho nên widget này được khuyên dùng cho các item không cố định trong UI
      => use case sử dụng: khi người dùng tìm kiếm thông tin, dùng widget này để đưa ra cho người dùng các kết quả gợi ý.
    • FilterChip: Cũng giống như ActionChip nhưng có thêm thuộc tính selected, widget này được sử dụng như CheckBox hay Switch, phù hợp khi đặt filter tìm kiếm thông tin. Ví dụ tại đây
    FilterChip
    • ChoiceChip: Gần tương tự FilterChip nhưng sử dụng khi muốn chọn 1 trong nhiều lựa chọn.
    • InputChip: Là sự kết hợp của những loại Chip bên trên, phù hợp khi thực hiện những tác vụ phức tạp.

    2. ResizeImage

    Thực ra đây cũng không hẳn là một widget nhưng nó khá là hay nên mình cũng xin giới thiệu cho những ai chưa biết 😀

    Đôi khi anh em muốn thêm ảnh vào trong ứng dụng của mình để cho thêm sinh động, nhưng nếu ở những vị trí nhỏ mà chúng ta thêm những ảnh có size lớn vào thì sẽ khá là lãng phí bộ nhớ. ResizeImage sẽ giúp chúng ta giảm thiểu điều này

    Cách sử dụng khá đơn giản, ResizeImage là một ImageProvider và cũng nhận vào một ImageProvider nên ta có thể sử dụng như sau:

    Image(
        image: ResizeImage(
          NetworkImage(imageUrl)),
         ),

    Như vậy là chúng ta có thể tiết kiệm được một chút bộ nhớ rồi 😀

    3. SwitchListTile

    Hầu hết các ứng dụng đều sẽ có phần cài đặt ứng dụng, và ở trong đó đôi khi sẽ có những lựa chọn theo kiểu bật / tắt, chúng ta sẽ nghĩ ngay đến widget Switch phải không nào?

    Nhưng với góc nhìn của một người dùng, việc phải click đúng vào ô switch bé tí để có thể sử dụng khiến ta đôi khi cảm thấy hơi bất tiện. Đừng lo vì chúng ta đã có SwitchListTile :)))

    Có thể hiểu nôm na đây chính là sự kết hợp của ListTile và Switch, chúng ta có thể click lên mọi điểm trong widget này để có thể kích hoạt nút switch, như vậy trải nghiệm người dùng (UX) sẽ được cải thiện thêm 😀

    Anh em có thể xem ví dụ tại đây.

    SwitchListTile

    Lưu ý: CheckBox hay Radio cũng có lựa chọn tương đương là CheckBoxListTileRadioListTile

    Mình xin được phép kết thúc phần 2 tại đây, mong rằng những thông tin mình chia sẻ sẽ giúp ích được anh em trong quá trình làm việc 😀

  • SQLite trong Flutter

    [FLUTTER] Tìm hiểu về cách sử dụng SQLite trong Flutter

    Để lưu những dữ liệu theo dạng bảng dễ dàng cho việc truy vấn và thay đổi, chúng ta cần sử dụng SQLite

    I. Khái quát về Sqlite trong Flutter

    Trong Flutter chúng ta sử dụng thư viện sqflite, thư viện này sẽ hỗ trợ chúng ta:

    • Sử dụng trên đa nền tảng Android, iOS, MacOS.
    • Hỗ trợ transactions và batches.
    • Tự động quản lý phiên bản (version) trong khi mở.
    • Hỗ trợ các câu lệnh truy vấn CRUD (Create – Read – Update – Delete) đầy đủ.
    • Thực hiện các xử lý trên background của iOS và Android.

    II. Cách sử dụng Sqlite trong Flutter

    1. Thêm thư viện

    1.1. Thêm thư viện sqflite path (dùng để xác định vị trí lưu database trong bộ nhớ) vào phần dependencies trong file pubspec.yaml

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

    2.1. Đầu tiên chúng ta cần tạo một class model Student, đây sẽ là dữ liệu chúng ta dùng trong quá trình lưu trữ.

    class Student {
      int id;
      String name;
      int grade;
    
      Student({required this.id, required this.name, required this.grade});
    }

    Nếu bạn chưa biết rõ về Sql cũng như các câu lệnh của chúng, hãy tìm hiểu về chúng trước khi tiếp tục: SQL Introduction (w3schools.com)

    2.2. Tiếp theo chúng ta khởi tạo database

    class StudentDatabase {
      static Database? _database;
    
      static Future<Database> getInstance() async {
        _database ??= await openDatabase(
    
            /// use join to create path for db, then the path will be path/student.db
            join(await getDatabasesPath(), "student.db"),
    
            /// This function will be called in the first time database is created
            onCreate: (db, version) {
          return db.execute(
              "CREATE TABLE student(id INTEGER PRIMARY KEY, name TEXT, grade INTEGER)");
        },
    
            /// This version will use when you want to upgrade or downgrade the database
            version: 1,
            singleInstance: true);
        return _database!;
      }
    }
    • Hàm onCreate() được gọi tại lần đầu mà database được khởi tạo
    • Version chính là phiên bản của database, nếu bạn muốn thay đổi cấu trúc của database thì chúng ta phải thay đổi version.

    Tiếp theo ta thêm các hàm phục vụ cho việc chuyển đổi dữ liệu khi muốn thêm vào database cũng như lấy dữ liệu ra từ database

    class Student{
    ...
    
    Map<String, dynamic> toMap() => {'id': id, 'name': name, 'grade': grade};
    
    factory Student.fromMap(Map<String, dynamic> map) {
      return Student(id: map['id'], name: map['name'], grade: map['grade']);
    }
    ...
    }

    Ở đây mình có viết thành một project CRUD, với các phân tần giữa data và UI rõ ràng, để có thể hiểu hơn về project này hãy đọc đến cuối bài viết.

    2.3. Thêm một trường dữ liệu vào bảng

    Future<DataResult> insertStudent(Student student, String tableName) async {
        try{
          Database db = await StudentDatabase.getInstance();
          int lastInsertedRow = await db.insert(tableName, student.toMap(),
              conflictAlgorithm: ConflictAlgorithm.replace);
          return DataResult.success(lastInsertedRow);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }
    • Hàm insert() có hai tham số truyền vào là tên của bảng và dữ liệu chúng ta muốn thêm vào bảng, dữ liệu này đã được chuyển thành một map để có thể thực hiện thêm vào database.
    • Tham số conflictAlgorithm: được sử dụng để xác định các xử lý khi có sự trùng lặp dữ liệu xảy ra, ở đây tham số này có giá trị là ConflictAlgorithm.replace có nghĩa là nếu trùng primary key thì giá trị cũ sẽ được thay thế bằng giá trị mới.
    • Nếu hàm insert thực hiện thành công sẽ trả về id của hàng vừa được thêm.
    • Nếu không thành công sẽ trả về giá trị 0.

    2.4. Lấy thông tin của tất cả các trường

    Trong Android Studio có một công cụ hỗ trợ chúng ta xem được các cơ sở dữ liệu đang có trong app, đó chính là App Inspection

    Tại đây chúng ta có thể thấy được bảng “student” đã được thêm 33 trường dữ liệu, vậy bây giờ chúng ta muốn lấy tất cả những dữ liệu này ra thì phải làm thế nào?

    Future<DataResult> getAllStudent() async{
        try{
          Database db = await StudentDatabase.getInstance();
          final List<Map<String,dynamic>> maps = await db.query("student");
          List<Student> students = maps.map((e) => Student.fromMap(e)).toList();
          return DataResult.success(students);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }

    Chúng ta chỉ cần sử dụng hàm query, truyền vào tham số là bảng mà chúng ta cần lấy dữ liệu.

    • Dữ liệu trả về sẽ ở dạng List<Map<String,dynamic>>, vì vậy chúng ta cần chuyển đổi chúng sang dạng của lớp Student sử dụng hàm fromMap() đã được thêm trong class Student

    2.5. Thay đổi thông tin của một trường.

    Future<DataResult> updateStudent(Student student) async{
        try{
          Database db = await StudentDatabase.getInstance();
          int numberOfChanges = await db.update("student", student.toMap(),where: "id = ?",whereArgs: [student.id]);
          return DataResult.success(numberOfChanges);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }

    Để thực hiện được việc thay đổi dữ liệu của một trường chúng ta cần sử dụng hàm update()

    • Các tham số truyền vào gồm có tên bảng, dữ liệu thay đổi (được chuyển sang map), điều kiện thay đổi và tham số cho điều kiện đó.
    • Có thể thấy phần thay đổi dữ liệu này khá giống với phần thêm dữ liệu, chỉ khác chúng ta có thêm hai tham số khá quan trọng là “where” và “whereArgs”.
    • Giá trị trả về của hàm update() là số lượng thay đổi đã diễn ra.

    2.6. Xóa thông tin của một trường

    Future<DataResult> deleteStudent(int id) async{
        try{
          Database db = await StudentDatabase.getInstance();
          int numberOfRowEffected = await db.delete("student",where: "id = ?",whereArgs: [id]);
          return DataResult.success(numberOfRowEffected);
        }catch(ex){
          return DataResult.failure(DatabaseFailure(ex.toString()));
        }
      }

    Để thực hiện việc xóa dữ liệu chúng ta sẽ cần dùng đến hàm delete()

    • Gần giống với hàm update chúng ta cũng cần có hai tham số “where” và “whereArgs”
    • Giá trị trả về sẽ là hàng bị thay đổi giá trị.

    III. Tổng kết

    Phần Sql này khá là khó và khô khan đối với những người mới học, mới tiếp cận, vì vậy mọi người cần luyện tập thêm để trở nên master phần này nhé.

    Link github của project mình tự làm cho mọi người tham khảo: lamdev99/sqflite_crud (github.com)

    Tài liệu tham khảo:

    Author: LamNT59

  • 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

  • [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] 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)

  • Cách viết Unit Test trong Flutter

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

    Lời ngỏ

    Unit Test là một phần rất quan trọng trong quá trình phát triển phần mềm, tuy nhiên nó thường xuyên bị lãng quên với một lập trình viên mới vào nghề hoặc chưa có nhiều kinh nghiệm. Mong rằng bài viết sẽ giúp bạn có cái nhìn trực quan hơn về Unit Test tron phát triển phầm mềm, đặc biệt là trong Flutter.

    A. Đôi điều về Unit Test

    1. Unit Test là gì?

    Unit Test là gì? Tìm hiểu chi tiết về Unit Test
    1.1 Ảnh unit test

    Unit Test là một loại kiểm thử phần mềm trong đó các đơn vị hay thành phần riêng lẻ của phần mềm được kiểm thử. Kiểm thử đơn vị được thực hiện trong quá trình phát triển ứng dụng. Mục tiêu của Kiểm thử đơn vị là cô lập một phần code và xác minh tính chính xác của đơn vị đó.

    Nếu khái niệm trên vẫn còn khá khó hiểu thì hãy thử tách nghĩa từng từ ra một nhé:

    • Unit là một thành phần Phần mềm nhỏ nhất mà ta có thể kiểm tra được như các hàm (Function), thủ tục (Procedure), lớp (Class), hoặc các phương thức (Method).
    • Test thì là kiểm thử, kiểm tra tính chính xác của một cái gì đó.

    Đến đây thì chắc các bạn cũng đã có cho mình một chút khái niệm về Unit Test rồi đúng không nhỉ.

    Thường các lập trình viên khi nghe về một khái niệm mà trong đó có từ “Test” thì điều mọi người sẽ nghĩ đến ngay đó là “Test là công việc của Tester đâu phải việc của mình nên mình không cần quan tâm :v”.

    Nhưng KHÔNG, các bạn đã nhầm to. Unit Test sẽ phải được viết bởi các lập trình viên, bởi chính những người viết ra những dòng code đó.

    2. Vòng đời của Unit Test

    Vòng đời của Unit Test gồm 3 giai đoạn:

    • Fail (trạng thái lỗi).
    • Ignore (tạm ngừng thực hiện).
    • Pass (trạng thái làm việc).

    Ba giai đoạn này sẽ thay phiên nhau làm việc khi một Unit Test được chạy tự động.

    3. Unit Test quan trọng không và khi nào thì cần viết Unit Test?

    Spring Break

    Unit Test là một phần không thể thiếu trong quá trình phát triển phần mềm. Unit Test đem lại cho chúng ta rất nhiều lợi ích:

    • Tạo ra một môi trường để kiểm tra bất kỳ đoạn code nào, duy trì sự ổn định của phần mềm. Unit Test giúp chúng ra kiểm tra những kết quả trả về mong muốn cũng như những ngoại lệ mong muốn.
    • Phát hiện các lỗi, các xử lý không hiệu quả trong code, các vấn đề về thiết kế.
    • Việc viết Unit Test có thể coi như việc tạo một người dùng đầu tiên cho ứng dụng, từ đó chúng ta có thể biết được những vấn đề mà trong quá trình sử dụng ứng dụng người dùng có thể gặp phải.
    • Giúp cho quá trình phát triển phần mềm trở nên nhanh hơn, số lượng test case khi được test cũng sẽ pass nhiều hơn. Điều này giúp cho các bộ phận khác như QA, Tester làm việc sẽ nhàn hơn. Và trên hết đối với những coder chúng ta, việc ít phải đối mặt với Tester cũng làm cho buổi làm việc “bớt sóng gió” hơn đúng không nào?

    Note

    Viết Unit Test càng sớm càng tốt trong giai đoạn viết code và xuyên suốt chu kỳ Phát triển phần mềm.

    4. Như nào là một Unit Test có giá trị?

    Muốn viết một Unit Test hiệu quả, đem lại nhiều lợi ích nhất cho bản thân cũng như dự án thì cần chú ý những điểm sau:

    • Unit Test chạy nhanh, sử dụng dữ liệu dễ hiểu, dễ đọc.
    • Hãy làm cho mỗi test độc lập với những phần khác. Mỗi test chỉ nên liên quan đến một hàm, thủ tục, … Điều này sẽ giúp chúng ta dễ dàng hơn trong quá trình quản lý unit test, cũng như đáp ứng được các thay đổi trong code.
    • Giả lập tất cả các dịch vụ và trạng thái bên ngoài. Ví dụ: Nếu bạn có làm việc với Database, thì KHÔNG nên sử dụng database thật của ứng dụng để viết Unit Test, bởi vì giá trị trong đó sẽ có thể thay đổi và ảnh hưởng đến các kết quả mong đợi của bạn. Thay vào đó hay tự vào cho mình một fake database, với dự liệu có sẵn và chỉ sử dụng các hàm, thủ tục để làm việc với nó.
    • Nên đặt tên các đơn vị kiểm thử rõ ràng và nhất quán với nhau để đảm bảo rằng test case dễ đọc. Để bất kỳ ai cũng có thể khởi chạy test case mà không gặp phải trở ngại.
    • Triển khai Unit Test bao quát hết tất cả các ngoại lệ, các test case.

    B. Cách triển khai Unit Test trong Flutter (Còn tiếp)

    Nguồn tham khảo:

    Tác giả: LamNT59