Category: Uncategorized

  • Null safety trong Flutter

    Null safety trong Flutter

    1. Null safety là gì

    a. Tổng quan về null safety

    • Trong các ngôn ngữ lập trình hướng đối tượng có một lỗi mà chúng ta rất hay gặp đó là NullPointerException, lỗi này có thể dẫn đến việc ứng dụng bị crash, ảnh hưởng đến trải nghiệm người dùng.
    • Để có thể giúp cho lập trình viên quản lý được việc nullable của một đối tượng từ đó từ khóa null safety xuất hiện.
    • Null safety cho phép bạn chỉ định việc một đối tượng có thể null hay không ngay từ khi khởi tạo. Và khi bạn muốn dùng nó compiler sẽ nhắc cho bạn để bạn có thể xử lý các exception liên quan đến việc biến có thể bị null

    b. Null safety trong Flutter và Dart

    • Null safety bắt đầu có từ phiên bản Dart 2.12 và Flutter 2
    • Ở trạng thái mặc định những biến của bạn sẽ ở trạng thái non-nullable, điều đó có nghĩa là nó không thể null trừ khi bạn cho phép nó có thể. Với null safety các lỗi liên quan đến null sẽ chuyển từ các lỗi trong quá trình chạy thành các lỗi trong quá trình viết code. Điều này tránh những sai sót không đáng có trong quá trình chạy ứng dụng.
    • Trình biên dịch Dart có thể tối ưu hóa code, điều sẽ giúp cho project nhỏ hơn và chạy nhanh hơn.

    Từ đó chúng ta có bài toán migrate những source code đã được viết trước phiên bản có null safety và biến nó thành source code có sử dụng null safety

    2. Cách migrate từ source code không có null safety

    a. Migrate code theo một thứ tự

    • Nếu chúng ta có sử dụng nhiều package, chúng ta nên lần lượt migrate chúng theo một thứ tự từ package được sử dụng nhiều nhất trong các package khác trước
    Illustration of C/B/A sentence

    b. Các bước migrate code sử dụng tool

    • Để demo cho phần này chúng ta sử dụng một class User
    class User {
      String name;
      int age;
      int yearOfBirth;
      String des;
      User({this.name, this.age});
    
      set setCalculate(int cal){
        yearOfBirth = cal;
      }
    }
    

    Phiên bản của Dart

    sdk: ‘>=2.10.0 <3.0.0'

    • Để bắt đầu chúng ta kiểm tra phiên bản của dart, hãy đảm bảo rằng bạn đang sử dụng phiên bản mới nhất của Dart SDK
    dart --version
    • Tiếp theo là kiểm tra trạng thái của các dependency
    dart pub outdated --mode=null-safety

    Nếu output hiển thị ra như hình dưới đây thì bạn sẽ phải update các dependency

    Output of dart pub outdated
    • Sử dụng tool để migrate code

    Trước khi bắt đầu migrate chúng ta cần biết về một vài hint marker – những thứ sẽ giúp chúng ta đánh dấu null safety cho các biến.

    expression /*!*/Thêm “!” vào trong code của bạn, biến biểu thức đó thành non-nullable
    type /*!*/Đánh dấu đối tượng đó là non-nullable
    /*?*/Đánh dấu đối tượng đó là nullable
    /*late*/Đánh dấu đối tượng đó được khởi tạo sau
    /*late final*/Đánh dấu đối tượng được khởi tạo sau nhưng chỉ được khởi tạo một lần
    /*required*/Đánh dấu tham số bị yêu cầu

    Chúng ta sử dụng lệnh sau ở terminal để bắt đầu quá trình migrate:

    dart migrate

    Sau một thời gian sẽ có một đường link dẫn đến tool migration, giao diện như sau:

    Đến đây chúng ta sẽ bắt đầu dùng những hint marker vào những chỗ được gợi ý dùng

    Ở đây chúng ta mong muốn 3 biến name, age, yearOfBirth đều là non-nullable, vì vậy chúng ta thêm /*!*/ vào các biến đó, biến des có thể null vậy chúng ta thêm /*?*/.

    Để tiến hành xem các thay đổi chúng ta ấn nút Rerun with changes, những thay đổi sẽ có trong code sau khi được migrate sẽ hiện ra

    Giải thích:

    • Hai biến name và age được đánh dấu non-nullable, và được đặt trong cặp dấu ‘{}’ trong constructor, vì vậy từ khóa ‘required’ sẽ được thêm vào để yêu cầu giá trị init cho chúng khi đối tượng User mới được khởi tạo
    • Biến yearOfBirth được đánh dấu là non-nullable, tuy nhiên không xuất hiện trong constructor, vì vậy sẽ có thêm từ khóa ‘late’ từ khóa này
    • Biến des được đánh dấu là nullable do biến đó có thể null.

    Sau khi kết thúc quá trình migration, chúng ta ấn nút ‘Apply migration’ để những thay đổi đó xuất hiện trên code của chúng ta

    class User {
      String name;
      int age;
      late int yearOfBirth;
      String? des;
      User({required this.name, required this.age});
    
      set setCalculate(int cal){
        yearOfBirth = cal;
      }
    }
    

    Trên đây là code của lớp User sau khi được migrate

    Cùng với sự thay đổi ở code, phiên bản của Dart cũng đã được update

    sdk: ‘>=2.12.0 <3.0.0'

    Đến đây là chúng ta đã hoàn thành quá trình migrate một đoạn code Dart sang null safety

    Việc apply null safety đem lại rất nhiều lợi ích cho code của bạn, ngoài việc bắt kịp về mặt công nghệ nó còn giúp code của bạn an toàn hơn, chạy nhanh hơn.

    Tham khảo

  • Mô hình phát triển ứng dụng

    Mô hình phát triển ứng dụng

    Mục đích của blog này là cung cấp phân tích chi tiết về phân tích mô hình phát triển di động và các lựa chọn công nghệ, khuôn khổ khác nhau. Điều này sẽ giúp xác định con đường để chọn dựa trên các thông lệ kinh doanh và kỹ thuật.

    CÁC MÔ HÌNH PHÁT TRIỂN ỨNG DỤNG

    Cung cấp trải nghiệm di động đẳng cấp thế giới cho người dùng cuối liên quan đến các mô hình phát triển Ứng dụng khác nhau. Có 5 mô hình phát triển ứng dụng tồn tại:

    1. Phát triển ứng dụng nền tảng(Native App Development)
    2. Phát triển ứng dụng kết hợp (Hybrid App Development)
    3. Phát triển ứng dụng nền tảng chéo (Cross Platform App Developmen)
    4. Phát triển PWA (PWA Development)
    5. Phát triển ứng dụng dành cho máy tính (Desktop App Development)

    Native App Development
    Phát triển ứng dụng nền tảng liên quan đến việc tạo ra một ứng dụng di động chỉ phù hợp với một nền tảng. Trong trường hợp này, chúng ta sử dụng ngôn ngữ lập trình riêng cho hệ điều hành. Nó có thể là Java hoặc Kotlin cho Android và Swift / Objective-C cho iOS. Native Development có quyền truy cập vào bộ tính năng đầy đủ của thiết bị và do đó cho phép chúng tôi tận dụng chức năng nâng cao có hiệu suất cao.
    Công cụ sử dụng: XCode, ATOM, Android Studio, Android IDE.
    Ví dụ: Ghim sở thích, Cleartrip, v.v.

    Hybrid App Development
    Các ứng dụng kết hợp kết hợp các yếu tố web với các yếu tố di động. Vì vậy, bạn tạo một cơ sở mã bằng cách sử dụng các công nghệ web tiêu chuẩn (HTML, CSS, JavaScript). Sau đó, bạn bọc nó bên trong một vùng chứa gốc – WebView. Các ứng dụng kết hợp chạy trong một trình duyệt toàn màn hình, được gọi là webview, ẩn đối với người dùng. Thông qua các plugin gốc có thể tùy chỉnh, họ có thể truy cập các tính năng gốc của thiết bị di động cụ thể (chẳng hạn như máy ảnh hoặc ID cảm ứng) mà không cần mã lõi bị ràng buộc với thiết bị đó.
    Công cụ sử dụng: Ionic, Cordova, v.v.
    Ví dụ: Evernote, Gmail, v.v.

    Cross Platform App Development
    Phương pháp tiếp cận dành cho nhà phát triển đa nền tảng sử dụng công cụ kết xuất gốc. Cơ sở mã được viết bằng ngôn ngữ lập trình phổ biến (Phụ thuộc vào khuôn khổ) kết nối với các thành phần gốc thông qua cái gọi là cầu nối. Điều này cung cấp UX gần giống với nguyên bản. Ứng dụng đa nền tảng không có móc nối nền tảng. Chúng cung cấp chức năng liền mạch, dễ dàng triển khai và sản xuất hiệu quả về chi phí.
    Các công cụ sử dụng: React Native, NativeScripts, Flutter, v.v.
    Ví dụ: Uber Eats, GoogleAds, Alibaba, Instagram, v.v.

    Progressive Web App Development
    PWAs  cung cấp một cách tiếp cận thay thế cho phát triển ứng dụng di động truyền thống bằng cách bỏ qua việc phân phối cửa hàng ứng dụng và cài đặt ứng dụng. PWA là các ứng dụng web sử dụng một tập hợp các khả năng của trình duyệt – chẳng hạn như làm việc ngoại tuyến, chạy quy trình nền và thêm liên kết vào màn hình chính của thiết bị – để cung cấp trải nghiệm người dùng ‘giống ứng dụng’.
    Công cụ sử dụng: React, Angular JS, v.v.
    Ví dụ: Flipkart, Ali Express, Hầu hết được thấy trong nền tảng thương mại điện tử.

    Desktop App Development

    Phát triển ứng dụng dành cho máy tính đã tăng mức độ áp dụng trong những năm gần đây và có rất nhiều sự phát triển đang diễn ra để tạo ra ứng dụng máy tính đa nền tảng, như chuông, Slack, Spotify, v.v.
    Công cụ sử dụng: Electron cho ứng dụng máy tính đa nền tảng
    Ví dụ: Chime, Slack vv

    CÁCH LỰA CHỌN MÔ HÌNH PHÁT TRIỂN – SO SÁNH

    Trong phần này, chúng tôi sẽ phân tích một số lựa chọn khung và đánh giá chúng về kỹ thuật và phi kỹ thuật, đối với nghiên cứu này, chúng tôi đã chọn 3 mô hình phát triển:

    1. Phát triển ứng dụng nền tảng(iOS và Android)
    2. Ứng dụng kết hợp (Cordova)
    3. Ứng dụng đa nền tảng (React Native, Flutter)

    Phân tích kỹ thuật

    Hiệu suất ứng dụng

    Hình 1.png
    1. Ứng dụng Hyrbid: Hiệu suất của Ứng dụng kết hợp bị tụt hậu so với nền tảng phát triển ứng dụng khác vì đó là chế độ xem web được bao bọc, Vỏ ứng dụng đang tương tác với thành phần gốc và do đó làm giảm hiệu suất ứng dụng tổng thể, đã nói rằng điều này cũng có thể dựa vào cách chúng tôi viết mã chế độ xem web, tất cả chức năng gốc mà chúng tôi đang xây dựng, v.v.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo cung cấp gần với hiệu suất ứng dụng gốc thực và đó là lý do tại sao hiệu suất tốt hơn so với kết hợp. Một trong những lý do là mã chúng ta viết trong khung chọn sẽ được biên dịch nguyên bản, có một chút khác biệt về hiệu suất tùy thuộc vào khung mà chúng ta chọn.
      2.1. Flutter: Flutter sử dụng khung công tác Dart có hầu hết các thành phần có sẵn nên thường không yêu cầu cầu nối để giao tiếp với các mô-đun gốc, Dark Framework sử dụng Skia một công cụ c ++ và tất cả mã được biên dịch sẽ ở trong skia để có hiệu suất tốt hơn.
      2.2. React Native: Kiến trúc React Native chủ yếu dựa vào kiến ​​trúc môi trường thời gian chạy JS, còn được gọi là cầu nối JavaScript. Mã JavaScript được biên dịch thành mã gốc trong thời gian chạy. Tóm lại, React Native sử dụng cầu nối JavaScript để giao tiếp với các mô-đun gốc.
    3. Ứng dụng Native: Bạn có thể không thể đánh bại các ngôn ngữ bản địa. Mã gốc được viết tốt sẽ luôn hoạt động hiệu quả hơn so với mã gốc đã được biên dịch hoặc mã kết hợp.

    Quyền truy cập tính năng gốc
    Hình 2.png

    1. Ứng dụng kết hợp: Hầu hết các ứng dụng kết hợp đều sử dụng wireova hoặc tụ điện cung cấp các API JS cho các ứng dụng vốn thường được gọi là thành phần gốc cho bất kỳ quyền truy cập tính năng Gốc nào, có rất nhiều plugin của bên thứ 3 được yêu cầu cho bất kỳ ứng dụng kết hợp nào để sử dụng cho quyền truy cập tính năng gốc, hầu hết của plugin tính năng gốc sẽ có thể sử dụng được nhưng nếu được yêu cầu, nhà phát triển ứng dụng cũng có thể viết các trình bao bọc của riêng mình xung quanh các chức năng gốc và sau đó đưa chúng vào mã.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo cung cấp quyền truy cập tính năng gần giống như bản địa, rất khó sử dụng:
      2.1. Flutter: Flutter cung cấp các gói chính thức cho hầu hết các tính năng gốc phổ biến và dễ dàng có sẵn trong hệ sinh thái rộng lớn.
      2.2. React Native: React Native có tập hợp các gói bên thứ ba phong phú cũng như một số API có sẵn để truy cập tính năng gốc, dựa vào các plugin của bên thứ ba đi kèm với chi phí bảo trì.
    3. Ứng dụng Native: Bạn có thể không thể đánh bại các ngôn ngữ native, truy cập trực tiếp vào thành phần bản địa và tất cả phần phát hành thành phần mới của hệ điều hành sẽ có thể sử dụng đầu tiên trong Phát triển ứng dụng bản địa.

    Cơ sở mã đơn: Khả năng tái sử dụng
    Hình 3.png

    1. Ứng dụng Hyrbid: Ứng dụng kết hợp có khả năng phục hồi tuyệt vời vì đây là một chế độ xem web sử dụng HTML / CSS / JS (Tương tự như Web), nó đi kèm với cơ sở Mã đơn và mức độ khả năng phục hồi cao hơn.
    2. Cross Platform App: Cross Platform cũng có độ phục hồi tốt nhưng không bằng hybrid, vẫn còn một số đoạn mã sẽ được viết riêng cho iOS / Android.
      2.1. Flutter: Flutter widgets sử dụng thiết kế Material design trên cả hai nền tảng theo mặc định, có nghĩa là cùng một CX sẽ ở đó cho cả hai ứng dụng, nếu chúng ta cần thiết kế kiểu dáng khác nhau dựa trên nền tảng thì sẽ có một số công việc cần thiết dành riêng cho hệ điều hành.
      2.2. React Native: React Native cung cấp một số thành phần giao diện người dùng cơ bản nhưng hầu hết chúng cần tự tạo kiểu, mã có thể được khôi phục vì chúng tôi đang sử dụng langugae lập trình đơn lẻ nhưng khoảng ~ 15-20% mã cần phải dành riêng cho nền tảng.
    3. Ứng dụng Native: Rất khó để duy trì khả năng phục hồi trong phát triển ứng dụng gốc vì khung phát triển, ngôn ngữ khác nhau ở cả hai nền tảng.

    Học một lần, viết ở mọi nơi
    Hình 4.png

    1. Ứng dụng Hyrbid: Ứng dụng kết hợp tất cả đều được viết trong khung phát triển web và học một công nghệ giúp tạo ứng dụng trên tất cả các nền tảng.
    2. Ứng dụng đa nền tảng: Nền tảng chéo cũng sử dụng khái niệm tương tự, khung công tác khác nhau có các lựa chọn ngôn ngữ khác nhau nhưng ngôn ngữ duy nhất của nó được sử dụng để tạo ứng dụng trên tất cả các nền tảng. Ví dụ: Flutter sử dụng phi tiêu và React Native sử dụng JavaScript.
    3. Ứng dụng Native: Tốt trong việc phát triển ứng dụng Gốc như một khung phát triển, ngôn ngữ khác nhau ở cả hai nền tảng, chúng ta cần tìm hiểu sâu về cả ngôn ngữ và nền tảng, ví dụ: iOS sử dụng XCode + Swift, Android sử dụng Kotling / Java.

    Thành phần kiểu giao diện người dùng

    Hình 5.png
    1. Ứng dụng Hyrbid: Ứng dụng hỗn hợp có thành phần giao diện người dùng được tạo kiểu sẵn phụ thuộc vào khung giao diện người dùng bạn chọn, những cái nổi tiếng là ion. Những thành phần đó áp dụng cho cả hai nền tảng và mang lại trải nghiệm giống như bản địa về Kiểu giao diện người dùng.
    2. Ứng dụng đa nền tảng:
      2.1. Flutter: Flutter đã tích hợp sẵn các widget sử dụng thiết kế Material design và một số phong cách cupertino (cụ thể cho iOS) với tất cả các widget tích hợp này, bạn có thể tạo ứng dụng tuyệt vời mà chỉ cần tạo kiểu thủ công rất ít.
      2.2. React Native: Các bản soạn phong cách giao diện người dùng phong phú có sẵn, rất nhiều plugin của bên thứ ba có thể sử dụng được yêu cầu nhà phát triển ứng dụng cũng phải viết kiểu riêng của họ.
    3. Ứng dụng Native: Việc tuân thủ giao diện người dùng ứng dụng gốc là phổ biến và có thể sử dụng được cho tất cả các nền tảng gốc.

    Thư viện bên thứ ba / Hệ sinh thái
    Hình 6.png

    1. Tất cả các nền tảng: Tất cả các nền tảng đều đã trưởng thành và có sẵn hệ sinh thái tốt nếu nhà phát triển gặp khó khăn.

    Phân tích phi kỹ thuật

    Mức độ phổ biến / Hỗ trợ cộng đồng

    Hình 7.png
    1. Ứng dụng Hyrbid: Ứng dụng kết hợp có Mức độ phổ biến đã giảm dần trong một số năm khi các nhà phát triển trong gần như cùng thời gian có thể tạo ứng dụng giống như bản địa bằng cách chọn nền tảng phát triển ứng dụng khác.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo đã cho thấy sự phổ biến to lớn sau khi ra mắt công khai phản ứng gốc vào năm 2015
      2.1. Flutter: Flutter được google ra mắt công khai vào khoảng năm 2017 và đã đạt được sự phát triển vượt bậc và rất được nhiều công ty lớn chấp nhận để tạo ứng dụng đa nền tảng.
      2.2. React Native: React Native dẫn đầu về phát triển ứng dụng, rất nhiều hỗ trợ từ cộng đồng, stackoverflow, github repositry (80k Stars trong github)
    3. Ứng dụng Native: Mặc dù việc phát triển ứng dụng khác đã phổ biến nhưng sự phát triển ứng dụng gốc vẫn đang bùng nổ ngày nay vì trải nghiệm và hiệu suất ứng dụng liền mạch. Rất nhiều công ty lớn đã thử sử dụng nền tảng kết hợp / chéo và cuối cùng đã tạo ra ứng dụng ở dạng bản địa. Ứng dụng ban đầu Ex Cleartrip là ứng dụng lai và sau đó được viết lại hoàn toàn bằng bản địa.
    Hình 8.png

    Thời gian đưa ra thị trường
    Hình 9.png

    1. Ứng dụng Hyrbid: Thời gian đưa ra thị trường sẽ ít hơn đối với việc phát triển Ứng dụng lai vì hệ số xem web và khả năng tái sử dụng của nó rất cao cũng như cơ sở mã duy nhất giúp thời gian đưa ra thị trường nhanh hơn.
    2. Ứng dụng Cross Platform: Thời gian tiếp thị trên nhiều nền tảng vẫn ít hơn so với ứng dụng gốc, lý do để đặt nó không ngang bằng với ứng dụng kết hợp là vẫn còn đường cong học tập ban đầu có thể làm tăng thời gian tiếp thị cho các ứng dụng này.
    3. Ứng dụng Native: Đường cong học tập, duy trì hai ngăn xếp khác nhau, cơ sở mã, v.v. khiến thời gian đưa ra thị trường chậm hơn tất cả các tùy chọn khác.

    Chi phí phát triển
    Hình 10.png

    1. Ứng dụng Hyrbid: Chi phí phát triển sẽ thấp hơn nhiều vì cùng một nhóm triển khai mã ứng dụng web có thể được sử dụng để tạo các trang web trong ứng dụng kết hợp.
    2. Ứng dụng Cross Platform: Chi phí đa nền tảng sẽ cao hơn một chút, vì sẽ có một số đường học tập về công nghệ mới và cũng yêu cầu một số kiến ​​thức phát triển ứng dụng gốc.
    3. Ứng dụng Native: Chi phí phát triển cao hơn vì bảo trì / phát triển hai cơ sở mã yêu cầu kiến ​​thức chuyên môn đặc biệt.

    Thuê mướn
    Hình 11.png

    1. Ứng dụng Hyrbid: Không cần thuê bất kỳ người mới nào vì cùng một nhà phát triển đang phát triển ứng dụng web có thể viết ứng dụng kết hợp.
    2. Ứng dụng đa nền tảng: có thể yêu cầu hoặc không vì nó yêu cầu phát triển khung cụ thể, chúng tôi đã thấy việc thuê tổ chức chỉ phản ứng nhà phát triển bản địa hoặc nhà phát triển chập chờn.
    3. Ứng dụng Native: Cần thuê hoặc duy trì hai nhóm dành riêng cho Android và iOS.

    Chi phí bảo trì
    Hình 12.png

    Thời gian phát triển
    Hình 13.png

    Trưởng thành
    Hình 14.png

    1. Ứng dụng Hyrbid: Ứng dụng kết hợp vẫn là ứng dụng đầu tiên ra đời sau khi bản địa vẫn chưa hoàn thiện về các tính năng truy cập bản địa.
    2. Ứng dụng nền tảng chéo: Nền tảng chéo đã cho thấy sự tuyệt vời và rất nhiều ứng dụng đa nền tảng như uber eat, instagram, quảng cáo goodle, alibaba được tạo ra ở đó ứng dụng trên ứng dụng đa nền tảng.
      2.1. Flutter: Flutter được google ra mắt công chúng vào khoảng năm 2017 và đã đạt được sự phát triển vượt bậc và nó rất được nhiều công ty lớn chấp nhận để tạo ra ứng dụng đa nền tảng, vì nó vẫn còn ở giai đoạn đầu, tôi chỉ xếp nó sau RN, điều này chắc chắn sẽ xảy ra trong thời gian tới.
      2.2. React Native: React Native dẫn đầu về phát triển ứng dụng, rất nhiều hỗ trợ từ cộng đồng, stackoverflow, github repositry (80k Stars trong github).
    3. Ứng dụng Native: Mặc dù việc phát triển ứng dụng khác đã phổ biến nhưng sự phát triển ứng dụng gốc vẫn đang bùng nổ ngày nay vì trải nghiệm và hiệu suất ứng dụng liền mạch. Các công ty phụ thuộc rất nhiều vào trải nghiệm di động chủ yếu chọn phát triển ứng dụng gốc như một cách thuần thục để tạo ứng dụng cho người dùng

    HIGH LEVEL DECISION TREE

    Hình 15.png
  • Differentiate between ephemeral state and app state.

    Theo nghĩa rộng nhất có thể, trạng thái của ứng dụng là mọi thứ tồn tại trong bộ nhớ khi ứng dụng đang chạy. Điều này bao gồm nội dung của ứng dụng, tất cả các biến mà khung Flutter lưu giữ về giao diện người dùng, trạng thái hoạt ảnh, kết cấu, phông chữ, v.v. Mặc dù định nghĩa rộng nhất có thể này về trạng thái là hợp lệ, nhưng nó không hữu ích lắm cho việc kiến trúc một ứng dụng.

    Đầu tiên, bạn thậm chí không quản lý một số trạng thái (như kết cấu). Khung xử lý những điều đó cho bạn. Vì vậy, một định nghĩa hữu ích hơn về trạng thái là “bất kỳ dữ liệu nào bạn cần để xây dựng lại giao diện người dùng của bạn bất kỳ lúc nào”. Thứ hai, trạng thái mà bạn tự quản lý có thể được tách thành hai loại khái niệm: ephemeral state and app state.

    Ephemeral state

    Ephemeral state (đôi khi được gọi là UI state or local state) là trạng thái bạn có thể chứa gọn gàng trong một tiện ích con.

    Đây là một định nghĩa mơ hồ, vì vậy đây là một vài ví dụ:

    • Page hiện tại trong PageView
    • Tiến hành hiện tại trong một animation phức tạp.
    • Tab được chọn hiện tại trong BottomNavigationBar.

    Các phần khác của widget tree hiếm khi cần phải truy cập vào loại trạng thái này. Không cần phải tuần tự hóa nó và nó không thay đổi theo những cách phức tạp.

    Nói cách khác, không cần sử dụng các kỹ thuật quản lý trạng thái (ScopedModel, Redux, v.v.) trên loại trạng thái này. Tất cả những gì bạn cần là một StatefulWidget.

    Dưới đây, bạn thấy cách mục hiện được chọn trong thanh điều hướng dưới cùng được giữ trong trường _index của lớp _MyHomepageState. Trong ví dụ này, _index là ephemeral state.

    Ở đây, việc sử dụng setState() và một trường bên trong lớp StatefulWidget’s State là hoàn toàn tự nhiên. Không phần nào khác trong ứng dụng của bạn cần truy cập _index. Biến chỉ thay đổi bên trong tiện ích MyHomepage. Và, nếu người dùng đóng và khởi động lại ứng dụng, bạn đừng bận tâm rằng _index đặt lại về 0.

    App state

    Đây không phải là ephemeral state mà bạn muốn chia sẻ trên nhiều phần của ứng dụng và bạn muốn giữ lại giữa các phiên của người dùng, chúng ta gọi là application state (đôi khi còn được gọi shared state).

    Ví dụ về application state:

    • Sở thích của người sử dụng
    • Thông tin đăng nhập
    • Thông báo trong một ứng dụng mạng xã hội
    • Giỏ hàng trong ứng dụng thương mại điện tử
    • Trạng thái đã đọc / chưa đọc của các bài báo trong một ứng dụng tin tức

    Để quản lý app state, bạn sẽ muốn nghiên cứu các tùy chọn của mình. Lựa chọn của bạn phụ thuộc vào mức độ phức tạp và bản chất của ứng dụng, trải nghiệm trước đây của nhóm bạn và nhiều khía cạnh khác.

    Không có quy tắc rõ ràng.

    Để rõ ràng hơn, bạn có thể sử dụng State và setState() để quản lý tất cả các trạng thái trong ứng dụng của mình. Trên thực tế, nhóm Flutter thực hiện điều này trong nhiều mẫu ứng dụng đơn giản (bao gồm cả ứng dụng dành cho người mới bắt đầu mà bạn nhận được sau mỗi lần tạo Flutter).

    Nó cũng đi theo hướng khác. Ví dụ: bạn có thể quyết định rằng — trong ngữ cảnh của ứng dụng cụ thể của bạn — tab đã chọn trong thanh điều hướng dưới cùng không phải là trạng thái tạm thời. Bạn có thể cần phải thay đổi nó từ bên ngoài lớp học, giữ nó giữa các phiên, v.v. Trong trường hợp đó, biến _index là trạng thái ứng dụng.

    Không có quy tắc chung, rõ ràng nào để phân biệt một biến cụ thể là ephemeral sate hay trạng thái ứng dụng. Đôi khi, bạn sẽ phải cấu trúc lại cái này thành cái khác. Ví dụ: bạn sẽ bắt đầu với một số trạng thái rõ ràng là tạm thời, nhưng khi ứng dụng của bạn phát triển về các tính năng, nó có thể cần được chuyển sang app state.

    Vì lý do đó, hãy lấy sơ đồ sau với một lượng muối lớn:

    Cảm ơn mọi người đã theo dõi.

  • Stateful Builder có thể bước thay thế vị trí của Stateful Widget không?

    Để hiểu điều này trước tiên chúng ta hãy hiểu Stateful Builder là gì?

    StatefulBuilder là một widget có trạng thái có thể thay đổi (trạng thái có thể thay đổi), điều làm cho nó trở nên đặc biệt là chỉ xây dựng lại widget cụ thể được bao bọc trong Stateful Builder. Rất may là không có việc xây dựng lại toàn bộ widget, không có vấn đề gì widget được bao bọc trong Stateful Builder nằm trong Stateless hay stateful Widget.

    Bây giờ câu hỏi đặt ra là chúng ta biết rằng nó không thể xây dựng lại Stateless Widget cho đến khi có một phiên bản mới. (SetState không hoạt động)

    Stateful builder có sức mạnh của StateSetter chức năng được truyền cho trình xây dựng được sử dụng để gọi một quá trình xây dựng lại chính nó thay vì một trạng thái điển hình của toàn bộ Widget.

    StatefulBuilder(
    builder: (BuildContext context, StateSetter setState) => Checkbox(
      value: isChecked,
      onChanged: (value) {
        setState(() {
          isChecked = value;
        });
      },
    ),
    ),

    GHI NHỚ

    Bất kỳ biến nào đại diện cho trạng thái nên được giữ bên ngoài hàm build có nghĩa là Bạn phải khai báo biến bên ngoài phương thức build để thay đổi xảy ra.

    Như tôi đã đề cập ở trên, nó chỉ xây dựng lại một widget cụ thể. Nó chỉ cung cấp một thể hiện mới (State) cho widget cụ thể đó. vì vậy bạn đã gói tất cả widget (chế độ xem) mà bạn muốn thấy sự thay đổi trong StatefulBuilder.

    Để hiểu Tại sao Stateful Builder không thể thay thế một Stateful widget. Đầu tiên chúng ta hãy hiểu điều gì tạo nên sự khác biệt giữa Widget không trạng thái và Widget trạng thái.

    Bạn có nhận thấy trong phương pháp xây dựng ghi đè không trạng thái và mặt khác trong tiện ích con Stateful trước tiên sẽ ghi đè createState và sau đó ghi đè phương thức build? Điều xảy ra ngầm là khi widget không trạng thái nhận được một phiên bản mà hàm xây dựng của nó được gọi. và trong widget trạng thái trước tiên, hãy tạo thể hiện trạng thái để xây dựng hàm.Đó là cách setState đang hoạt động. Sau khi chạy setStatecreateState đưa ra một thể hiện mới để xây dựng lại phương thức và tiện ích con.

    Giờ chúng ta sẽ hiểu tại sao Stateful Builder không thể thay thế tiện ích Stateful.

    Nếu nó đang xây dựng lại phần cụ thể của cây widget. Tại sao chúng ta không thể sử dụng thay thế cho Stateful Widget?

    Nếu bạn lưu trữ bất kỳ trạng thái nào trong các biến cá thể trong widget không trạng thái, nó có thể bị mất bất cứ lúc nào vì hàm tạo cho widget không trạng thái có thể được gọi lặp lại, giống như phương thức build. Chỉ có Trạng thái phiên bản liên quan của tiện ích con Stateful được lưu giữ bởi Flutter.

    StatefulWidget vẫn được giữ nguyên trạng thái của nó ngay cả trong các lần xây dựng lại, đó là lý do tại sao đôi khi bạn thấy các hoạt động sai của TextEditorController trong Stateless Widget

    Hoặc nếu bạn không đủ khả năng sử dụng StatefulBuilder với Stateless Widget thì bạn phải lưu trạng thái bằng các khóa Toàn cục.

    Cảm ơn bạn đã đọc bài viết này nếu bạn thấy bất cứ điều gì có thể được cải thiện, vui lòng cho tôi biết, tôi rất muốn cải thiện.

  • Flutter: Quản lý state bằng provider.

    Ví dụ.

    Để minh họa, hãy xem xét ứng dụng đơn giản sau.

    Ứng dụng có hai màn hình riêng biệt: danh mục và giỏ hàng (được đại diện bởi các tiện ích MyCatalog và MyCart, tương ứng). Nó có thể là một ứng dụng mua sắm, nhưng bạn có thể tưởng tượng cấu trúc tương tự trong một ứng dụng mạng xã hội đơn giản (thay thế danh mục cho “tường” và giỏ hàng cho “yêu thích”).

    Màn hình danh mục bao gồm thanh ứng dụng tùy chỉnh (MyAppBar) và dạng xem cuộn của nhiều mục danh sách (MyListItems).

    Đây là ứng dụng được hình dung dưới dạng cây tiện ích con.

    Vì vậy, chúng ta có ít nhất 5 lớp con của Widget. Nhiều người trong số họ cần quyền truy cập vào trạng thái “thuộc về” ở nơi khác. Ví dụ: mỗi MyListItem cần có khả năng tự thêm vào giỏ hàng. Nó cũng có thể muốn xem liệu mặt hàng được hiển thị hiện đã có trong giỏ hàng hay chưa.

    Điều này đưa chúng ta đến câu hỏi đầu tiên: chúng ta nên đặt trạng thái hiện tại của giỏ hàng ở đâu?

    Đưa trạng thái lên

    Trong Flutter, bạn nên giữ trạng thái ở trên các widget sử dụng nó.

    Tại sao? Trong các khung công tác khai báo như Flutter, nếu bạn muốn thay đổi giao diện người dùng, bạn phải xây dựng lại nó. Không có cách nào dễ dàng để có MyCart.updateWith (somethingNew). Nói cách khác, thật khó để thay đổi thứ bậc một tiện ích từ bên ngoài, bằng cách gọi một phương thức trên đó. Và ngay cả khi bạn có thể làm cho điều này thành công, bạn sẽ chiến đấu với khuôn khổ thay vì để nó giúp bạn.

    Ngay cả khi code trên hoạt động, bạn sẽ phải xử lý những điều sau trong tiện ích MyCart:

    Bạn sẽ cần phải xem xét trạng thái hiện tại của giao diện người dùng và áp dụng dữ liệu mới cho nó. Thật khó để tránh lỗi theo cách này.

    Trong Flutter, bạn tạo một widget mới mỗi khi nội dung của nó thay đổi. Thay vì MyCart.updateWith (somethingNew) (một cuộc gọi phương thức), bạn sử dụng MyCart (nội dung) (một phương thức khởi tạo). Vì bạn chỉ có thể tạo các tiện ích con mới trong các phương pháp tạo của cha mẹ chúng, nên nếu bạn muốn thay đổi nội dung, tiện ích con đó phải ở chế độ gốc của MyCart trở lên.

    Bây giờ MyCart chỉ có một đường dẫn mã để xây dựng bất kỳ phiên bản giao diện người dùng nào.

    Trong ví dụ của chúng ta, nội dung cần phải tồn tại trong MyApp. Bất cứ khi nào nó thay đổi, nó sẽ xây dựng lại MyCart từ bên trên (sẽ nói thêm về điều đó sau). Do đó, MyCart không cần phải lo lắng về vòng đời — nó chỉ khai báo những gì sẽ hiển thị cho bất kỳ nội dung nhất định nào. Khi điều đó thay đổi, tiện ích MyCart cũ sẽ biến mất và được thay thế hoàn toàn bằng tiện ích mới.

    Đây là ý của chúng ta khi nói rằng widget là bất biến. Chúng không thay đổi — chúng được thay thế.

    Bây giờ chúng ta đã biết nơi đặt trạng thái của giỏ hàng, hãy xem cách truy cập vào nó.

    Truy cập trạng thái

    Khi người dùng nhấp vào một trong các mặt hàng trong danh mục, mặt hàng đó sẽ được thêm vào giỏ hàng. Nhưng kể từ khi giỏ hàng nằm trên MyListItem, chúng ta làm điều đó như thế nào?

    Một tùy chọn đơn giản là cung cấp một lệnh gọi lại mà MyListItem có thể gọi khi nó được nhấp vào. Các hàm của Dart là các đối tượng hạng nhất, vì vậy bạn có thể chuyển chúng theo bất kỳ cách nào bạn muốn. Vì vậy, bên trong MyCatalog, bạn có thể xác định những điều sau:

    Điều này hoạt động tốt, nhưng đối với một trạng thái ứng dụng mà bạn cần sửa đổi từ nhiều nơi khác nhau, bạn sẽ phải chuyển qua nhiều lần gọi lại — điều này sẽ cũ đi khá nhanh.

    May mắn thay, Flutter có các cơ chế để các widget cung cấp dữ liệu và dịch vụ cho con cháu của chúng (nói cách khác, không chỉ con cái của chúng mà bất kỳ widget nào bên dưới chúng). Như bạn mong đợi từ Flutter, nơi mọi thứ đều là Widget, các cơ chế này chỉ là những loại widget đặc biệt — InheritedWidget, InheritedNotifier, InheritedModel, v.v. Chúng ta sẽ không đề cập đến những điều đó ở đây, vì chúng hơi thấp so với những gì chúng ta đang cố gắng thực hiện.

    Thay vào đó, chúng ta sẽ sử dụng một gói hoạt động với các widget cấp thấp nhưng dễ sử dụng. Nó được gọi là nhà cung cấp.

    Trước khi làm việc với nhà cung cấp, đừng quên thêm phần phụ thuộc vào nhà cung cấp đó vào pubspec.yaml của bạn.

    Bây giờ bạn có import ‘package:provider/provider.dart’; và bắt đầu xây dựng.

    Với provider, bạn không cần phải lo lắng về các cuộc gọi lại hoặc InheritedWidgets. Nhưng bạn cần hiểu 3 khái niệm:

    • ChangeNotifier
    • ChangeNotifierProvider
    • Consumer

    ChangeNotifier

    ChangeNotifier là một lớp đơn giản có trong Flutter SDK cung cấp thông báo thay đổi cho người nghe của nó. Nói cách khác, nếu thứ gì đó là ChangeNotifier, bạn có thể đăng ký các thay đổi của nó. (Đây là một dạng có thể quan sát được, dành cho những người quen thuộc với thuật ngữ này.)

    Trong provider, ChangeNotifier là một cách để đóng gói trạng thái ứng dụng của bạn. Đối với các ứng dụng rất đơn giản, bạn có thể sử dụng với một ChangeNotifier duy nhất. Trong những cái phức tạp, bạn sẽ có một số mô hình và do đó là một số ChangeNotifier. (Bạn hoàn toàn không cần sử dụng ChangeNotifier với provider, nhưng đó là một lớp dễ làm việc.)

    Trong ví dụ về ứng dụng mua sắm của chúng ta, chúng ta muốn quản lý trạng thái của giỏ hàng trong ChangeNotifier. Chúng ta tạo một lớp mới để mở rộng nó, giống như vậy

    Code duy nhất dành riêng cho ChangeNotifier là lệnh gọi tới notificationListists (). Gọi phương thức này bất cứ khi nào mô hình thay đổi theo cách có thể thay đổi giao diện người dùng của ứng dụng của bạn. Mọi thứ khác trong CartModel là chính mô hình và logic của nó.

    ChangeNotifierProvider

    ChangeNotifierProvider là widget cung cấp một phiên bản của ChangeNotifier cho con cháu của nó. Nó đến từ provider package.

    Chúng ta đã biết nơi đặt ChangeNotifierProvider: phía trên các widget cần truy cập nó. Trong trường hợp của CartModel, điều đó có nghĩa là ở đâu đó trên cả MyCart và MyCatalog.

    Bạn không muốn đặt ChangeNotifierProvider cao hơn mức cần thiết (vì bạn không muốn làm giảm hiệu suất). Nhưng trong trường hợp của chúng ta, tiện ích duy nhất nằm trên cả MyCart và MyCatalog là MyApp.

    Consumer

    Bây giờ CartModel đã được cung cấp cho các widget trong ứng dụng của chúng ta thông qua khai báo ChangeNotifierProvider ở trên cùng, chúng ta có thể bắt đầu sử dụng nó.

    Điều này được thực hiện thông qua tiện ích Consumer widget.

    Chúng ta phải chỉ định loại mô hình mà chúng ta muốn truy cập. Trong trường hợp này, chúng ta muốn CartModel, vì vậy chúng ta viết Consumer <CartModel>. Nếu bạn không chỉ định chung chung (<CartModel>), provider sẽ không thể giúp bạn. Provider dựa trên các loại và nếu không có loại, nó không biết bạn muốn gì.

    Đối số bắt buộc duy nhất của Consumer Widget là trình tạo. Builder là một hàm được gọi bất cứ khi nào ChangeNotifier thay đổi. (Nói cách khác, khi bạn gọi thông báo () trong mô hình của mình, tất cả các phương thức trình tạo của tất cả Consumer Widget tương ứng sẽ được gọi.)

    Trình tạo được gọi với ba đối số. Điều đầu tiên là ngữ cảnh, mà bạn cũng nhận được trong mọi phương pháp xây dựng.

    Đối số thứ hai của hàm trình tạo là phiên bản của ChangeNotifier. Đó là những gì chúng ta đã yêu cầu ngay từ đầu. Bạn có thể sử dụng dữ liệu trong mô hình để xác định giao diện người dùng sẽ trông như thế nào tại bất kỳ điểm nhất định nào.

    Đối số thứ ba là con, ở đó để tối ưu hóa. Nếu bạn có một cây con tiện ích con lớn trong Consumer Widget không thay đổi khi mô hình thay đổi, bạn có thể tạo nó một lần và lấy nó thông qua trình tạo.

  • Flutter Custom Radio Button với Custom Shapes

    Sự phát triển của Flutter đang trở nên phổ biến và ngày càng phổ biến hơn do có nhiều tùy biến, widget tùy chỉnh và cách tiếp cận rất dễ thực hiện. Hôm nay chúng ta sẽ tìm hiểu cách tạo nút radio tùy chỉnh Flutter với các hình dạng tùy chỉnh như hộp đựng hình vuông, hộp đựng hình tròn hoặc biểu tượng. Tôi đã cố gắng tạo ra các phương pháp khác nhau để chúng có thể được sử dụng theo nhu cầu và nhà phát triển sẽ học từ hướng dẫn này cũng có thể có ý tưởng tạo nút radio tùy chỉnh rung của riêng mình, với hình dạng hoặc tiện ích theo yêu cầu của mình.

    Ví dụ về Nút radio tùy chỉnh Flutter này sẽ tăng tốc độ phát triển của bạn và tiết kiệm thời gian của bạn bằng cách gọi nó trực tiếp như một phần tử con của bất kỳ tiện ích nào hoặc bằng cách thực hiện các phương thức tĩnh khi nó cần thiết. Bởi vì không có ứng dụng nào có thể được thực hiện mà không thực hiện bất kỳ widegt tùy chỉnh nào, tất cả các widget được tạo sẵn không phải lúc nào cũng giúp ích được. Vì vậy, học cách chế tạo widget tùy chỉnh là phần quan trọng nhất của quá trình phát triển Flagship.

    Hãy bắt đầu thực hiện từng bước để chúng ta có thể hiểu những gì cần phải làm để đạt được kết quả đầu ra mong muốn. Đầu tiên, chúng ta sẽ tạo widget home, nơi chúng ta sẽ hiển thị các nút radio tùy chỉnh.

    Nó là một widget toàn diện có khả năng xử lý tất cả các chức năng của nút radio, như hiển thị thiết kế tùy chỉnh, sự kiện chạm và lựa chọn, v.v.

    home_screen.dart

    class HomeScreen extends StatefulWidget {
      const HomeScreen({Key? key}) : super(key: key);
      @override
      _HomeScreenState createState() => _HomeScreenState();
    }
    class _HomeScreenState extends State {
      List radioOptions = [];
      @override
      void initState() {
        super.initState();
        radioOptions.add(RadioButtonModel(false, 'A', 'Option A'));
        radioOptions.add(RadioButtonModel(false, 'B', 'Option B'));
        radioOptions.add(RadioButtonModel(false, 'C', 'Option C'));
        radioOptions.add(RadioButtonModel(false, 'D', 'Option D'));
      }
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Custom Check Box Example'),
          ),
          body: MyRadioButtonWidget(
            options: radioOptions,
            onItemSelected: (index) {
              print('I am index: $index');
            },
          ),
        );
      }
    }

    Giờ hãy tạo nút radio tùy chỉnh

    Lớp này chịu trách nhiệm xử lý tất cả các hoạt động của nút radio tùy chỉnh, như thiết kế, sự kiện và gọi lại. Nếu bạn nhận thấy, tôi đã thêm một chức năng gọi lại để trả về chỉ mục mục được nhấn hiện tại. Trong trường hợp bạn muốn làm cho lớp nút radio tùy chỉnh của mình độc lập và chung chung, bạn có thể tạo cho các sự kiện theo yêu cầu của mình.

    final Function(int)? onItemSelected;

    my_radio_button_widget.dart

    class MyRadioButtonWidget extends StatefulWidget {
      final List<RadioButtonModel>? options;
      final Function(int)? onItemSelected;
      const MyRadioButtonWidget({Key? key, this.options, this.onItemSelected})
          : super(key: key);
      @override
      createState() {
        return MyRadioButtonWidgetState();
      }
    }
    class MyRadioButtonWidgetState extends State<MyRadioButtonWidget> {
      @override
      Widget build(BuildContext context) {
        return ListView.builder(
          itemCount: widget.options!.length,
          itemBuilder: (BuildContext context, int index) {
            return InkWell(
              onTap: () {
                setState(() {
                  for (var element in widget.options!) {
                    element.isSelected = false;
                  }
                  widget.options![index].isSelected = true;
                  widget.onItemSelected!(index);
                });
              },
              child: CircleRadioButtonItem(widget.options![index]),
            );
          },
        );
      }
    }

    Radio Button Model

    class RadioButtonModel {
      bool isSelected;
      final String buttonText;
      final String text;
      RadioButtonModel(this.isSelected, this.buttonText, this.text);
    }

    Bây giờ chúng tôi sẽ tạo các hình dạng tùy chỉnh cho nút radio của chúng ta, ta có nhiều tùy chọn, bạn có thể tìm thấy ba lớp khác nhau để thể hiện phong cách khác nhau. bằng cách sử dụng ý tưởng này, bạn có thể tự tạo và sửa đổi các ví dụ đã cho theo ý muốn.

    Nút radio vuông

    Ở đây chúng tôi đang tạo hộp chứa hình vuông với văn bản bên trong nó.

    class SquareRadioButtonItem extends StatelessWidget {
      final RadioButtonModel _item;
      const SquareRadioButtonItem(this._item, {Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.all(15.0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Container(
                height: 50.0,
                width: 50.0,
                child: Center(
                  child: Text(_item.buttonText,
                      style: TextStyle(
                          color: _item.isSelected ? Colors.white : Colors.black,
                          //fontWeight: FontWeight.bold,
                          fontSize: 18.0)),
                ),
                decoration: BoxDecoration(
                  color: _item.isSelected ? Colors.blueAccent : Colors.transparent,
                  border: Border.all(
                      width: 1.0,
                      color: _item.isSelected ? Colors.blueAccent : Colors.grey),
                  borderRadius: const BorderRadius.all(Radius.circular(2.0)),
                ),
              ),
              Container(
                margin: const EdgeInsets.only(left: 10.0),
                child: Text(_item.text),
              )
            ],
          ),
        );
      }
    }

    Nút radio tròn

    class CircleRadioButtonItem extends StatelessWidget {
      final RadioButtonModel _item;
      const CircleRadioButtonItem(this._item, {Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.all(15.0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              Container(
                height: 50.0,
                width: 50.0,
                child: Center(
                  child: Text(_item.buttonText,
                      style: TextStyle(
                          color: _item.isSelected ? Colors.white : Colors.black,
                          //fontWeight: FontWeight.bold,
                          fontSize: 18.0)),
                ),
                decoration: BoxDecoration(
                  shape: BoxShape.circle,
                  color: _item.isSelected ? Colors.blueAccent : Colors.transparent,
                  border: Border.all(
                      width: 1.0,
                      color: _item.isSelected ? Colors.blueAccent : Colors.grey),
                ),
              ),
              Container(
                margin: const EdgeInsets.only(left: 10.0),
                child: Text(_item.text),
              )
            ],
          ),
        );
      }
    }

    Icon Radio

    class IconRadioButtonItem extends StatelessWidget {
      final RadioButtonModel _item;
      const IconRadioButtonItem(this._item, {Key? key}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        return Container(
          margin: const EdgeInsets.all(15.0),
          child: Row(
            mainAxisSize: MainAxisSize.max,
            children: [
              _item.isSelected
                  ? const Icon(Icons.circle)
                  : const Icon(Icons.circle_outlined),
              Container(
                margin: const EdgeInsets.only(left: 10.0),
                child: Text(_item.text),
              )
            ],
          ),
        );
      }
    }

  • Difference between Multiprogramming, multitasking, multithreading, and multiprocessing

    1. Multiprogramming

    Trong một hệ thống máy tính hiện đại, thường có một số tiến trình ứng dụng đồng thời muốn thực thi. Giờ đây, hệ điều hành có trách nhiệm quản lý tất cả các quy trình một cách hiệu quả và hiệu quả.

    Một trong những khía cạnh quan trọng nhất của hệ điều hành là đa chương trình.

    Trong một hệ thống máy tính, có nhiều quá trình đang chờ được thực thi, tức là chúng đang đợi khi CPU sẽ được cấp phát cho chúng và chúng bắt đầu thực thi. Các quy trình này còn được gọi là công việc. Bây giờ bộ nhớ chính quá nhỏ để chứa tất cả các quy trình hoặc công việc này vào đó. Do đó, các quy trình này ban đầu được lưu giữ trong một khu vực được gọi là nhóm công việc. Nhóm công việc này bao gồm tất cả các quy trình đang chờ cấp phát bộ nhớ chính và CPU.

    CPU chọn một công việc trong số tất cả các công việc đang chờ này, đưa nó từ nhóm công việc đến bộ nhớ chính và bắt đầu thực thi nó. Bộ xử lý thực hiện một công việc cho đến khi nó bị gián đoạn bởi một số yếu tố bên ngoài hoặc nó thực hiện một tác vụ I / O.

    Hệ thống không đa chương trình đang hoạt động:

    • Trong một hệ thống không đa lập trình, Ngay sau khi một công việc rời khỏi CPU và thực hiện một số tác vụ khác (ví dụ I / O), CPU sẽ trở nên nhàn rỗi. CPU tiếp tục chờ và đợi cho đến khi công việc này (đã được thực thi trước đó) quay trở lại và tiếp tục thực hiện với CPU. Vì vậy, CPU vẫn miễn phí trong tất cả thời gian này.
    • Bây giờ nó có một nhược điểm là CPU không hoạt động trong một thời gian rất dài. Ngoài ra, các công việc khác đang chờ thực hiện có thể không có cơ hội thực hiện vì CPU vẫn được phân bổ cho công việc trước đó. Điều này đặt ra một vấn đề rất nghiêm trọng là mặc dù các công việc khác đã sẵn sàng thực thi, CPU không được cấp phát cho chúng vì CPU được phân bổ cho một công việc thậm chí không sử dụng nó (vì nó đang bận trong các tác vụ I / O).
    • Không thể xảy ra trường hợp một công việc sử dụng CPU trong 1 giờ trong khi những công việc khác đã chờ đợi trong hàng đợi trong 5 giờ. Để tránh những tình huống như thế này và sử dụng CPU hiệu quả, người ta đã đưa ra khái niệm đa lập trình.

    Nhiều hệ thống được chương trình đang hoạt động

    • Trong một hệ thống đa lập trình, ngay sau khi một công việc thực hiện một tác vụ I / O, hệ điều hành sẽ ngắt công việc đó, chọn một công việc khác từ nhóm công việc (hàng đợi), cung cấp cho CPU công việc mới này và bắt đầu thực thi nó. Công việc trước đó tiếp tục thực hiện hoạt động I / O của nó trong khi công việc mới này thực hiện các tác vụ ràng buộc CPU. Bây giờ giả sử công việc thứ hai cũng áp dụng cho một tác vụ I / O, CPU chọn công việc thứ ba và bắt đầu thực thi nó. Ngay sau khi một công việc hoàn thành hoạt động I / O của nó và quay trở lại với các tác vụ của CPU, CPU sẽ được cấp phát cho nó.
    • Bằng cách này, không có thời gian CPU bị lãng phí bởi hệ thống chờ đợi tác vụ I / O được hoàn thành. Do đó, mục tiêu cuối cùng của lập trình đa là giữ cho CPU luôn bận rộn miễn là có các tiến trình sẵn sàng thực thi. Bằng cách này, nhiều chương trình có thể được thực thi trên một bộ xử lý bằng cách thực thi một phần của chương trình tại một thời điểm, một phần của chương trình khác sau phần này, sau đó là một phần của chương trình khác, v.v., do đó thực thi nhiều chương trình. Do đó, CPU không bao giờ ngừng hoạt động.

    Trong hình dưới đây, chương trình A chạy một thời gian rồi chuyển sang trạng thái chờ. Trong thời gian trung bình, chương trình B bắt đầu thực hiện. Vì vậy, CPU không lãng phí tài nguyên của nó và tạo cơ hội cho chương trình B chạy.

    2. Multiprocessing

    Trong hệ thống đơn bộ xử lý, chỉ có một quá trình thực thi tại một thời điểm.

    Đa xử lý là việc sử dụng hai hoặc nhiều CPU (bộ xử lý) trong một hệ thống máy tính. Thuật ngữ này cũng đề cập đến khả năng của một hệ thống hỗ trợ nhiều hơn một bộ xử lý trong một hệ thống máy tính duy nhất. Bây giờ vì có nhiều bộ xử lý có sẵn, nhiều quá trình có thể được thực hiện cùng một lúc. Các bộ xử lý đa này chia sẻ bus máy tính, đôi khi cả đồng hồ, bộ nhớ và các thiết bị ngoại vi.

    Nhiều hệ thống xử lý đang hoạt động:

    • Với sự trợ giúp của đa xử lý, nhiều quá trình có thể được thực hiện đồng thời. Giả sử các quá trình P1, P2, P3 và P4 đang chờ thực thi. Bây giờ trong một hệ thống xử lý duy nhất, trước hết một quá trình sẽ thực thi, sau đó là quá trình kia, sau đó là quá trình khác, v.v.
    • Nhưng với đa xử lý, mỗi quá trình có thể được gán cho một bộ xử lý khác nhau để thực hiện. Nếu nó là bộ xử lý lõi kép (2 bộ xử lý), hai quá trình có thể được thực thi đồng thời và do đó sẽ nhanh hơn hai lần, tương tự bộ vi xử lý lõi tứ sẽ nhanh hơn bốn lần so với bộ xử lý đơn.

    Tại sao sử dụng đa xử lý?

    • Ưu điểm chính của hệ thống đa xử lý là hoàn thành nhiều công việc hơn trong một khoảng thời gian ngắn hơn. Các loại hệ thống này được sử dụng khi yêu cầu tốc độ rất cao để xử lý một khối lượng lớn dữ liệu. Hệ thống đa xử lý có thể tiết kiệm tiền so với hệ thống xử lý đơn vì các bộ xử lý có thể dùng chung thiết bị ngoại vi và nguồn cung cấp năng lượng.
    • Nó cũng cung cấp độ tin cậy cao hơn theo nghĩa là nếu một bộ xử lý bị lỗi, công việc sẽ không dừng lại mà chỉ chậm lại. ví dụ. nếu chúng ta có 10 bộ vi xử lý và 1 bộ xử lý bị lỗi, thì công việc sẽ không dừng lại, đúng hơn là 9 bộ vi xử lý còn lại có thể chia sẻ công việc của bộ xử lý thứ 10. Do đó, toàn bộ hệ thống chỉ chạy chậm hơn 10 phần trăm, chứ không phải là thất bại hoàn toàn.

    Đa xử lý đề cập đến phần cứng (tức là các đơn vị CPU) chứ không phải phần mềm (tức là các quy trình đang chạy). Nếu phần cứng bên dưới cung cấp nhiều hơn một bộ xử lý thì đó là quá trình đa xử lý. Đó là khả năng của hệ thống tận dụng sức mạnh tính toán của nhiều bộ xử lý.

    Sự khác biệt giữa đa chương trình và da xử lý

    • Một hệ thống có thể được lập trình đa bằng cách có nhiều chương trình chạy cùng lúc và đa xử lý bằng cách có nhiều hơn một bộ xử lý vật lý. Sự khác biệt giữa đa xử lý và đa lập trình là Đa xử lý về cơ bản là thực hiện nhiều quy trình cùng lúc trên nhiều bộ xử lý, trong khi đa lập trình là lưu giữ một số chương trình trong bộ nhớ chính và thực thi chúng đồng thời chỉ bằng một CPU duy nhất.
    • Đa xử lý xảy ra bằng cách xử lý song song trong khi Đa lập trình xảy ra bằng cách chuyển từ quá trình này sang quá trình khác (hiện tượng được gọi là chuyển mạch ngữ cảnh).

    3. Multitasking

    Như tên gọi của chính nó, đa tác vụ đề cập đến việc thực thi nhiều tác vụ (chẳng hạn như quy trình, chương trình, luồng, v.v.) tại một thời điểm. Trong các hệ điều hành hiện đại, chúng ta có thể phát nhạc MP3, chỉnh sửa tài liệu trong Microsoft Word và lướt Google Chrome đồng thời, điều này được thực hiện nhờ đa tác vụ.

    Đa nhiệm là một phần mở rộng hợp lý của đa lập trình. Cách chính mà đa nhiệm khác với đa lập trình là lập trình đa hoạt động chỉ dựa trên khái niệm chuyển đổi ngữ cảnh trong khi đa nhiệm dựa trên chia sẻ thời gian cùng với khái niệm chuyển đổi ngữ cảnh.

    Hệ thống đa nhiệm đang hoạt động:

    • Trong một hệ thống chia sẻ thời gian, mỗi quá trình được ấn định một số lượng thời gian cụ thể để một quá trình thực thi. Giả sử có 4 quy trình P1, P2, P3 và P4 sẵn sàng thực thi. Vì vậy, mỗi người trong số họ được gán một số lượng tử thời gian mà chúng sẽ thực thi, ví dụ lượng tử thời gian 5 nano giây (5 ns). Khi một quá trình bắt đầu thực hiện (ví dụ P2), nó sẽ thực thi trong lượng tử thời gian đó (5 ns). Sau 5 ns, CPU bắt đầu thực hiện quá trình khác (ví dụ P3) trong lượng tử thời gian được chỉ định.
    • Do đó, CPU làm cho các tiến trình chia sẻ các lát thời gian giữa chúng và thực thi tương ứng. Ngay sau khi lượng tử thời gian của một quá trình hết hạn, một quá trình khác sẽ bắt đầu thực hiện.
    • Ở đây về cơ bản cũng có một chuyển đổi ngữ cảnh đang xảy ra nhưng nó xảy ra quá nhanh đến mức người dùng có thể tương tác với từng chương trình riêng biệt trong khi nó đang chạy. Bằng cách này, người dùng có ảo tưởng rằng nhiều quy trình / tác vụ đang thực hiện đồng thời. Nhưng trên thực tế, chỉ có một tiến trình / tác vụ được thực thi tại một thời điểm cụ thể. Trong đa nhiệm, chia sẻ thời gian được thể hiện tốt nhất bởi vì mỗi quá trình đang chạy chỉ chiếm một lượng tử thời gian hợp lý của CPU.

    Nói một cách khái quát hơn, đa nhiệm là việc có nhiều chương trình, tiến trình, tác vụ, luồng chạy cùng một lúc. Thuật ngữ này được sử dụng trong các hệ điều hành hiện đại khi nhiều tác vụ chia sẻ một tài nguyên xử lý chung (ví dụ: CPU và Bộ nhớ).

    • Như được mô tả trong hình trên, Tại bất kỳ thời điểm nào CPU chỉ thực hiện một tác vụ trong khi các tác vụ khác đang chờ đến lượt. Ảo tưởng về sự song song đạt được khi CPU được phân công lại cho một nhiệm vụ khác. tức là tất cả ba nhiệm vụ A, B và C dường như xảy ra đồng thời vì chia sẻ thời gian.
    • Vì vậy, để đa nhiệm diễn ra, trước tiên phải có đa chương trình, tức là sự hiện diện của nhiều chương trình sẵn sàng để thực thi. Và thứ hai là khái niệm chia sẻ thời gian.

    4. Multithreading

    Một luồng là một đơn vị cơ bản của việc sử dụng CPU. Đa luồng là một mô hình thực thi cho phép một quá trình có nhiều đoạn mã (tức là các luồng) chạy đồng thời trong “ngữ cảnh” của quá trình đó.

    Ví dụ. Trình phát phương tiện VLC, trong đó một luồng được sử dụng để mở trình phát phương tiện VLC, một luồng để phát một bài hát cụ thể và một luồng khác để thêm các bài hát mới vào danh sách phát.

    Đa luồng là khả năng của một quá trình quản lý việc sử dụng nó bởi nhiều người dùng tại một thời điểm và quản lý nhiều yêu cầu của cùng một người dùng mà không cần phải có nhiều bản sao của chương trình.

    Hệ thống đa luồng đang hoạt động:

    Trường hợp 1:

    • Giả sử có một máy chủ web xử lý các yêu cầu của khách hàng. Bây giờ nếu nó thực thi như một quy trình đơn luồng, thì nó sẽ không thể xử lý nhiều yêu cầu cùng một lúc. Đầu tiên, một máy khách sẽ đưa ra yêu cầu của mình và hoàn thành việc thực thi và chỉ sau đó, máy chủ mới có thể xử lý yêu cầu của một máy khách khác. Đây là một công việc thực sự tốn kém, mất thời gian và mệt mỏi. Để tránh điều này, có thể sử dụng đa luồng.
    • Bây giờ, bất cứ khi nào có yêu cầu của khách hàng mới, máy chủ web chỉ cần tạo một luồng mới để xử lý yêu cầu này và tiếp tục thực thi để nghe thêm các yêu cầu của khách hàng. Vì vậy máy chủ web có nhiệm vụ lắng nghe các yêu cầu mới của khách hàng và tạo các luồng cho từng yêu cầu riêng lẻ. Mỗi luồng mới được tạo xử lý một yêu cầu của khách hàng, do đó giảm bớt gánh nặng cho máy chủ web.

    Trường hợp 2:

    • Chúng ta có thể coi các luồng là các quy trình con chia sẻ tài nguyên quy trình mẹ nhưng thực thi độc lập. Bây giờ hãy lấy trường hợp của GUI. Giả sử chúng tôi đang thực hiện một phép tính trên GUI (mất rất nhiều thời gian để hoàn thành). Bây giờ chúng ta không thể tương tác với phần còn lại của GUI cho đến khi lệnh này kết thúc quá trình thực thi. Để có thể tương tác với phần còn lại của GUI, lệnh tính toán này nên được gán cho một luồng riêng biệt. Vì vậy, tại thời điểm này, 2 luồng sẽ thực thi, tức là một để tính toán và một cho phần còn lại của GUI. Do đó, ở đây trong một quy trình duy nhất, chúng tôi đã sử dụng nhiều luồng cho nhiều chức năng.

    Hình ảnh dưới đây mô tả hoàn toàn ví dụ về trình phát VLC:

    Ưu điểm của đa luồng

    • Lợi ích của Đa luồng bao gồm tăng khả năng phản hồi. Vì có nhiều luồng trong một chương trình, vì vậy nếu một luồng mất quá nhiều thời gian để thực thi hoặc nếu nó bị chặn, phần còn lại của luồng sẽ tiếp tục thực thi mà không gặp bất kỳ sự cố nào. Do đó, toàn bộ chương trình vẫn đáp ứng cho người dùng bằng các luồng còn lại.
    • Một ưu điểm khác của đa luồng là nó ít tốn kém hơn. Tạo các quy trình hoàn toàn mới và phân bổ tài nguyên là một công việc tốn nhiều thời gian, nhưng vì các luồng chia sẻ tài nguyên của quy trình mẹ nên việc tạo các luồng và chuyển đổi giữa chúng tương đối dễ dàng. Do đó đa luồng là nhu cầu của các Hệ điều hành hiện đại.

    Cảm ơn mọi người đã theo dõi.

    Author: DuongVT19

  • Dart: Tạo packages.

    Ngôn ngữ Dart sử dụng các package để chia sẻ phần mềm như thư viện và công cụ. Bài này cho bạn biết cách tạo một package, tập trung vào loại package phổ biến nhất, package thư viện.

    Điều gì tạo nên một package thư viện?

    Sơ đồ sau đây cho thấy cách bố trí của package thư viện đơn giản nhất:

    Các yêu cầu tối thiểu đối với một thư viện là:

    – tệp pubspec

    Tệp pubspec.yaml cho một thư viện cũng giống như cho một package ứng dụng. Không có ký hiệu đặc biệt nào để chỉ ra rằng package đó là một thư viện.

    – thư mục lib

    Như bạn có thể mong đợi, mã thư viện nằm trong thư mục lib và công khai đối với các package khác. Bạn có thể tạo bất kỳ hệ thống phân cấp nào dưới lib, nếu cần. Theo quy ước, mã thực thi được đặt dưới lib / src. Mã dưới lib / src được coi là riêng tư; các package khác sẽ không bao giờ cần nhập src / …. Để đặt các API dưới lib / src ở chế độ công khai, bạn có thể xuất tệp lib / src từ tệp trực tiếp dưới lib.

    Cấu thành một package thư viện

    Package thư viện dễ bảo trì, mở rộng và kiểm tra nhất khi bạn tạo các thư viện nhỏ, riêng lẻ, được gọi là thư viện nhỏ. Trong hầu hết các trường hợp, mỗi lớp phải nằm trong thư viện nhỏ của riêng nó, trừ khi bạn gặp trường hợp hai lớp được kết hợp chặt chẽ với nhau.

    Tạo tệp thư viện “chính” trực tiếp dưới lib, lib / <package-name> .dart, xuất tất cả các API công khai. Điều này cho phép người dùng có được tất cả các chức năng của thư viện bằng cách nhập một tệp.

    Thư mục lib cũng có thể bao gồm các thư viện có thể nhập khác, không phải src,. Ví dụ: có thể thư viện chính của bạn hoạt động trên nhiều nền tảng, nhưng bạn tạo các thư viện riêng biệt dựa trên dart: io hoặc dart: html. Một số package có các thư viện riêng biệt được nhập với một tiền tố, trong khi thư viện chính thì không.

    Hãy xem cách tổ chức package thư viện trong thế giới thực: giá sách. Package giá sách cung cấp một cách dễ dàng để tạo máy chủ web bằng Dart và được bố trí theo cấu trúc thường được sử dụng cho các package thư viện Dart:

    Trực tiếp bên dưới lib, tệp thư viện chính, shelf.dart, xuất API từ một số tệp trong lib / src. Để tránh để lộ nhiều API hơn dự định – và để cung cấp cho nhà phát triển cái nhìn tổng quan về toàn bộ API công khai của package – chương trình sử dụng books.dart để chỉ định chính xác những ký hiệu nào cần xuất:

    Package shelf cũng chứa một thư viện nhỏ: shelf_io. Bộ điều hợp này xử lý các đối tượng HttpRequest từ dart: io.

    Nhập các tệp thư viện

    Khi nhập tệp thư viện từ một package khác, hãy sử dụng chỉ thị package: để chỉ định URI của tệp đó.

    Khi nhập tệp thư viện từ package của riêng bạn, hãy sử dụng một đường dẫn tương đối khi cả hai tệp nằm trong lib hoặc khi cả hai tệp nằm ngoài lib. Package sử dụng: khi tệp nhập nằm trong lib và tệp nhập ở bên ngoài.

    Hình dưới đây cho thấy cách nhập lib / foo / a.dart từ cả lib và web.

    Nhập và xuất các tệp thư viện có điều kiện.

    Nếu thư viện của bạn hỗ trợ nhiều nền tảng, thì bạn có thể cần nhập hoặc xuất các tệp thư viện có điều kiện. Một trường hợp sử dụng phổ biến là một thư viện hỗ trợ cả nền tảng web và nền tảng gốc.

    Để nhập hoặc xuất có điều kiện, bạn cần kiểm tra sự hiện diện của các thư viện dart: *. Dưới đây là một ví dụ về mã xuất có điều kiện để kiểm tra sự hiện diện của dart: io và dart: html:

    Đây là những gì mã đó làm:

    • Trong một ứng dụng có thể sử dụng dart: io (ví dụ: một ứng dụng dòng lệnh), hãy xuất src / hw_io.dart.
    • Trong một ứng dụng có thể sử dụng dart: html (một ứng dụng web), hãy xuất src / hw_html.dart.
    • Nếu không, hãy xuất src / hw_none.dart.

    Để nhập tệp có điều kiện, hãy sử dụng mã tương tự như trên, nhưng thay đổi export thành import.

    Trên đây là các chia sẽ của mình về tạo package trong ngôn ngữ dart.

    Cảm ơn mọi người đã theo dõi.

    Author: DuongVT19

  • Custom Paint trong Flutter

    Lớp con CustomPainter ghi đè hai phương thức: paint()và shouldRepaint().

    Hãy để tôi hướng dẫn bạn qua mã để hiểu rõ hơn về những gì đang xảy ra ở đây. Như bạn có thể thấy giao diện người dùng mà chúng ta sẽ đạt được theo đó, tôi đã tạo một lớp CustomPaintExample chứa mã cho giao diện người dùng. Có hai yếu tố chính, hình dạng và hoạt ảnh của nút. Nút khi được nhấn sẽ tăng giá trị percentValue lên 10 và theo đó có thể thấy tiến trình trên đường viền nút đang tăng lên. Tiện ích này cũng được chia thành hai phần tử CustomPainterExample.buildShape() và CustomPainterExample.buildButtonAnimation() tôi đã tạo trong các lớp khác nhau CustomPainterExampleWidgets.

    Trong Class CustomPainterExampleWidgets, Phần tử CustomPainterExample.buildShape()trả về một ClipPath bao gồm clipper CustomShapeClass và phần tử còn lại CustomPainterExample.buildButtonAnimation() bao gồm một Container với CustomPaint là phần tử con của nó hiển thị ở giữa màn hình. . Phần trăm tăng được hiển thị bằng phương thức onPressed tại nút Raised.

    import 'package:flutter/material.dart';
    class CustomShapeClass extends CustomClipper<Path> {
      @override
      getClip(Size size) {
        // TODO: implement getClip
        var path = new Path();
        path.lineTo(0, size.height / 4.25);
        var firstControlPoint = new Offset(size.width / 4, size.height / 3);
        var firstEndPoint = new Offset(size.width / 2, size.height / 3 - 60);
        var secondControlPoint =
        new Offset(size.width - (size.width / 4), size.height / 4 - 65);
        var secondEndPoint = new Offset(size.width, size.height / 3 - 40);
        path.quadraticBezierTo(firstControlPoint.dx, firstControlPoint.dy,
            firstEndPoint.dx, firstEndPoint.dy);
        path.quadraticBezierTo(secondControlPoint.dx, secondControlPoint.dy,
            secondEndPoint.dx, secondEndPoint.dy);
        path.lineTo(size.width, size.height / 3);
        path.lineTo(size.width, 0);
        path.close();
        return path;
      }
      @override
      bool shouldReclip(CustomClipper oldClipper)
      {
        return true;
      }
    }

    CustomShapeClass kế thừa CustomClipper được minh họa ở trên Trong điều khiển này, các điểm điều khiển và điểm kết thúc được xác định trong đó đường cong bezier bậc hai được vẽ. Bây giờ hãy khai báo lõi của ứng dụng lớp ButtonPainter

    import 'dart:math';
    import 'package:flutter/cupertino.dart';
    import 'package:flutter/material.dart';
    class ButtonPainter extends CustomPainter
    {
      Color buttonBorderColor;
      Color progressColor;
      double percentage;
      double width;
      ButtonPainter({this.buttonBorderColor,this.progressColor,this.percentage,this.width});
      @override
      void paint(Canvas canvas, Size size) {
        Paint line = Paint()
          ..color = buttonBorderColor
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        Paint complete =  Paint()
          ..color = progressColor
          ..strokeCap = StrokeCap.round
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        Offset center  = Offset(size.width/2, size.height/2);
        double radius  = min(size.width/2,size.height/2);
        canvas.drawCircle(
            center,
            radius,
            line
        );
        double arcAngle = 2*pi* (percentage/100);
        canvas.drawArc(
            Rect.fromCircle(center: center,radius: radius),
            -pi/2,
            arcAngle,
            false,
            complete
        );
      }
      @override
      bool shouldRepaint(CustomPainter oldDelegate) {
        return true;
      }
    }

    Ở đây chúng ta nhận được các thuộc tính khác nhau lineColor, có nghĩa là màu cho đường đi, trên đó tiến trình sẽ được vẽ, completeColor cho tiến độ đã hoàn thành, completePercent cho mức tiến độ đã hoàn thành. Từ tiện ích con của nó, chúng tôi gửi thuộc tính này xuống, ở đây chúng tôi chỉ đơn giản là vẽ.

    ButtonPainter kế thừa lớp CustomPainter và chúng ta cần ghi đè paint phương thức của nó để vẽ đồ họa. Để vẽ, ngay từ đầu, chúng ta cần thông báo một thứ gọi là Paint , nó định hình tất cả các thuộc tính dự kiến ​​sẽ vẽ thứ gì đó trên màn hình. Vì vậy, chúng ta có hai đối tượng Paint ; đầu tiên là một dòng nói lên đường theo dõi mà tiến trình sẽ được vẽ trên đó. Chúng tôi cung cấp cho nó một màu sắc, được cung cấp dưới dạng thuộc tính, strokeCap ​​là hình tròn và chúng tôi yêu cầu style là PaintingStyle cho một trong những cung cấp. Vì vậy, ngoài ra, chúng tôi thông báo đối tượng Paint hoàn chỉnh cho tiến trình đã hoàn thành.

    Tóm lại, chúng ta phải vẽ một vòng tròn đầy đủ và sau đó vẽ một vòng cung trên đó, thế là đã hoàn thành.

    Offset center  = Offset(size.width/2, size.height/2);
    double radius  = min(size.width/2,size.height/2);

    Trong hai đường thẳng này, chúng tôi tính toán các hướng cho tâm của hình tròn và bán kính cơ bản của nó, tâm là chiều rộng và chiều cao của thùng chứa chia cho 2. Chúng tôi cần chính xác tâm của các hình tròn ở giữa. size là những gì chúng ta nhận được dưới dạng một tham số trong hàm paint, là kích thước của thùng chứa. radius Đây là mức tối thiểu của một nửa chiều rộng và chiều cao và bây giờ chúng tôi đang lấy mức tối thiểu của cả hai vì vùng chứa có thể không phải luôn luôn là hình vuông

    canvas.drawCircle(center,radius,line);

    Bây giờ chúng ta chỉ vẽ với hàm drawCircle được gọi trên đối tượng canvas được cung cấp, chúng ta chuyền vào tâm, bán kính và đối tượng Paint .

    canvas.drawArc(Rect.fromCircle(center: center,radius: radius),
        -pi/2,arcAngle,false,complete);

    Bây giờ, chúng ta tìm góc cho cung tròn đã hoàn thành, mà nó đã xuất hiện. Tôi không đi sâu vào tính toán hình học. Để gọi drawArc, chúng ta cần chỉ định một Rect , là hộp giới hạn của cung tròn, chúng ta nhận được Rect bao gồm các tham số của đường tròn mà chúng ta đã vẽ trước đó. Tại thời điểm đó, chúng ta cho một góc bắt đầu là -pi / 2 radian, hãy nhớ nó không phải là 0. Đỉnh là -pi/2, 0 là đích ngoài cùng bên phải của đường tròn. Chúng tôi cung cấp arcAngle tại điểm đó, đó là cung sẽ kéo dài bao nhiêu. Sau đó, chúng tôi chuyền vào false để nói rằng chúng tôi không muốn phần cuối của vòng cung được kết nối trở lại trung tâm và cuối cùng chúng chuyền vào complete. Đó là tất cả những gì chúng tôi mong đợi để vẽ bằng cách sử dụng CustomPaint.

    Bây giờ, hãy thêm các hình ảnh động.

    percentageAnimationController = new AnimationController(vsync: this,
        duration: new Duration(milliseconds: 1000))
      ..addListener((){setState(() {
          percentage=lerpDouble(percentage,newPercentage,
          percentageAnimationController.value);});});

    Chúng tôi thêm hoạt ảnh trên màn hình HomePage trong initState(). Sau đó, chúng tôi đã thêm một trình nghe trên AnimationController đó được gọi ở mỗi bước của hoạt ảnh và chúng tôi thay đổi tỷ lệ phần trăm. Để thay đổi tỷ lệ phần trăm, chúng tôi nhận được một giá trị được chèn với sự trợ giúp của hàm lerpDouble , giá trị nằm trong percentage và newPercentage lớn hơn giá trị ban đầu 10, chúng ta nhận dựa trên giá trị AnimationController’s.

    Chúng ta thêm dòng này vào nút onPressed để bắt đầu hoạt ảnh.

    percentageAnimationController.forward(from: 0.0);

    Bạn sẽ thấy mã đầy đủ trên GitHub và đây là một ví dụ về quy trình vòng tròn nhỏ để tích hợp với custom paint và video dưới đây cho biết cách CustomPaint sẽ hoạt động.

    Hy vọng blog này sẽ cung cấp cho bạn đầy đủ thông tin trong việc thử Custom Paint trong các dự án hiện tại của bạn Cảm ơn vì đã đọc bài viết này

  • Cách sử dụng các biến môi trường trong Docker Compose

    Sớm hay muộn, tất cả chúng ta đều phải xử lý các biến môi trường trong tệp soạn của mình. Chúng có thể trở thành một nỗi đau, đặc biệt nếu chúng ta không biết cách sử dụng chúng đúng cách. Dưới đây là mọi thứ tôi biết về các biến môi trường và cách sử dụng các biến này dễ dàng và trên hết là an toàn.

    Chúng ta sử dụng các biến môi trường như thế nào?

    Docker Compose cho phép chúng ta chuyển các biến môi trường vào thông qua dòng lệnh hoặc xác định chúng trong trình bao của chúng ta. Tuy nhiên, tốt nhất là giữ các giá trị này bên trong tệp soạn thực tế và bên ngoài dòng lệnh. “Tại sao?” bạn có thể yêu cầu.

    Bởi vì theo cách này, chúng ta không phải nhớ tất cả các biến môi trường mà chúng ta sử dụng mỗi khi triển khai vùng chứa của mình. Bằng cách lưu trữ chúng trong tệp soạn, chúng ta duy trì tính nhất quán trong quá trình xây dựng của mình.

    Có nhiều hướng khác nhau để làm điều đó.

    Sử dụng lựa chọn môi trường.

    Sử dụng tùy chọn môi trường soạn cho phép chúng ta khai báo các biến môi trường và giá trị của chúng bên trong tệp soạn của chúng ta, như thế này.

    Đây là cách dễ nhất, nhanh nhất để lưu trữ các biến môi trường bên trong soạn tệp. Tuy nhiên, nó có một nhược điểm lớn là liên quan đến bảo mật. Bạn có đoán được nó là gì không?

    Đúng rồi.

    Việc lưu trữ các giá trị của các biến môi trường của bạn trong tệp Soạn sẽ – 95% thời gian – chuyển thẳng đến kiểm soát nguồn và đó là một rủi ro bảo mật rất lớn. May mắn thay, chúng ta có một giải pháp thay thế: sử dụng tệp bên ngoài để lưu trữ các biến môi trường của chúng ta.

    Sử dụng tập tin .env

    Ưu điểm chính của việc sử dụng tệp bên ngoài cho các biến môi trường của bạn là bạn có thể giữ tệp đã nói ngoài quyền kiểm soát nguồn của mình. Rốt cuộc, không ai thích có mật khẩu, khóa API hoặc thông tin siêu bí mật khác của họ trên internet.

    Tệp .env là tệp văn bản thuần túy, chúng ta sử dụng để cấu hình. Hãy nhớ rằng, vì tên tệp bắt đầu bằng dấu ‘ . ’ Nên chúng vẫn bị ẩn đối với hệ thống.

    Chú ý: Để liệt kê các tệp ẩn, bạn có thể sử dụng lệnh ls -a trên Linux hoặc lệnh dir / a: h trên Windows.

    Bạn phải tạo tệp .env tại thư mục gốc của dự án, đây cũng là nơi chứa tệp docker-compost.yml của bạn.

    Chúng ta có thể khai báo và gán các biến trong tệp .env của mình. Đặt tên cho các biến theo cách bạn muốn vì chúng ta sẽ chỉ truy cập các giá trị của chúng.

     Đây là tệp .env của tôi:

    Bạn cũng có thể tạo và điền tệp .env của mình từ dòng lệnh bằng cách sử dụng lệnh mèo Linux:

    Chú ý: Hãy nhớ không để lại bất kỳ khoảng trống nào giữa dấu = và giá trị được gán cho biến của bạn, vì chúng sẽ được thêm vào chuỗi.

    Bây giờ chúng ta đã lưu trữ các biến trong tệp .env, hãy sử dụng chúng trong tệp Soạn của chúng ta. Bây giờ là lúc sử dụng phép nội suy chuỗi (đó là một cái tên ưa thích khi sử dụng ký hiệu $ {string}) để gán giá trị của các biến .env của chúng ta cho các biến môi trường trong tệp Soạn.

    Như bạn có thể thấy, chúng ta duy trì tùy chọn môi trường và chỉ cần gán các giá trị bên ngoài của chúng ta cho các biến môi trường Soạn.

    Để kiểm tra xem mọi thứ có hoạt động bình thường hay không, hãy chạy lệnh sau: docker-compile up.

    Chú ý: Bạn có thể kiểm tra giá trị nào được gán cho các biến môi trường bằng cách chạy lệnh sau (trong một thiết bị đầu cuối khác): docker-compile config.

    Mức độ ưu tiên biến môi trường

    Một điều rất quan trọng mà chúng ta phải ghi nhớ là mức độ ưu tiên được Compose sử dụng để lựa chọn giá trị môi trường của nó. Điều đó có nghĩa là gì?

    Nếu chúng ta khai báo cùng một biến môi trường trong một số tệp (ví dụ: trong tệp Soạn và trong tệp .env bên ngoài) với các giá trị khác nhau, thì Soạn sẽ sử dụng giá trị của biến được khai báo trong tệp Soạn. Tại sao? Bởi vì, tùy thuộc vào nơi mà biến được khai báo, Soạn sẽ cấp cho biến đó mức độ ưu tiên cao hơn hoặc thấp hơn. Đây là thứ tự, xếp hạng từ mức độ ưu tiên cao nhất đến mức độ ưu tiên thấp nhất:

    1. Soạn tệp
    2. Biến môi trường Shell
    3. Tệp môi trường
    4. Dockerfile
    5. Biến không được xác định

    Nếu vì lý do nào đó, tính năng Soạn đang chọn và chỉ định một giá trị mà bạn không mong đợi, thì đây có thể là nguyên nhân. Đảm bảo rằng tất cả các biến của bạn được khai báo chính xác ở nơi bạn muốn.

    Sử dụng tùy chọn env file

    Trong phần trước, chúng ta đã nói về các tệp .env thuần túy, nhưng chúng ta chưa bao giờ sử dụng các tệp .env có tên. Nếu chúng ta muốn tệp .env của mình có tên, như secret-things.enf, thì Compose có một tùy chọn nhỏ tiện lợi có tên là env_file.

    Tùy chọn này cho phép chúng ta thông báo cho soạn thảo tệp .env mà nó phải tìm kiếm, trái ngược với hành vi mặc định của nó, đó là tìm kiếm tệp .env không tên. Đây là cách chúng ta sử dụng tùy chọn env_file.

    Như bạn có thể thấy, chúng ta đã thêm tùy chọn env_file, tùy chọn này trỏ đến một tệp có tên tệp secret -uff.env. Tất cả những gì còn lại là đổi tên tệp .env trước đó của chúng ta thành secret -uff.env.

    Bạn có thể nhận thấy tùy chọn môi trường không còn xuất hiện trong tệp soạn thư của chúng ta. Điều này là do việc sử dụng tùy chọn env_file làm nảy sinh một vấn đề – một vấn đề khiến tôi khá đau đầu!

    Để gán các giá trị được khai báo trong tệp .env có tên bên ngoài cho các biến soạn yêu cầu tệp đã nói phải được xác định trong dịch vụ soạn chính. Điều này liên quan đến thứ tự mà Soạn tuân theo khi thực hiện các hoạt động. Tuy nhiên, chúng ta không có dịch vụ chính, vì chúng ta chỉ sử dụng một dịch vụ db. Do đó, nếu chúng ta cố gắng triển khai tệp Soạn của mình, nó sẽ phàn nàn rằng các biến của chúng ta không được xác định và thay thế chúng bằng các chuỗi trống.

    Bạn không cần phải nghe lời tôi, hãy tiếp tục và thử nó! Chạy docker-soạn lên và xem điều gì sẽ xảy ra.

    Vì vậy, làm thế nào để chúng ta giải quyết vấn đề này? Đây là những gì tôi đã tìm ra:

    Nếu chúng ta xóa tùy chọn môi trường khỏi tệp soạn, khi triển khai, soạn sẽ tìm kiếm tệp secret -uff.env được chỉ định, điều mà tùy chọn này không thực hiện khi có tùy chọn môi trường. Vấn đề đã được giải quyết!

    Tuy nhiên, hãy nhớ rằng vì chúng ta không còn tùy chọn môi trường nữa, chúng ta phải khai báo các biến môi trường trực tiếp trong tệp secret -uff.env, như sau:

    Một lần nữa, để kiểm tra xem mọi thứ có hoạt động bình thường hay không, hãy chạy: docker-compile up.

    Đây các cách khác nhau để xử lý an toàn với các biến môi trường trong soạn tệp. Cảm ơn đã theo dõi.

    Author: DuongVT19