Author: DangDo

  • 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

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

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

    Tiếp tục với nội dung của phần 1, phần 2 sẽ nói về các thuộc tính counter, style, max lines, hintTextDirection, floating label behavior và thuộc tính isCollapsed trong Input Decoration.

    Counter

    Bạn có thể thực hiện thay đổi widget dựa trên số lượng ký tự đã được nhập. Nếu thuộc tính này không rỗng thì counterText sẽ bị bỏ qua.

    Style

    Thuộc tính này giúp thay đổi TextStyle cho các loại Text bên trong InputDecoration.

    Ví dụ bên dưới hiển thị thay đổi TextStyle của hintStyle, nhưng nó cũng làm tương tự nhau để đặt TextStyle cho labelStyle, counterStyle, errorStyle, prefixStyle, suffixStyle và helperStyle.

    Max lines

    Nếu không có MaxLines thì mặc định sẽ là 1 dòng.

    Dưới đây là ví dụ hintMaxLines cho hintText, nhưng còn có errorMaxLines và helperMaxlines đều sử dụng giống như hintMaxlines.

    Hint text direction

    Hint text direction có nhiệm vụ điều hướng văn bản cho hintText.

    Left to right

    Right to left

    Floating label behavior

    Never

    Khi người dùng tương tác với TextField, nhãn mác sẽ không xuất hiện ở bên trên trường nhập liệu.

    Auto

    Nhãn mác của TextField sẽ được đẩy lên và xuất hiện ở bên trên trường nhập liệu khi ta tương tác với TextField để nhập liệu.

    Always

    Nhãn mác của TextField sẽ luôn luôn được xuất hiện ở bên trên trường nhập liệu.

    Condensed

    Thay đổi kích thước của TextField giúp cho giảm bớt khoảng trắng trong trường nhập.

    False

    True

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

    TextField là một Widget nhập văn bản cho phép người dùng thu thập dữ liệu vào từ bàn phím mobile vào ứng dụng. Chúng ta có thể sử dụng Widget TextField trong việc xây dựng biểu mẫu, gửi tin nhắn, tạo trải nghiệm tìm kiếm và nhiều hơn nữa. Theo mặc định của Flutter, TextField được trang trí bằng một gạch dưới.

    Dưới đây là một số cách để trang trí cho TextField.

    None

    Icon

    Một icon để hiện thị phía trước trường nhập và bên ngoài vùng chứa của trang trí.

    Kích thước và màu sắc của icon được định cấu hình tự động bằng cách sử dụng IconTheme.

    Prefix icon

    Một biểu tượng xuất hiện ở trước phần có thể chỉnh sửa của trường nhập văn bản, bên trong decoration’s container. PrefixIcon bị hạn chế với kích thước tối thiểu là 48px x 48px, nhưng có thể được mở rộng hơn thế.

    Suffix icon

    Một biểu tượng xuất hiện ở sau phần có thể chỉnh sửa của trường nhập văn bản, bên trong decoration’s container. SuffixIcon bị hạn chế với kích thước tối thiểu là 48px x 48px, nhưng có thể được mở rộng hơn thế.

    Prefix text

    Là tiền tố văn bản được chọn để đặt trên dòng trước đầu vào.

    Hint text

    Là văn bản dùng để gợi ý loại đầu vào nào mà trường nhập chấp nhận.

    Suffix text

    Là hậu tố văn bản được tùy chọn để đặt trên dòng sau phần nhập.

    Label text

    Văn bản mô tả trường nhập cho người dùng biết.

    Khi trường nhập trống hoặc không được nhấn vào, nhãn sẽ hiển thị bên trên đầu trường nhập. Khi trường nhập được nhấn vào hoặc nhận bất cứ giá trị nào thì nhãn sẽ di chuyển lên trên theo chiều dọc liền kề trường nhập.

    Khi người dùng chưa tương tác với TextField.

    Khi người dùng tương tác với TextField.

    Error text

    Văn bản xuất hiện bên dưới InputDecorator.child và đường viền, văn bản thể hiện lỗi khi nhập liệu của người dùng.

    Helper text

    Văn bản cung cấp ngữ cảnh về giá trị của InputDecorator.child, chẳng hạn như cách giá trị sẽ được sử dụng như thế nào. Văn bản ở cùng vị trí với errorText.

    Nếu một giá trị errorText không phải null thì helperText sẽ không được hiển thị.

    Counter text

    Văn bản được đặt bên phải phía bên dưới trường nhập dưới dạng số ký tự.