Month: April 2022

  • [Flutter] Kiến trúc MVVM

    [Flutter] Kiến trúc MVVM

    Kiến trúc MVVM là gì?

    MVVM (Model View ViewModel) là một kiến ​​trúc thiết kế tạo điều kiện cho việc chia nhỏ các nguyên tắc thiết kế, thường được gọi là sự tách biệt phát triển của những người nghiệp dư về công nghệ, thành các phần riêng biệt của GUI. Tư tưởng cơ bản đằng sau việc triển khai các phương pháp hay nhất về kiến ​​trúc ứng dụng dành cho thiết bị di động là xây dựng “View Model” có thể đại diện cho dữ liệu thông qua một chế độ xem.

    Lý do tại sao phần lớn các nhà phát triển, nói đến thiết kế ứng dụng Android hoặc iOS, ưa thích MVVM là vì nó tách biệt Activity và Fragment  khỏi logic. Để viết một codebase linh hoạt, các nhà phát triển ứng dụng bắt buộc phải xây dựng một lớp View-Model có thể được sử dụng bởi một số ‘Views’ nhất định. Hơn nữa, kiến ​​trúc MVVM cũng giúp các nhà phát triển tự động hóa việc truyền các sửa đổi bên trong View-Model tới Views.

    Ba thành phần quan trọng của kiến ​​trúc MVVM trong Flutter

    Mẫu thiết kế MVVM được quản lý bởi ba thành phần chính là Model, View và ViewModel . Các yếu tố chính này giúp thiết lập một khối cho toàn bộ cơ sở mã của mẫu thiết kế MVVM. Mặc dù mỗi thành phần có vai trò và chức năng khác nhau, nhưng sự tương tác logic giữa các thành phần này trong quá trình phát triển ứng dụng đóng một vai trò quan trọng. Trong kiến ​​trúc MVVM, khung nhìn tương tác với mô hình khung nhìn để liên kết dữ liệu và mô hình khung nhìn giao tiếp với mô hình. Để hiểu tại sao MVVM lại quan trọng đối với dự án ứng dụng của bạn, điều cần thiết là phải chăm chỉ nhận thức từng yếu tố. Vì vậy, chúng ta hãy bắt đầu.

    1. Model

    Vai trò chính của Model trong MVVM là thực hiện logic nghiệp vụ trong mẫu thiết kế mà chỉ hoạt động dựa trên nguồn dữ liệu cần thiết trong một hoạt động. Hiểu theo cách khác, phần tử model của mẫu kiến ​​trúc MVVM được sử dụng để đại diện cho dữ liệu thời gian thực cuối cùng sẽ được sử dụng trong phát triển ứng dụng.

    Điều tốt nhất về model là hiển thị sự tương tác giữa tất cả các thành phần hoàn toàn bằng cách tìm nạp dữ liệu từ cơ sở dữ liệu phòng. Nếu chúng ta phải xác định model trong một câu duy nhất, chúng ta có thể nói – nó là một phần tử lưu trữ dữ liệu và logic liên quan của kiến ​​trúc.

    2. View

    View là viết tắt của các thành phần UI như HTML, CSS. Trong MVVM, View chịu trách nhiệm về lớp trình bày trong mẫu thiết kế và là một điểm vào ứng dụng. Khi chúng ta nói về lợi ích của kiến ​​trúc MVVM, các thuộc tính của view đã được ca ngợi. Có một tập hợp phân cấp được duy trì giữa mỗi mô hình MVVM trong khi thể hiện sự tương tác. Ví dụ: View không bao giờ tương tác trực tiếp với Model mà thông qua ViewModel. Tất cả dữ liệu được thu thập bởi Model giúp tạo dữ liệu bản trình bày cho View. View thực thi logic thiết kế UI-UX và yêu cầu mô hình gửi lại đầu ra cho người dùng.

    3. ViewModel

    Ngoài việc làm trung gian giữa các thành phần Model và View, ViewModel triển khai dữ liệu và các lệnh được kết nối với View trong kiến ​​trúc MVVM để thông báo cho phần tử sau về các thay đổi trạng thái. Có một ví dụ ViewModel Android để thực hiện các công việc khác nhau như tạo lớp con hoặc thêm phần phụ thuộc, v.v. ViewModels có thể được liên kết với một hoặc nhiều mô hình.
    Về cơ bản, vai trò của ViewModel trong kiến trúc MVVM là hỗ trợ trạng thái của View và hoạt động như một lớp logic của toàn bộ cộng đồng kiến ​​trúc MVVM. Sẽ không sai khi trích dẫn ViewModel là phần tích hợp của tất cả những gì làm cho Mô hình duy trì dữ liệu thực tế và phần View đóng gói dữ liệu cập nhật giữ bộ điều khiển như một cổng giữa chúng.

    Lợi ích của kiến ​​trúc MVVM dành cho ứng dụng Android và iOS

    Mục tiêu cơ bản của mô hình kiến ​​trúc ứng dụng dành cho thiết bị di động trong phát triển ứng dụng là củng cố các chiến lược phát triển ứng dụng dành cho thiết bị di động với nhiều kỹ thuật dựa trên các tiêu chuẩn của ngành và nhà cung cấp cụ thể để cuối cùng thúc đẩy việc xây dựng ứng dụng, Android hoặc iOS. Một câu hỏi khiến hầu hết các chủ dự án không hài lòng là – Tại sao MVVM lại quan trọng đối với ứng dụng của bạn? Nói cách khác, câu hỏi đề xuất – “điều kỳ diệu mà mẫu thiết kế này có thể làm với ứng dụng của bạn”.

    MVVM, một biến thể viết tắt của Model View ViewModel, nhằm mục đích tách ứng dụng thành ba thành phần hợp lý và sau đó xử lý các khía cạnh phát triển cụ thể của ứng dụng. Mặc dù thị trường có rất nhiều mẫu kiến ​​trúc cải thiện hiệu suất ứng dụng và khả năng của thiết bị, MVVM đặc biệt tập trung vào tầm quan trọng của kiến ​​trúc giải pháp mang lại trải nghiệm UI tốt hơn cho người dùng. Bên cạnh việc quản lý và trình bày các đối tượng dữ liệu, kiến ​​trúc MVVM có rất nhiều lợi ích để cung cấp, một số lợi ích được liệt kê dưới đây.

    1. Khả năng tái sử dụng

    Khi hiểu được toàn bộ khái niệm về việc giới thiệu kiến ​​trúc MVVM trong quá trình thiết kế ứng dụng, có thể xác định đây là mô hình giúp các nhà phát triển phân biệt rõ ràng giữa các ứng dụng được kết hợp lỏng lẻo và kết hợp chặt chẽ. Khớp nối là yếu tố phụ thuộc của thiết kế trong ứng dụng và có những thiết kế kiến ​​trúc có khớp nối chặt chẽ, điều này cuối cùng làm tăng chi phí bảo trì phát triển trang web và giảm khả năng tái sử dụng của thành phần. Kiến trúc MVVM đi kèm với khớp nối lỏng lẻo để đạt được sự phân tách trách nhiệm và nâng cao mức độ khớp nối lỏng lẻo. Mặt khác, yếu tố khả năng tái sử dụng cho phép các mã được sử dụng để xây dựng các mẫu thiết kế trong các ứng dụng khác.

    2. Thúc đẩy phát triển mã độc lập

    MVVM là một mô hình kiến ​​trúc độc lập cung cấp dữ liệu dưới dạng đầu ra thông qua View. Toàn bộ cơ sở mã của mẫu MVVM được tạo ra theo cách khắc phục các trục trặc phổ biến mà hầu hết các mẫu kiến ​​trúc trở thành nạn nhân của nó. Với ba thành phần chính của nó, kiến ​​trúc MVVM truy xuất và lưu giữ các đối tượng thông qua một dịch vụ. Dù nói đến thiết kế mô hình kiến ​​trúc MVVM Android Architecture hoặc iOS , mô hình MVVM thúc đẩy sự phát triển độc lập của ứng dụng. Vì kiến ​​trúc mã và đơn giản hóa mẫu thiết kế là mục tiêu cốt lõi của các nhà phát triển, phương pháp tiếp cận mẫu thiết kế MVVM sẽ giải quyết một số vấn đề tồn tại trong khuôn khổ thiết kế của một ứng dụng và xem xét tất cả các khía cạnh quan trọng mang lại kết quả tuyệt vời ở phần cuối của dự án.

    3. Nâng cao khả năng kiểm tra

    Mẫu thiết kế MVVM Android hoặc iOS dường như đang thu hút sự chú ý trên thị trường do các nhà phát triển có kỹ năng điều hành. Phần lớn các nhà phát triển tin rằng MVVM có thể đọc được, có thể mở rộng và có thể kiểm tra được so với các mô hình thiết kế kiến ​​trúc khác. Trong số tất cả các thành phần, ViewModel có trách nhiệm rất lớn là đại diện cho dữ liệu và khởi tạo trường hợp thử nghiệm trong mô hình kiến ​​trúc MVVM. Chính ViewModel làm cho thành phần View có thể tái sử dụng và có thể kiểm tra được, giúp cho việc khen ngợi logic nghiệp vụ kiểm thử đơn vị trở nên dễ dàng hơn. Do sự phụ thuộc của phần tử này, việc bắt đầu các trường hợp thử nghiệm trở nên ít phức tạp hơn một chút.

    4. Khả năng bảo trì

    Mặc dù một số nhà phát triển có thể thấy các chỉ số hiệu suất ứng dụng dành cho thiết bị di động và tính năng chia sẻ của kiến ​​trúc MVVM hơi khó chịu và phức tạp, nhưng khả năng bảo trì của kiến ​​trúc MVVM mang lại lợi ích cho mô hình là mạch lạc và đơn giản hóa, lưu ý các yêu cầu bổ sung. Khả năng bảo trì lồng vào nhau cung cấp khả năng mở rộng tối đa với sự phân tách ranh giới rõ ràng.
    Khả năng thay thế hoặc thêm các khối mã mới trong quá trình ứng dụng là một điều lý tưởng cần xem xét để đặt các mã trong cơ sở hạ tầng ứng dụng một cách phù hợp. Ngoài ra, tính năng ánh xạ trong kiến ​​trúc MVVM là chưa từng có. Mô hình MVVM sử dụng ánh xạ một đến nhiều giữa View và ViewModel.

  • Final và Constant trong Dart – Cách xác định Hằng số trong Dart

    Final và Constant trong Dart – Cách xác định Hằng số trong Dart

    Xin chào!. Trong bài viết này, chúng ta sẽ tim hiểu về final và constant trong dart là gì. Cách xác định constant trong Dart. Câu hỏi quan trọng nhất là Tại sao chúng ta cần final và const và mục đích của những từ khóa này là gì?

    Final Constant của dart

    Nếu bạn không bao giờ muốn thay đổi một giá trị thì hãy sử dụng các từ khóa final và const.

    Có rất nhiều trường hợp mà chúng ta không muốn thay đổi giá trị của một biến. Ví dụ, giá trị của Pi.

    const PI = 3,14;

    Hoặc có thể bạn không muốn thay đổi tên trong toàn bộ chương trình.

    final name = ”Waheed”;

    Hằng số PI sẽ chứa giá trị 3,14 và final name sẽ có giá trị ‘Waheed’ trong toàn bộ mã. Bây giờ trong cả hai trường hợp, bạn sẽ không thể thay đổi giá trị của biến PI hoặc name. Câu hỏi phổ biến đó là Sự khác biệt giữa final và const là gì.

    Sự khác biệt giữa Final và Const

    • Một biến final chỉ có thể được đặt một lần và nó chỉ được khởi tạo khi được truy cập.
      • Ở đây mấu chốt ở đây là “nó chỉ được khởi tạo chỉ khi nó được truy cập” nghĩa là bạn chỉ sử dụng được biến final trong chương trình của mình khi giá trị được khởi tạo và vị trí bộ nhớ được cấp phát. Và nếu bạn không bao giờ sử dụng biến final, giá trị sẽ không bao giờ được khởi tạo trong chương trình của bạn.
    • Một biến const hoàn toàn là final nhưng nó là một hằng số compile time
      • Nó có nghĩa là tất cả các biến const cũng là final.
      • Nó là một hằng số compile time đã biên dịch có nghĩa là nó được khởi tạo trong quá trình biên dịch. Bất cứ khi nào bạn biên dịch chương trình, giá trị của PI sẽ được khởi tạo và bộ nhớ sẽ được cấp phát. Không quan trọng bạn có đang sử dụng biến này hay không.
    • Một biến instance có thể là final nhưng không thể là const
      • Nếu bạn muốn tạo một Hằng số ở class, hãy khai báo nó là static const chứ không chỉ mỗi const.

    Bây giờ đã đến lúc làm rõ các khái niệm bằng một ví dụ thực tế.

    Chúng tôi đã xác định một biến final name và sau đó chúng tôi đang cố gắng thay đổi giá trị của biến final name nhưng chúng tôi sẽ nhận được thông báo lỗi như thế này.

    ‘error‘name’, a final variable, can only be set once.‘ Or something like ‘ cannot assign to final variable name‘

    Có nghĩa là: ‘‘Error‘name’, một biến final, chỉ có thể được đặt một lần. ‘Hoặc một cái gì đó như‘ không thể gán cho biến final name ‘’

    Bạn có thể nhận thấy khi chưa xác định kiểu dữ liệu cho tên biến. Chà, Dart hiểu nó là một Chuỗi theo giá trị nghĩa đen của nó. Bạn có thể xác định rõ ràng nó là một String nhưng nó là tùy chọn.

    Chúng ta cũng hãy xem một ví dụ về const.

    Đây là cách chúng ta định nghĩa const trong Dart.

    Có một khái niệm rất quan trọng khác cần hiểu. Hãy nhớ rằng chúng tôi đã nghiên cứu rằng ‘Một biến instance có thể là biến final nhưng không thể là const.’ Hãy làm rõ khái niệm này với sự trợ giúp của một ví dụ. Để làm được điều đó, chúng ta phải tạo một Class. Nếu bạn không quen thuộc với class là gì, xin đừng lo lắng, chúng tôi sẽ sớm đề cập đến khái niệm class. Còn bây giờ chỉ cần tiếp tục theo dõi với chúng tôi.

    Chà, trình biên dịch sẽ cung cấp cho chúng ta một lỗi tại const weight. ‘Chỉ có thể khai báo các trường static là const‘.

    Vì vậy, nếu bạn xác định một const ở mức Class thì bạn cũng phải sử dụng từ khóa static. Chúng ta sẽ đề cập đến từ khóa static là gì nhưng hiện tại, hãy nhớ rằng chúng ta không thể khai báo một biến const ở cấp độ Class một cách đơn giản. Nó phải là static const.

    Đó là một hướng dẫn rất ngắn nhưng chi tiết về final và const của dart. Chúng tôi chắc chắn rằng bây giờ bạn sẽ không bao giờ quên các khái niệm final và const. Cảm ơn vì đã theo dõi.

  • 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

  • Stack with Heap Memory Allocation

    1. Stack memory allocation

    Stack memory allocation: Việc cấp phát này xảy ra trên các khối bộ nhớ liền kề. Chúng ta gọi nó là Stack memory allocation vì việc cấp phát xảy ra trong ngăn xếp lệnh gọi hàm. Kích thước của bộ nhớ được cấp phát đã được trình biên dịch biết và bất cứ khi nào một hàm được gọi, các biến của nó sẽ nhận được bộ nhớ được cấp phát trên ngăn xếp. Và bất cứ khi nào gọi hàm kết thúc, bộ nhớ cho các biến sẽ được hủy cấp phát. Tất cả điều này xảy ra bằng cách sử dụng một số quy trình được xác định trước trong trình biên dịch. Lập trình viên không phải lo lắng về việc cấp phát bộ nhớ và hủy cấp phát các biến ngăn xếp. Loại cấp phát bộ nhớ này còn được gọi là Cấp phát bộ nhớ tạm thời bởi vì ngay sau khi phương thức kết thúc việc thực thi, tất cả dữ liệu thuộc về phương thức đó sẽ tự động thoát ra khỏi ngăn xếp. Có nghĩa là, bất kỳ giá trị nào được lưu trữ trong lược đồ bộ nhớ ngăn xếp đều có thể truy cập được miễn là phương thức chưa hoàn thành việc thực thi và hiện ở trạng thái đang chạy.

    Những điểm chính của bộ nhớ stack:

    1. Nó cấp phát bộ nhớ tạm thời trong đó các thành viên dữ liệu chỉ có thể truy cập được nếu phương thức chứa chúng hiện đang chạy.
    2. Nó tự động cấp phát hoặc hủy cấp phát bộ nhớ ngay sau khi phương thức tương ứng hoàn thành việc thực thi.
    3. Chúng ta nhận được nếu bộ nhớ ngăn xếp được lấp đầy hoàn toàn.
    4. Cấp phát bộ nhớ stack được coi là an toàn hơn so với cấp phát bộ nhớ heap vì dữ liệu được lưu trữ chỉ có thể được truy cập bởi luồng chủ của nó.
    5. Cấp phát và thu hồi cấp phát bộ nhớ nhanh hơn so với cấp phát bộ nhớ Heap.
    6. Bộ nhớ stack được cấp phát bộ nhớ lưu trữ nhỏ hơn so với bộ nhớ Heap.

    Tất cả các biện trên sẽ được cấp pháp bộ nhớ trong bộ nhớ stack.

    2. Heap memory allocation

    Bộ nhớ được cấp phát trong quá trình thực thi các lệnh do người lập trình viết. Lưu ý rằng tên heap không liên quan gì đến cấu trúc dữ liệu heap. Nó được gọi là heap vì nó là một không gian bộ nhớ có sẵn cho các lập trình viên để cấp phát và thu hồi cấp phát. Mỗi khi chúng ta tạo một đối tượng, nó luôn tạo ra trong Heap-space và thông tin tham chiếu đến các đối tượng này luôn được lưu trữ trong bộ nhớ stack. Phân bổ bộ nhớ Heap không an toàn như phân bổ bộ nhớ Stack vì dữ liệu được lưu trữ trong vùng này có thể truy cập hoặc hiển thị cho tất cả các chuỗi. Nếu một lập trình viên không xử lý tốt bộ nhớ này, thì chương trình có thể bị thiếu bộ nhớ.

    Việc cấp phát bộ nhớ Heap được chia thành ba loại. Ba loại này giúp chúng ta sắp xếp thứ tự ưu tiên dữ liệu (đối tượng) sẽ được lưu trữ trong bộ nhớ Heap hoặc trong quá trình thu gom rác (quá trình xác định và loại bỏ các Object không được sử dụng (unreferenced) khỏi bộ nhớ Heap) bao gồm:

    1. Young Generation: Phần bộ nhớ nơi tất cả dữ liệu (đối tượng) mới được tạo ra để phân bổ vùng và bất cứ khi nào bộ nhớ này được lấp đầy hoàn toàn thì phần còn lại của dữ liệu sẽ được lưu trữ trong bộ sưu tập Rác.
    2. Old or Tenured Generation: Phần của bộ nhớ Heap chứa các đối tượng dữ liệu cũ hơn không được sử dụng thường xuyên hoặc không được sử dụng nữa sẽ được đặt.
    3. Permanent Generation: Đây là phần của bộ nhớ Heap chứa siêu dữ liệu của JVM cho các lớp thời gian chạy và các phương thức ứng dụng.

    Những điểm chính của hepa memory allocation:

    1. Chúng ta nhận được thông báo lỗi tương ứng nếu Heap-space đã đầy hoàn toàn.
    2. Việc cấp phát bộ nhớ này khác với bộ nhớ stack, ở đây không cung cấp tính năng tự động thu hồi bộ nhớ. Chúng ta cần sử dụng quá trình xác định và loại bỏ rác để loại bỏ các đối tượng cũ không sử dụng để sử dụng bộ nhớ một cách hiệu quả.
    3. Thời gian xử lý (Thời gian truy cập) của bộ nhớ này khá chậm so với bộ nhớ stack.
    4. Bộ nhớ Heap cũng không phải là một luồng an toàn như bộ nhớ stack vì dữ liệu được lưu trữ trong bộ nhớ Heap được hiển thị cho tất cả các luồng.
    5. Bộ nhớ đống có thể truy cập hoặc tồn tại miễn là toàn bộ ứng dụng chạy.
    6. bộ nhớ Heap được cấp phát bộ nhớ lưu trữ lớn hơn so với bộ nhớ stack.

    Bộ nhớ này cho 10 kiểu dữ liệu interger được cấp phát trong Heap.

    Ví dụ:

    Trong ví dụ trên:

    • Khi chúng ta bắt đầu thực thi chương trình có, tất cả các lớp thời gian chạy được lưu trữ trong không gian bộ nhớ Heap.
    • Sau đó, chúng ta tìm thấy phương thức main() trong dòng tiếp theo được lưu trữ trong bộ nhớ stack cùng với tất cả các phương thức nguyên thủy (hoặc cục bộ) và biến tham chiếu Emp kiểu Emp_detail cũng sẽ được lưu trữ trong bộ nhớ stack và sẽ trỏ đến đối tượng tương ứng được lưu trữ trong bộ nhớ Heap.
    • Sau đó, dòng tiếp theo sẽ gọi đến phương thức khởi tạo tham số Emp(int, String) từ main() và nó cũng sẽ cấp phát cho phần trên cùng của cùng một khối bộ nhớ Stack. Điều này sẽ lưu trữ:
      • Đối tượng tham chiếu của đối tượng được gọi của bộ nhớ Stack.
      • Giá trị nguyên thủy (kiểu dữ liệu nguyên thủy) int id trong bộ nhớ Stack.
      • Biến tham chiếu của đối số String emp_name sẽ trỏ đến chuỗi thực từ nhóm chuỗi vào bộ nhớ heap.
    • Sau đó, phương thức main sẽ lại gọi đến phương thức static Emp_detail(), phương thức này sẽ được thực hiện trong khối bộ nhớ Stack trên đầu khối bộ nhớ trước đó.
    • Vì vậy, đối với đối tượng mới tạo Emp kiểu Emp_detail và tất cả các biến thể hiện sẽ được lưu trữ trong bộ nhớ heap.

    Cụ thể như hình dưới:

  • 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.

  • Try catch , error và exception trong ngôn ngữ dart

    Lỗi (Error)
    Lỗi là vấn đề khá là nghiêm trọng và khó có thể “deal with” với nó, và không thể phục hồi. ví dụ: out of memory (đầy bộ nhớ).

    Ngoại lệ (Exceptions)] nhằm truyền đạt thông tin cho người dùng về lỗi,  để lỗi có thể được giải quyết theo chương trình. Nó được dự định là  đã bắt được và nó phải chứa các trường dữ liệu hữu ích.


    Khi đang chạy chương trình, đột nhiên ngừng lại và xuất hiện thông báo lỗi – đó chính là ngoại lệ ( Exceptions).

    Trong quá trình xây dựng phần mềm sẽ có thể sảy ra nhiều lỗi và những ngoại lệ , điều này là không tránh khỏi. Vậy cách nào để kiểm soát và phát hiện chúng ? Ở bài viết này mình sẽ sử dụng try catch để xử lý lỗi giúp cho app không bị chết đột ngột .

    Cú pháp

    Try{

    // Khối lệnh có nguy cơ xảy ra exception

    } catch(e){

    }finally{

    }

    Sau đây chúng ta hãy đi vào ví dụ cụ thể :

    Ở ví dụ trên ta có thể thấy đã xảy ra lỗi nhưng bản thân mỗi lập trình viên không thể vì lỗi mà làm chết app ảnh hưởng đến trải nhiệm người dùng . Trong thực tế app có thể chết do mạng internet hay filenotfound hay nhiều  vô vàn lý do khác . Các lập trình viên phải handle các lỗi nhiều nhất có thể để ứng dụng luôn luôn “sống” .

    finally : try-catch-finally hay try – finally , khối lệnh trong finally sẽ được thực hiện bất chấp có sảy ra lỗi trong khối try hay không.

    Try-on-catch : để bắt được loại ngoại lệ cụ thể. Ví dụ:

    Khi bắt được chính xác loại ngoại lệ ở trước thì khối lệnh trong catch sẽ không được thực thi.

    Tóm lại bài viết này nhằm mục đích gới thiệu về try catch finaly một cách cơ bản , hi vọng giúp ích được cho bạn

  • Cách viết các ứng dụng mạnh mẽ mọi lúc, bằng cách sử dụng “Clean Architecture”

    Cách viết các ứng dụng mạnh mẽ mọi lúc, bằng cách sử dụng “Clean Architecture”

    Là nhà phát triển, chúng ta không thể tiếp tục sử dụng các thư viện và framework bên ngoài trong hệ thống của mình. Việc cộng đồng tạo ra những công cụ hữu ích và việc sử dụng chúng là điều đương nhiên. Tuy nhiên, mọi thứ đều có mặt trái của nó.

    Các nhóm và các cá nhân bất cẩn có thể rơi vào tình huống nguy hiểm bằng cách cấu trúc hệ thống của họ xung quanh các công cụ mà họ sử dụng. Các business logic có thể bị lẫn lộn với các chi tiết thực hiện. Điều này có thể dẫn đến một hệ thống khó mở rộng và bảo trì. Những gì nên thay đổi nhanh chóng trong GUI cuối cùng lại biến thành một cuộc truy tìm lỗi kéo dài hàng giờ. Nhưng câu chuyện không cần thiết phải như thế này.

    Kiến trúc phần mềm đề xuất các mô hình và quy tắc để xác định cấu trúc (classes, interfaces, và structs) trong một hệ thống và cách chúng liên quan với nhau. Các quy tắc này thúc đẩy khả năng tái sử dụng và sự tách biệt các mối quan tâm đối với các yếu tố này. Điều này giúp dễ dàng thay đổi các chi tiết triển khai như DBMS hoặc thư viện front-end. Trình tái cấu trúc và sửa lỗi ảnh hưởng đến càng ít bộ phận của hệ thống càng tốt. Và thêm các tính năng mới trở nên dễ dàng.

    Trong bài viết này, tôi sẽ giải thích một mô hình kiến trúc được đề xuất vào năm 2012 bởi Robert C. Martin, Uncle Bob. Ông là tác giả của những tác phẩm kinh điển như Clean Code và The Clean Coder…vv.

    Mô hình có được xây dựng dựa trên các khái niệm đơn giản:

    Chia thành phần của hệ thống thành các lớp với các vai trò riêng biệt và được xác định rõ ràng. Và hạn chế các mối quan hệ giữa các entities ở các tầng khác nhau. Không có gì mới trong việc tách ứng dụng của bạn thành các lớp. Nhưng tôi đã chọn cách tiếp cận này vì nó là cách đơn giản nhất để nắm bắt và thực hiện. Và nó làm cho các usecase thử nghiệm trở nên đơn giản.

    Chúng tôi chỉ cần đảm bảo Tương tác hoạt động bình thường. Đừng lo lắng nếu từ “Tương tác” có vẻ xa lạ với bạn, chúng ta sẽ tìm hiểu về chúng ngay sau đây.

    Từ trong ra ngoài, chúng ta sẽ khám phá từng lớp sâu hơn một chút. Chúng tôi sẽ sử dụng một ứng dụng mẫu khá quen thuộc với chúng ta: counter app. Không mất thời gian để hiểu, vì vậy chúng ta có thể tập trung vào chủ đề của bài viết này.

    Entities

    Entities trong sơ đồ là Business Rules. Các Entities bao gồm các Business Rules phổ biến cho một công ty. Chúng đại diện cho các entities cơ bản đối với lĩnh vực hoạt động của nó. Chúng là những thành phần có mức độ trừu tượng cao nhất.

    Trong ví dụ counter app của chúng tôi, có một Thực thể rất rõ ràng: chính là Counter.

    Use Cases

    Các trường hợp sử dụng được chỉ ra dưới dạng Application Business Rules. Chúng đại diện cho từng trường hợp sử dụng của một ứng dụng. Mỗi phần tử của lớp này cung cấp một giao diện cho lớp bên ngoài và hoạt động như một trung tâm giao tiếp với các phần khác của hệ thống. Chúng chịu trách nhiệm thực hiện hoàn chỉnh các usecase và thường được gọi là Tương tác.

    Trong ví dụ của chúng tôi, chúng tôi có một Trường hợp sử dụng để tăng hoặc giảm bộ đếm của chúng

    Lưu ý rằng factory function cho ChangeCounterInteractor nhận một tham số kiểu CounterGateway. Chúng ta sẽ thảo luận về sự tồn tại của loại hình này ở phần sau của bài viết. Nhưng chúng ta có thể nói rằng Gateways là thứ đứng giữa các usecase và layer tiếp theo.

    Interface Adapters

    Lớp này bao gồm ranh giới giữa các business rules của hệ thống và các công cụ cho phép hệ thống tương tác với các phần bên ngoài, như database và UI. Các phần tử trong lớp này hoạt động như các phần tử trung gian, nhận dữ liệu từ một lớp và chuyển nó sang lớp kia, điều chỉnh dữ liệu khi cần thiết.

    Trong ví dụ của chúng tôi, chúng tôi có một vài Interface Adapters. Một trong số đó là React component trình bày Bộ đếm và các điều khiển của nó để tăng và giảm:

    Lưu ý rằng component không sử dụng một thể hiện Counter để trình bày giá trị của nó mà thay vào đó là một instance của CounterViewData. Chúng tôi đã thực hiện thay đổi này để tách present logic khỏi business data. Một ví dụ về điều này là logic hiển thị của counter dựa trên view mode. Cách triển khai CounterViewData ở bên dưới:

    Một ví dụ khác về Interface Adapter sẽ là triển khai Redux của ứng dụng của chúng tôi. Các mô-đun chịu trách nhiệm về các yêu cầu tới máy chủ và việc sử dụng bộ nhớ cục bộ cũng sẽ nằm trong layer này.

    Frameworks and Drivers

    Các công cụ mà hệ thống của bạn sử dụng để giao tiếp với các phần bên ngoài tạo nên lớp ngoài cùng. Chúng tôi thường không viết mã trong lớp này, bao gồm các thư viện như React / Redux, browser API, v.v.

    The Dependency Rule

    Sự phân chia thành các lớp này có hai mục tiêu chính. Một trong số đó là làm rõ trách nhiệm của từng bộ phận trong hệ thống. Hai là đảm bảo vai trò của mỗi lớp độc lập với nhau nhất có thể. Để điều này xảy ra, có một quy tắc nêu rõ các yếu tố phải phụ thuộc vào nhau như thế nào:

    Một phần tử không được phụ thuộc vào bất kỳ phần tử nào thuộc một lớp bên ngoài lớp của nó.

    Ví dụ: một phần tử trong Use Cases layer không được có bất kỳ thông tin nào về bất kỳ lớp hoặc mô-đun nào liên quan đến GUI hoặc tính ổn định của dữ liệu. Tương tự như vậy, một Entity không thể biết các Use Cases nào sử dụng nó.

    Quy tắc này có thể đã đặt ra câu hỏi trong đầu bạn. Lấy ví dụ về một Use Case. Nó được kích hoạt do tương tác của người dùng với UI. Việc thực thi nó liên quan đến việc cập nhật trong một số bộ lưu trữ dữ liệu liên tục như cơ sở dữ liệu. Làm cách nào Interactor có thể thực hiện các lệnh gọi liên quan đến quy trình cập nhật mà không phụ thuộc vào Interface Adapter chịu trách nhiệm về tính ổn định của dữ liệu?

    Câu trả lời nằm trong một yếu tố mà chúng tôi đã đề cập trước đây: Gateways. Họ chịu trách nhiệm thiết lập giao diện mà các usecase cần để thực hiện công việc của họ. Sau khi họ đã thiết lập giao diện này, Interface Adapters có thể thực hiện các khía cạnh của chúng, như thể hiện trong sơ đồ ở trên. Chúng tôi có CounterGateway interface và triển khai cụ thể bằng Redux bên dưới:

    Có thể bạn không cần nó

    Tất nhiên, ứng dụng mẫu này hơi phức tạp đối với counter app tăng / giảm. Và tôi muốn nói rõ rằng bạn không cần tất cả những điều này cho một dự án nhỏ hoặc nguyên mẫu. Nhưng hãy tin tôi, khi ứng dụng của bạn lớn hơn, bạn sẽ muốn tối đa hóa khả năng tái sử dụng và khả năng bảo trì. Kiến trúc phần mềm tốt giúp cho các dự án có khả năng chống chọi với thời gian.

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

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

    Collections

    Bộ sưu tập rất hữu ích để nhóm dữ liệu liên quan. Dart bao gồm một số loại bộ sưu tập khác nhau, nhưng hướng dẫn này sẽ bao gồm hai loại phổ biến nhất: List và Map.

    Lists

    Lists trong Dart tương tự như arrays trong các ngôn ngữ khác. Bạn sử dụng chúng để duy trì một danh sách các giá trị có thứ tự. Danh sách dựa trên 0, vì vậy mục đầu tiên trong danh sách ở chỉ mục 0:

    Dưới đây là danh sách các món tráng miệng khác nhau:

    List desserts = ['cookies', 'cupcakes', 'donuts', 'pie'];

    Bạn đặt các phần tử của danh sách trong dấu ngoặc vuông [ ] . Bạn sử dụng dấu phẩy để phân tách các phần tử.

    Ở đầu dòng, bạn có thể thấy rằng loại là List. Bạn sẽ nhận thấy không có loại nào được bao gồm. Dart suy luận rằng danh sách có loại List

    Đây là danh sách các số nguyên:

    final numbers = [42, -1, 299792458, 100];

    Nhập numbers vào DartPad và bạn sẽ thấy rằng Dart nhận dạng loại List của int.

    Làm việc với các phần tử trong List

    Để truy cập các phần tử của danh sách, hãy sử dụng ký hiệu chỉ số con bằng cách đặt số chỉ mục giữa dấu ngoặc vuông sau tên biến danh sách. Ví dụ:

    final firstDessert = desserts[0];
    print(firstDessert); // cookies

    Vì chỉ số danh sách dựa trên số không, desserts[0]là phần tử đầu tiên của danh sách.

    Thêm và xóa các phần tử với add và remove tương ứng:

    desserts.add('cake');
    print(desserts); 
    // [cookies, cupcakes, donuts, pie, cake]
    
    desserts.remove('donuts');
    print(desserts); 
    // [cookies, cupcakes, pie, cake]

    Chạy mã để xem kết quả.

    Trước đó, bạn đã học về forvòng lặp. for-inVòng lặp của Dart hoạt động đặc biệt tốt với các danh sách. Hãy thử nó:

    for (final dessert in desserts) {
      print('I love to eat $dessert.');
    }
    // I love to eat cookies.
    // I love to eat cupcakes.
    // I love to eat pie.
    // I love to eat cake.

    Bạn không cần sử dụng chỉ mục. Dart chỉ lặp qua mọi phần tử của dessertsvà gán nó mỗi lần cho một biến có tên dessert.

    Maps

    Khi bạn muốn một danh sách các giá trị được ghép nối, Map là một lựa chọn tốt. Dart Map tương tự như dictionaries  trong Swift và maps trong Kotlin.

    Đây là một ví dụ về map trong Dart:

    Map<String, int> calories = {
      'cake': 500,
      'donuts': 150,
      'cookies': 100,
    };

    Bạn bao quanh Maps bằng dấu ngoặc nhọn { }. Sử dụng dấu phẩy để phân tách các phần tử của bản đồ.

    Các phần tử của bản đồ được gọi là cặp khóa-giá trị , trong đó khóa ở bên trái dấu hai chấm và giá trị ở bên phải.

    Bạn tìm thấy một giá trị bằng cách sử dụng khóa để tra cứu nó, như sau:

    final donutCalories = calories['donuts'];
    print(donutCalories); // 150

    Từ khóa 'donuts'nằm trong dấu ngoặc vuông sau tên bản đồ. Trong trường hợp này, nó ánh xạ tới một giá trị 150.

    Nhập donutCalories vào DartPad và bạn sẽ thấy rằng kiểu được suy luận thì int? đúng hơn int. Đó là bởi vì, nếu một bản đồ không chứa khóa mà bạn đang tìm kiếm, nó sẽ trả về một giá trị null.

    Thêm một phần tử mới vào bản đồ bằng cách chỉ định khóa và gán cho nó một giá trị:

    calories['brownieFudgeSundae'] = 1346;
    print(calories);
    // {cake: 500, donuts: 150, cookies: 100, brownieFudgeSundae: 1346}

    Chạy mã đó và bạn sẽ thấy bản đồ được in ở định dạng ngang với món tráng miệng mới của bạn ở cuối.

    Functions

    Các hàm cho phép bạn đóng gói nhiều dòng mã liên quan vào một nội dung duy nhất. Sau đó, bạn triệu hồi hàm để tránh lặp lại những dòng mã đó trong ứng dụng Dart của mình.

    Một hàm bao gồm các phần tử sau:

    • Kiểu trả về
    • Tên chức năng
    • Danh sách tham số trong ngoặc đơn
    • Nội dung hàm được đặt trong dấu ngoặc

    Defining Functions

    Đoạn mã bạn đang chuyển thành một hàm nằm trong dấu ngoặc nhọn. Khi bạn gọi hàm, bạn truyền vào các đối số phù hợp với loại tham số của hàm.

    Tiếp theo, bạn sẽ viết một hàm mới trong DartPad sẽ kiểm tra xem một chuỗi đã cho có phải là Banana hay không :

    bool isBanana ( String fruit) {
       return fruit == 'banana' ; 
    }

    Hàm sử dụng return để trả về kiểu bool. Đối số bạn truyền vào hàm sẽ xác định bool.

    Hàm này sẽ luôn trả về cùng một kiểu giá trị cho bất kỳ đầu vào nhất định nào. Nếu một hàm không cần trả về giá trị, bạn có thể đặt kiểu trả về void.  main làm điều này, chẳng hạn.

    Làm việc với các Function

    Bạn có thể gọi hàm bằng cách truyền vào một chuỗi. Sau đó, bạn có thể chuyển kết quả của cuộc gọi đó tới print:

    void main() {
      var fruit = 'apple';
      print(isBanana(fruit)); // false
    }

    Các Function lồng vào nhau

    Thông thường, bạn xác định các hàm bên ngoài các hàm khác hoặc bên trong các lớp Dart. Tuy nhiên, bạn cũng có thể lồng các hàm Dart. Ví dụ, bạn có thể làm tổ isBanana bên trong main.

    void main() {
      bool isBanana(String fruit) {
        return fruit == 'banana';
      }
    
      var fruit = 'apple';
      print(isBanana(fruit)); // false
    }

    Bạn cũng có thể thay đổi đối số thành một hàm, sau đó gọi lại đối số đó bằng đối số mới:

    fruit = 'banana';
    print(isBanana(fruit));  // true

    Kết quả của việc gọi hàm phụ thuộc hoàn toàn vào các đối số bạn truyền vào.

    Optional Parameters

    Nếu một tham số cho một hàm là tùy chọn, bạn có thể bao quanh nó bằng dấu ngoặc vuông và đặt kiểu là vô hiệu:

    String fullName(String first, String last, [String? title]) {
      if (title == null) {
        return '$first $last';
      } else {
        return '$title $first $last';
      }
    }

    Trong chức năng này, title là tùy chọn. Nó sẽ mặc định thành null nếu bạn không chỉ định nó.

    Bây giờ, bạn có thể gọi hàm có hoặc không có tham số tùy chọn:

    print(fullName('Joe', 'Howard'));
    // Joe Howard
    
    print(fullName('Albert', 'Einstein', 'Professor'));
    // Professor Albert Einstein

    Named Parameters and Default Values

    Khi bạn có nhiều tham số, bạn có thể nhầm lẫn khi nhớ cái nào là cái nào. Dart giải quyết vấn đề này với các tham số được đặt tên , mà bạn nhận được bằng cách bao quanh danh sách tham số bằng dấu ngoặc nhọn { }.

    Các tham số này là tùy chọn theo mặc định, nhưng bạn có thể cung cấp cho chúng các giá trị mặc định hoặc yêu cầu chúng bằng cách sử dụng từ khóa required:

    bool withinTolerance({required int value, int min = 0, int max = 10}) {
      return min <= value && value <= max;
    }

    valuelà bắt buộc, trong khi minvà maxlà tùy chọn với các giá trị mặc định.

    Với các tham số được đặt tên, bạn có thể chuyển vào các đối số theo một thứ tự khác bằng cách cung cấp các tên tham số bằng dấu hai chấm:

    print(withinTolerance(value: 5)); // true

    Chạy mã của bạn để xem các chức năng mới của bạn đang hoạt động.

    Anonymous Functions

    Dart hỗ trợ first-class functions , nghĩa là nó xử lý các hàm giống như bất kỳ kiểu dữ liệu nào khác. Bạn có thể gán chúng cho các biến, chuyển chúng dưới dạng đối số và trả lại chúng từ các hàm khác.

    Để chuyển các hàm này xung quanh dưới dạng giá trị, hãy bỏ qua tên hàm và kiểu trả về. Vì không có tên nên loại hàm này được gọi là hàm ẩn danh .

    Bạn có thể gán một hàm ẩn danh cho một biến có tên onPressed như sau:

    final onPressed = () {
      print('button pressed');
    };

    onPressedcó giá trị kiểu Function. Các dấu ngoặc trống cho biết hàm không có tham số. Giống như các hàm thông thường, mã bên trong dấu ngoặc nhọn là phần thân của hàm.

    Để thực thi mã bên trong thân hàm, hãy gọi tên biến như thể nó là tên của hàm:

    onPressed(); // button pressed

    Bạn có thể đơn giản hóa các hàm có nội dung chỉ chứa một dòng duy nhất bằng cách sử dụng cú pháp mũi tên . Để thực hiện việc này, hãy xóa dấu ngoặc nhọn và thêm một mũi tên =>.

    Dưới đây là so sánh của hàm ẩn danh ở trên và phiên bản đã cấu trúc lại:

    // original anonymous function
    final onPressed = () {
      print('button pressed');
    };
    
    // refactored
    final onPressed = () => print('button pressed');

    Sử dụng Anonymous Functions

    Bạn sẽ thường thấy các hàm ẩn danh trong Flutter, giống như các hàm ở trên, được chuyển xung quanh dưới dạng lệnh gọi lại cho các sự kiện giao diện người dùng. Điều này cho phép bạn chỉ định mã chạy khi người dùng làm điều gì đó, chẳng hạn như nhấn một nút.

    Một nơi phổ biến khác mà bạn sẽ thấy các chức năng ẩn danh là với các collection. Bạn có thể cung cấp cho tập hợp một hàm ẩn danh sẽ thực hiện một số tác vụ trên mỗi phần tử của tập hợp. Ví dụ:

    // 1
    final drinks = ['water', 'juice', 'milk'];
    // 2
    final bigDrinks = drinks.map(
      // 3
      (drink) => drink.toUpperCase()
    );
    // 4
    print(bigDrinks); // (WATER, JUICE, MILK)

    Hãy xem xét từng bước:

    1. Xác định danh sách drinks có các chữ cái thường.
    2. .map nhận tất cả các giá trị danh sách và trả về một tập hợp mới với chúng.
    3. Một hàm ẩn danh được chuyển dưới dạng một tham số. Trong hàm ẩn danh đó, bạn có một drink đối số đại diện cho từng phần tử của danh sách.
    4. Phần thân của hàm ẩn danh chuyển đổi từng phần tử thành chữ hoa và trả về giá trị. Vì danh sách ban đầu là danh sách các chuỗi, nên drink cũng có kiểu String.

    Sử dụng một hàm ẩn danh và kết hợp nó với .maplà một cách thuận tiện để chuyển đổi một bộ sưu tập này thành một bộ sưu tập khác. Lưu ý : Đừng nhầm giữa .mapmethod với Maptype.

    Chạy mã để xem bộ sưu tập kết quả.

    Xin chúc mừng, bạn đã hoàn thành phần hướng dẫn. Bây giờ bạn sẽ hiểu rõ hơn về mã Dart mà bạn thấy khi học cách xây dựng ứng dụng Flutter!

  • [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 😀