Blog

  • Flutter – Life cycle

    Flutter – Life cycle

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

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

    State Life cycle

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

    Thiết lập

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

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

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

    Cập nhật

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

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

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

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

    Hủy

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

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

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

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

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

    Vòng đời của app

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

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

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

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

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

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

    Frame draw callback

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

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

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

    1. Tổng quan.

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

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

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

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

    2.1. Future and Stream types

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

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

    2.2. Async-await

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

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

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

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

    3.1. Main isolate

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

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

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

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

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

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

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

    3.4. Background workers

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

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

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

    4. Ví dụ:

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

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

    Code:

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

    5. Tổng kết.

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

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

    Author: DuongVT19

  • [FLUTTER] Tổng quan về ListView

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

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

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

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

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

    Sử dụng ListView.builder

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

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

    Sử dụng ListView.separated

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

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

    Sử dụng ListView.custom

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

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

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

    padding

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

    scrollDirection

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

    reverse

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

    physics

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

    Tài liệu tham khảo

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

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

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

    Border

    None

    Underline

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

    Outline with border radius

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

    Specific border behavior

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

    Trường hợp enabledBorder.

    Trường hợp focusedBorder.

    Filled with color

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

    Filled with color and no underline

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

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

    Hover color

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

    Enabled

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

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

    True

    False

    Combined effects

    None

    Dense

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

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

    Content padding

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

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

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

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

    Tác giả: DangDH9

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

  • Get data from server flutter

    Trong hầu hết các app hiện nay việc lấy dữ liệu từ server hay data base là việc hết sức bình thường để app của bạn có thể cập nhập các data mới nhất cũng như tương tác của người dùng . Trong bài viết này mình sẽ call data từ server một cách đơn giản nhất để những ai chưa biết có thể call được 1 cách dễ dàng . flutter có hỗ trợ nhiều thư viện nhưng ở bài viết này mình sẽ sử dụng packet http . http là một thư viện Future-based sử dụng tính năng await và async. Nó cung cấp phương thức cấp cao và đơn giản để phát triển REST trên ứng dụng di động. 

    Một vài phương thức chính :

    – read : gởi yêu cầu lên sever thông qua phương thức GET và trả về  Future<String>

    – get : gởi yêu cầu lên sever thông qua phương thức GET và trả về Future<Response>. Response là lớp giữ lại các thông tin phản hồi 

    – post : gởi yêu cầu lên sever thông qua phương thức POST  bằng việc đưa giá trị lên sever và phản hồi Future<Response>

    – put : gởi yêu cầu lên sever thông qua phương thức PUT và trả về phản hồi như Future<Response>

    – head : gởi yêu cầu lên sever thông qua phương thức HEAD và trả về phản hồi như Future<Response>

    – delete : gởi yêu cầu lên sever thông qua phương thức DELETE và trả về phản hồi như Future<Response> 

    Ở bài viết này sẽ chỉ tập trung vào phương thức GET và hiển thị data dưới dangj list view cơ bản .

    Step 1 : Bạn cần add thư viện http ở pub.dev vào file pubspec.yaml

    https://pub.dev/packages/http

    Step2 : Cần 1 link apidemo mà mình lấy link này

    https://jsonplaceholder.typicode.com/posts

    Step3 :

    Tạo file mới post_model.dart trong lớp model 

    Viết factory constructor trong lớp model , Post.fromMap dùng để chuyển đổi dữ liệu map trong đối tượng Post . Thông thường, tệp JSON sẽ được chuyển đổi bên trong đối tượng Dart Map và sau đó chuyển đổi sang đối tượng liên qua (Post) 

    Step 3 : Bây giờ ta sẽ viết 2 phương thức – parsePost và fetchPost – trong lớp chính để lấy và tải thông tin sản phẩm từ web server(máy chủ) trong List<Post>

    Step 4 : Ta có thể hiển thị data lên màn hình ở main.dart

    Và đây là kết quả :

    Hi vọng qua bài viết này các anh em  có thể nắm được cách get data từ server một cách dễ dàng

  • Get data from server flutter

    Trong hầu hết các app hiện nay việc lấy dữ liệu từ server hay data base là việc hết sức bình thường để app của bạn có thể cập nhập các data mới nhất cũng như tương tác của người dùng . Trong bài viết này mình sẽ call data từ server một cách đơn giản nhất để những ai chưa biết có thể call được 1 cách dễ dàng . flutter có hỗ trợ nhiều thư viện nhưng ở bài viết này mình sẽ sử dụng packet http . http là một thư viện Future-based sử dụng tính năng await và async. Nó cung cấp phương thức cấp cao và đơn giản để phát triển REST trên ứng dụng di động. 

    Một vài phương thức chính :

    – read : gởi yêu cầu lên sever thông qua phương thức GET và trả về  Future<String>

    – get : gởi yêu cầu lên sever thông qua phương thức GET và trả về Future<Response>. Response là lớp giữ lại các thông tin phản hồi 

    – post : gởi yêu cầu lên sever thông qua phương thức POST  bằng việc đưa giá trị lên sever và phản hồi Future<Response>

    – put : gởi yêu cầu lên sever thông qua phương thức PUT và trả về phản hồi như Future<Response>

    – head : gởi yêu cầu lên sever thông qua phương thức HEAD và trả về phản hồi như Future<Response>

    – delete : gởi yêu cầu lên sever thông qua phương thức DELETE và trả về phản hồi như Future<Response> 

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

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

    Tiếp tục phần 1, sang phần 2 này ta sẽ tìm hiểu về các toán tử có trong Dart.

    Operators

    Dart có tất cả các toán tử thông thường mà bạn quen thuộc từ các ngôn ngữ khác như C, Swift và Kotlin.

    Một số ví dụ về các toán tử của Dart bao gồm:

    • Toán tử số học
    • Toán tử so sánh
    • Toán tử logic

    Lưu ý : Dart cũng cho phép nạp chồng toán tử , như trong C ++ và Kotlin, nhưng điều đó nằm ngoài phạm vi của hướng dẫn này. Để tìm hiểu thêm về chủ đề này, hãy truy cập trang operator overloading của Wikipedia .

    Tiếp theo, bạn sẽ xem xét từng toán tử này.

    Toán tử số học

    Các toán tử số học hoạt động giống như bạn mong đợi. Hãy thử chúng bằng cách thêm một loạt các thao tác vào DartPad của bạn:

    in ( 40 + 2 ); // 42 
    
    print ( 44 - 2 ); // 42 
    
    print ( 21 * 2 ); // 
    
    42 print ( 84/2 ) ; // 42.0
    
    

    Đối với phép chia, ngay cả với số nguyên, Dart suy ra biến kết quả là a double. Đó là lý do tại sao bạn nhận được 42.0 thay vì 42 cho print câu lệnh cuối cùng.

    So sánh bằng

    Dart sử dụng toán tử double-equals ( ==) và not-equals ( !=):

    in ( 42 == 43 ); // 
    
    in sai ( 42 ! = 43 ); // true
    
    

    Toán tử so sánh

    Dart sử dụng các toán tử so sánh điển hình:

    • Nhỏ hơn (<)
    • Lớn hơn (>)
    • Lơn hơn hoặc bằng(>=)
    • Nhỏ hơn hoặc bằng(<=)

    Dưới đây là một số ví dụ:

    print ( 42 < 43 ); // true 
    print (42> = 43); // false
    
    

    Ngoài ra, nó cũng sử dụng các toán tử số học / phép gán ghép thông thường:

    var value = 42.0;
    
    value += 1; print(value); // 43.0
    
    value -= 1; print(value); // 42.0
    
    value *= 2; print(value); // 84.0
    
    value /= 2; print(value); // 42.0
    
    
    

    Toán tử số học / phép gán ghép thực hiện hai nhiệm vụ. +=thêm giá trị ở bên phải vào biến ở bên trái và sau đó gán kết quả cho biến.

    Một dạng rút gọn của += 1 là ++:

    value++;
    
    print(value); // 43.0
    
    

    Và Dart có toán tử modulo thông thường ( %) để trả về phần còn lại sau khi một số đã được chia cho một số khác:

    print( 392 % 50 ); // 42
    
    

    392 ÷ 50 = 7,84 nhưng 42 đó trong ngăn kết quả đến từ đâu? Nó dễ dàng hơn để xem với sự phân chia dài.

    Toán tử logic

    Dart sử dụng các toán tử logic giống như các ngôn ngữ khác, bao gồm và có kí hiệu là &&, OR  có kí hiệu là ||

    print (( 41 < 42 ) && ( 42 < 43 )); // true 
    
    print (( 41 < 42 ) || ( 42 > 43 )); // true
    
    

    Toán tử phủ định là dấu chấm than , biến false thành true và true thành false.

    print (! ( 41 < 42 )); // false
    
    

    Xem tài liệu Dart để biết danh sách đầy đủ các toán tử được hỗ trợ .

    Strings

    Loại chuỗi Dart là String. Các chuỗi được thể hiện trong Dart bằng cách sử dụng văn bản được bao quanh bởi dấu ngoặc kép đơn hoặc kép .

    Bạn có thể sử dụng var và nhập suy luận hoặc String để tạo biến chuỗi, giống như các kiểu khác mà bạn đã thấy:

    var firstName = 'Albert' ; String lastName = "Einstein" ;
    
    
    
    

    Tương tự như các ngôn ngữ như Kotlin và Swift, bạn có thể nhúng giá trị của một biểu thức bên trong một chuỗi bằng cách sử dụng ký hiệu ký hiệu đô la: $ { biểu thức } .

    Nếu biểu thức là một số nhận dạng, bạn có thể bỏ qua dấu {} . Thêm những điều sau:

    var physicist = "$firstName $lastName likes the number ${84 / 2}";
    
    print(physicist); // Albert Einstein
    
    

    $firstName và $lastName được thay thế bằng các giá trị biến. Trả về kết quả được tính toán.

    Escaping Strings

    Các trình tự thoát được sử dụng trong Dart tương tự như các trình tự được sử dụng trong các ngôn ngữ giống C khác. Ví dụ, bạn sử dụng \n cho một dòng mới.

    Nếu có các ký tự đặc biệt trong chuỗi, hãy sử dụng \ để thoát khỏi chúng:

    var quote = 'If you can\'t explain it simply\nyou don\'t understand it well enough.';
    
    print(quote);
    // If you can't explain it simply
    
    // you don't understand it well enough.
    
    

    Ví dụ này sử dụng các dấu ngoặc kép, vì vậy nó cần một chuỗi thoát \', để nhúng các dấu nháy đơn cho can’t và don’t vào chuỗi. Bạn sẽ không cần phải thoát khỏi dấu nháy đơn nếu thay vào đó bạn sử dụng dấu ngoặc kép.

    Nếu bạn cần hiển thị trình tự thoát trong chuỗi, bạn có thể sử dụng raw strings , có tiền tố là r.

    var rawString = r"If you can't explain it simply\nyou don't understand it well enough.";
    
    print(rawString); 
    
    // If you can't explain it simply\nyou don't understand it well enough.
    
    

    Ở đây, Dart được coi `\n`là văn bản bình thường vì chuỗi bắt đầu bằng r.

    Nhấp vào RUN trong DartPad để xem tất cả các chuỗi của bạn trong bảng điều khiển.

    Immutability

    Dart sử dụng các từ khóa constvà finalcác giá trị không thay đổi.

    Sử dụng constcho các giá trị đã biết tại thời điểm biên dịch. Sử dụng finalcho các giá trị không cần biết tại thời điểm biên dịch nhưng không thể gán lại sau khi được khởi tạo.Lưu ý : finalhoạt động giống như valtrong Kotlin hoặc lettrong Swift.

    Bạn có thể sử dụng constvà finalthay cho varvà để phép suy luận kiểu xác định kiểu:

    const speedOfLight = 299792458 ;
    
    in (speedOfLight); // 299792458
    
    

    Ở đây, Dart cho rằng speedOfLight là một biến kiểu int, như bạn có thể thấy trong bảng thông tin của DartPad.

    final cho biết rằng một biến là không thể thay đổi , nghĩa là bạn không thể gán lại final các giá trị. Bạn có thể nêu rõ loại với một trong hai final hoặc const:

    final planet = 'Jupiter';
    
    // planet = 'Mars';
    
    // error: planet chỉ có thể khởi tạo 1 lần
    
    final String moon = 'Europa';
    
    print('$planet has a moon, $moon');
    
    // Jupiter has a moon, Europa
    

    Nullability

    Trước đây, nếu bạn không khởi tạo một biến, Dart đã cấp cho nó giá trị null, có nghĩa là không có gì được lưu trữ trong biến. Tuy nhiên, kể từ Dart 2.12, Dart kết hợp với các ngôn ngữ khác, như Swift và Kotlin, để không thể null theo mặc định.

    Ngoài ra, Dart đảm bảo rằng kiểu không thể null sẽ không bao giờ chứa giá trị null. Điều này được gọi là sound null safety. .

    Thông thường, nếu bạn muốn khai báo một biến, bạn phải khởi tạo nó:

    String middleName = 'May';
    
    print(middleName); // May
    
    

    Tuy nhiên, không phải tất cả mọi người đều có tên đệm, vì vậy việc tạo middleName một giá trị vô hiệu là rất hợp lý. Để nói với Dart rằng bạn muốn cho phép giá trị null, hãy thêm ? vào sau kiểu giữ liểu.

    String? middleName = null;
    
    print(middleName); // null
    
    

    Giá trị mặc định cho kiểu nullable là null, vì vậy bạn có thể đơn giản hóa biểu thức thành như sau:

    String? middleName;
    
    print(middleName); // null
    
    

    Run nó và null in ra bảng điều khiển.

    Toán tử Null-Aware

    Dart có một số toán tử nhận biết null mà bạn có thể sử dụng khi làm việc với các giá trị null.

    Toán tử dấu hỏi kép ?? giống như toán tử Elvis trong Kotlin: Nó trả về toán hạng bên trái nếu đối tượng không phải là null. Nếu không, nó trả về giá trị bên phải:

    var name = middleName ?? 'none';
    
    print(name); // none
    
    

    Vì middleName là null, Dart chỉ định giá trị bên phải của 'none'.

    Toán tử ?. bảo vệ bạn khỏi việc truy cập các thuộc tính của các đối tượng null. Nó trả về null nếu bản thân đối tượng là null. Nếu không, nó trả về giá trị của thuộc tính ở phía bên phải:

    print(middleName?.length); // null
    

    Ngày trước khi chưa có null safety, nếu bạn quên dấu chấm hỏi và viết middleName.length, ứng dụng của bạn sẽ gặp sự cố trong thời gian chạy nếu middleNametrống. Đó không còn là vấn đề nữa, vì Dart giờ đây sẽ cho bạn biết ngay lập tức khi nào bạn cần xử lý các giá trị null.

    Kết thúc phần 2.

  • Cách Flutter render các Widget

    Cách Flutter render các Widget

    Lời mở đầu

    Xin chào!

    Gần đây, Flutter nổi lên và được Google PR như một xu thế của lập trình di động, trên con đường trở thành master fluter thì Widget chính là chìa khóa. Trong Flutter, mọi thứ đều là widget. Và vì vậy điều quan trọng là phải hiểu cách chúng hoạt động và cách Flutter hiển thị các Widget. Trong bài viết này chúng ta sẽ tìm hiểu về Element Tree, Widget Tree và Render Tree.

    Widget tree

    Đầu tiên là Widget tree. Widget tree được sử dụng để cấu hình Giao diện người dùng. Ở đó, bạn có thể định cấu hình các thuộc tính của Widget và định nghĩa nó sẽ hiển thị như thế nào. Tức là code mà bạn viết sẽ tạo lên widget tree.

    Element tree

    Cây thứ hai được gọi là Element tree. Element tree được sử dụng để quản lý và cập nhật mọi thứ. Element tree liên kết vào Widget tree, một phần tử trong Element tree là một thể hiện cụ thể của một widget. Điều này nghe có vẻ quen thuộc nếu bạn biết OOP (Lập trình hướng đối tượng) nơi bạn có các class và object trong đó các object chỉ là các thể hiện của một class.

    Render Tree

    Mảnh ghép còn thiếu cuối cùng là Render Object. Bên trong Render tree là các render object và về cơ bản đây là những gì bạn đang thấy trên màn hình. Bạn không thấy các widget mà bạn thấy các render object. Render object sẽ quan tâm đến kích thước, bố cục và bức tranh thực tế trên màn hình.

    Cách Flutter render các widget – 3 trạng thái

    Flutter trải qua 3 giai đoạn khi hiển thị các widget ra màn hình. Như chúng ta đã biết, đó không phải là những widget mà chúng ta thấy trên màn hình. Đó là các render object. Nhưng làm thế nào Flutter có thể xử lý điều này?

    1. Cấu hình

    Như tôi đã đề cập ở trên, widget tree chứa các widget và ở trạng thái này, tất cả là về cấu hình của các widget của chúng ta. Thông qua một API, bạn chỉ định các thuộc tính và giá trị cho các tiện ích con của mình mà cây tiện ích con sẽ nắm giữ.

    2. Vòng đời

    Tại đây, toàn bộ vòng đời của giao diện người dùng được quản lý. Cũng ở đây, nó xác định các thành phần hiện có trong hệ thống phân cấp giao diện người dùng của bạn. Bạn có thể hình dung trạng thái này giống như chất keo kết dính giữa trạng thái 1 và 3.

    3. Vẽ

    Ở đây Render tree phát huy tác dụng. Tất cả những điều liên quan về vẽ UI sẽ được thực hiện ở đây. Nó quan tâm đến các ràng buộc, phần con của các widget sẽ thực sự trông như thế nào, kích thước của chúng ra sao. Đây là nơi các render object được vẽ trên màn hình.

    Ok, nhưng tại sao lại là 3 cây?

    Bây giờ bạn có thể tự hỏi mình “OK Nhưng tại sao Flutter không chỉ sử dụng các widget và chỉ có một cây?”.

    Thực ra đây là một câu hỏi rất hay.

    Đó là về hiệu năng

    Ok, điều đầu tiên.

    Các widget là bất biến.

    Bạn không thể thay đổi các widget. Và về cơ bản đây là lý do tại sao một widget tree là không đủ. Hãy tưởng tượng bạn thay đổi một widget Text chẳng hạn. Bạn sẽ cần một widget hoàn toàn mới bởi vì bạn không thể thay đổi cùng một widget. Và nếu bạn đang code Flutter, bạn có thể biết UI có thể thay đổi thường xuyên như thế nào.

    Điều này có nghĩa là khi một widget cần thay đổi, đối tượng widget bên trong widget tree sẽ được thay thế hoàn toàn và không thể cập nhật được.

    Và tại đây 2 cây còn lại phát huy tác dụng.

    Cả Element và Render Tree đều được cập nhật bất cứ khi nào có thể thay vì được tạo hoàn toàn mới. Và đây là một sự cải thiện hiệu suất lớn!

    Cập nhật hay không cập nhật?

    Và làm thế nào Flutter quyết định xem Element và Render Object có thể được cập nhật hay không?

    Hãy tưởng tượng bạn có một Widget Text, nơi bạn chỉ cần thay đổi nội dụng văn bản. Lần thứ hai bạn thay đổi nó, Phương thức CanUpdate của Widget sẽ kiểm tra hai thứ và nếu một trong số chúng là đúng, thì Render Object và Element Object sẽ được cập nhật thay vì được tạo ra các đối tượng hoàn toàn mới.

    • RuntimeType (kiểu widget như Text, Column, Container, v.v.) có còn giống nhau không?
    • Key có giống nhau không?

    Trong ví dụ của chúng ta, runtimeType vẫn là Text và vì vậy nó đúng và Đối tượng phần tử sẽ gọi Phương thức updateRenderObject để đảm bảo Render Object được cập nhật thay vì được tạo lại.

    Key là một thuộc tính mà bạn có thể cấp cho hầu hết mọi widget con và giúp Flutter xác định một widget con. Khá hữu ích cho mục đích này.

    Và bạn có thể kiểm tra hành vi này khá dễ dàng. Khi bạn chạy một Ứng dụng Flutter bên trong IDE của mình và mở Flutter DevTools, bạn sẽ thấy chế độ xem sau:

    Ở đây tôi đã chọn một widget Text và bên trong hộp màu đỏ, bạn sẽ thấy Render Object. Và Render Object này có ID bắt đầu bằng ký hiệu #. Khi bạn thay đổi nội dung văn bản cho Widget Text này, ID sẽ KHÔNG thay đổi.

    Tóm lại

    Flutter sẽ cố gắng sử dụng lại càng nhiều tài nguyên (các object trong cây) càng tốt trong khi cố gắng render các đối tượng mới càng ít càng tốt.

    Kết luận

    Flutter khá thông minh trong cách hiển thị các widget!

    Bài viết này đã cung cấp và đi sâu hơn vào đặc tính và cách render UI của Flutter. Tôi hy vọng bây giờ bạn đã có thể hiểu rõ hơn về những gì xảy ra đằng sau quá trình render các Widget. Mong là bạn sẽ thích bài viết này.‌‌