Tải bản đầy đủ (.pdf) (108 trang)

đồ án 2 xây dựng ứng dụng chia sẻ công thức món ăn

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (3.44 MB, 108 trang )

<span class="text_page_counter">Trang 1</span><div class="page_container" data-page="1">

<b>TRƯỜNG ĐẠI HỌC CÔNG NGHỆ THÔNG TIN KHOA CÔNG NGHỆ PHẦN MỀM </b>

------

<b>BÁO CÁO ĐỒ ÁN 2 Đề tài: </b>

<b>XÂY DỰNG ỨNG DỤNG CHIA SẺ CƠNG THỨC MĨN ĂN </b>

<b>Giảng viên hướng dẫn: ThS. Huỳnh Tuấn Anh Lớp: SE122.O11 </b>

</div><span class="text_page_counter">Trang 3</span><div class="page_container" data-page="3">

<b>LỜI CẢM ƠN </b>

Sau quá trình học tập và rèn luyện tại khoa Công nghệ phần mềm trường Đại học Công nghệ Thông tin – ĐHQG TP.HCM, em đã được trang bị các kiến thức cơ bản cùng các kỹ năng thực tế để có thể hồn thành Đồ án 2 của mình.

Để hồn thành đồ án này, với lịng biết ơn sâu sắc em xin gửi lời cảm ơn chân thành đến:

Ban Giám hiệu trường Đại học Công nghệ Thơng tin – ĐHQG TP.HCM vì đã tạo điều kiện thuận lợi để sinh viên tìm kiếm, nghiên cứu thơng tin với hệ thống thư viện hiện đại, đa dạng các loại sách và tài liệu.

Gần gũi hơn là những lời tốt đẹp nhất xin gửi đến đến thầy Nguyễn Công Hoan đã tận tình giúp đỡ, định hướng cách tư duy và hướng làm việc khoa học. Đó là những góp ý hết sức q báu khơng chỉ trong q trình thực hiện đồ án mà còn là hành trang tiếp bước cho em trong quá trình học tập và làm việc sau này.

Sau cùng, xin chúc quý Thầy Cô trong khoa Cơng nghệ Phần mềm nói riêng cũng như các giáo viên tại trường Cơng nghệ thơng tin nói chung thật dồi dào sức khỏe, niềm tin để tiếp tục thực hiện sứ mệnh cao đẹp của mình.

Thành phố Hồ Chí Minh, tháng 12 năm 2023 Sinh Viên

<b>Bùi Lê Hoài An </b>

</div><span class="text_page_counter">Trang 4</span><div class="page_container" data-page="4">

<b>NHẬN XÉT CỦA GIẢNG VIÊN </b>

</div><span class="text_page_counter">Trang 5</span><div class="page_container" data-page="5">

<b>MỤC LỤC </b>

<small>LỜI CẢM ƠN ... 3</small>

<small>NHẬN XÉT CỦA GIẢNG VIÊN ... 4</small>

<small>MỤC LỤC ... 5</small>

<b><small>CHƯƠNG 1: GIỚI THIỆU TỞNG QUAN ... 9</small></b>

<b><small>1.Thơng tin sinh viên:</small></b><small> ... 9</small>

<b><small>2.Tổng quan đề tài:</small></b><small> ... 9</small>

<b><small>2.1Giới thiệu đề tài:</small></b><small> ... 9</small>

<b><small>2.2Phạm vi nghiên cứu:</small></b><small> ... 9</small>

<b><small>2.3Nội dung nghiên cứu:</small></b><small> ... 9</small>

<b><small>2.4Kết quả hướng tới:</small></b><small> ... 10</small>

<b><small>3.Công cụ sử dụng:</small></b><small> ... 10</small>

<b><small>CHƯƠNG 2: CÔNG NGHỆ SỬ DỤNG ... 11</small></b>

<b><small>1.Giới thiệu ngơn ngữ lập trình Kotlin ... 11</small></b>

<b><small>2.Jetpack Compose</small></b><small> ... 11</small>

<b><small>3.Tại sao lại là Jetpack Compose? ... 12</small></b>

<b><small>4.Mơ hình tư duy trong Jetpack Compose ... 12</small></b>

<b><small>4.1.Mơ hình lập trình khai báo... 13</small></b>

<b><small>4.2.Hàm composable đơn giản ... 13</small></b>

<b><small>4.3.Thay đổi mô hình khai báo ... 15</small></b>

<b><small>4.4.Nợi dung đợng ... 17</small></b>

<b><small>4.5.Recomposition</small></b><small> ... 18</small>

<b><small>4.5.1.Các hàm composable có thể thực thi theo thứ tự bất kỳ</small></b><small> ... 19 </small>

<b><small>4.5.2.Các hàm composable có thể chạy song song</small></b><small> ... 19 </small>

</div><span class="text_page_counter">Trang 6</span><div class="page_container" data-page="6">

<b><small>5.4.Mơ hình bố cục ... 27</small></b>

<b><small>5.5.Hiệu suất ... 29</small></b>

<b><small>5.6.Sử dụng modifiers ... 29</small></b>

<b><small>6.Quản lý trạng thái... 30</small></b>

<b><small>6.1.Trạng thái và composition ... 31</small></b>

<b><small>6.2.Trạng thái trong composition ... 32</small></b>

<b><small>6.3.Chuyển trạng thái lên trên ... 33</small></b>

<b><small>6.4.Khôi phục trạng thái ... 36</small></b>

<b><small>6.4.1.Parcelize</small></b><small> ... 36 </small>

<b><small>6.4.2.MapSaver</small></b><small> ... 37 </small>

<b><small>6.5.Phần tử giữ trạng thái ... 37</small></b>

<b><small>6.6.Kích hoạt lại tính năng ghi nhớ các tính tốn khi khố thay đởi ... 37</small></b>

<b><small>7.Vịng đời của Composable ... 39</small></b>

<b><small>7.1.Tởng quan ... 39</small></b>

<b><small>7.2.Phân tích mợt Composable trong Composition ... 41</small></b>

<b><small>7.2.1.Thêm thơng tin hỗ trợ quá trình recomposition</small></b><small> ... 42 </small>

<b><small>7.3.Bỏ qua nếu giá trị đầu vào không thay đổi</small></b><small> ... 46 </small>

<b><small>8.Các giai đoạn trong Jetpack Compose ... 47</small></b>

<b><small>8.1.3 giai đoạn của một frame ... 47</small></b>

<b><small>8.2.Đọc trạng thái ... 49</small></b>

<b><small>8.3.Đọc trạng thái theo giai đoạn ... 50</small></b>

<b><small>8.3.1.Giai đoạn 1: Composition ... 50 </small></b>

<b><small>8.3.2.Giai đoạn 2: Layout ... 50 </small></b>

<b><small>8.3.3.Giai đoạn 3: Drawing ... 51 </small></b>

<b><small>8.4.Tối ưu hóa việc đọc trạng thái ... 52</small></b>

<b><small>8.5.Vịng lặp tái kết hợp (phần phụ tḥc giai đoạn tuần hoàn)... 54</small></b>

</div><span class="text_page_counter">Trang 7</span><div class="page_container" data-page="7">

<b><small>1.3.Đối tượng hướng đến ... 59</small></b>

<b><small>1.4.Môi trường phát triển ứng dụng ... 59</small></b>

<b><small>1.5.Kết quả mong đợi</small></b><small> ... 60</small>

<b><small>1.6.Quy trình thực hiện các cơng việc chính</small></b><small> ... 60</small>

<b><small>2.Phân tích, thiết kế hệ thống</small></b><small> ... 60</small>

<b><small>2.1.Xác định và mơ hình hóa các u cầu phần mềm</small></b><small> ... 60</small>

<b><small>2.1.1.Xác định yêu cầu</small></b><small> ... 60 </small>

<b><small>2.1.1.1. Một số yêu cầu phần mềm phải có ... 60 </small></b>

<b><small>2.1.1.2. Ràng buộc logic ban đầu ... 61 </small></b>

<b><small>2.2.Thiết kế hệ thống</small></b><small> ... 77</small>

<b><small>2.2.1.Kiến trúc hệ thống: Multi module architecture</small></b><small> ... 77 </small>

<b><small>2.2.1.1. Vấn đề về độ lớn của mã ... 77 </small></b>

<b><small>2.2.1.2. Module hóa ... 77 </small></b>

<b><small>2.2.2.Kiến trúc ứng dụng</small></b><small> ... 78 </small>

<b><small>2.3.Thiết kế dữ liệu</small></b><small> ... 80</small>

<b><small>2.4.Thiết kế giao diện</small></b><small> ... 85</small>

<b><small>2.4.1.Sơ đồ liên kết màn hình ... 85 </small></b>

<b><small>2.4.2.Danh sách các màn hình</small></b><small> ... 86 </small>

<b><small>3.Cài đặt và thử nghiệm</small></b><small> ... 105</small>

</div><span class="text_page_counter">Trang 9</span><div class="page_container" data-page="9">

<b>CHƯƠNG 1: GIỚI THIỆU TỞNG QUAN </b>

<b>1. Thơng tin sinh viên: </b>

20520985 Bùi Lê Hoài An

<b>2. Tổng quan đề tài: </b>

Chúng ta không thể phủ nhận sự hữu ích của các framework platform (Flutter, React-Native, …) đã giúp chúng ta phần nào giải quyết được vấn đề về mặt thời gian. Tuy vậy khi nhìn vào thực tế, các cơng ty lớn trên thế giới tiếp tục sử dụng các ứng dụng native, đó là vấn đề lớn cũng cần phải được kể đến: hiệu suất. Hiệu suất ảnh hưởng trực tiếp đến trải nghiệm người dùng. Vì thế phát triển ứng dụng.

coss-Phát triển ứng dụng native từ lâu đã tồn tại những yếu điểm dễ dàng nhận thấy. Đơn cử như đối với Android, đó là việc nhà phát triển phải sử dụng một loại ngơn ngữ khác hồn tồn với ngơn ngữ chính để lập trình – XML. Đối với các nhà phát triển cũng như các công ty phải học song song 2 ngôn ngữ (Java/Kotlin và XML) là rất mất thời gian, dẫn đến đó là tiêu tốn nhiều chi phí.

Kể từ sau Google I/O 2019, Kotlin dần phát triển để thay thế Java, trở thành một ngơn ngữ lập trình được ưa chuộng nhất cho phát triển ứng dụng Android native. Và điều chúng ta mong muốn nhất rồi cũng đến: Jetpack Compose, sử dụng Kotlin làm ngôn ngữ lập trình đã ra đời với nhiệm vụ tiên quyết: source code chỉ chứa duy nhất 1 ngôn ngữ lập trình.

</div><span class="text_page_counter">Trang 10</span><div class="page_container" data-page="10">

Em sẽ tiến hành nghiên cứu chi tiết về cách thức hoạt động, ưu - khuyết điểm cũng như các thư viện - tính năng - package liên quan đến Compose.

Với đề tài này, em đề ra hai mục tiêu chính:

• Đối với các nhân: mở rộng kiến thức của mình về Compose thơng qua q trình tìm hiểu và áp dụng vào ứng dụng thực tế. Các kiến thức tìm hiểu được thông qua đồ án thúc đẩy em phát triển thêm các ứng dụng khác bằng Compose Jetpack. Đồng thời, em học được cách nghiên cứu và sử dụng một framework mới cần trải qua quá trình gì nhằm giúp em dễ dàng tiếp xúc với các cơng nghệ mới hơn trong tương lai.

• Có cái nhìn tổng quát hơn trong việc ứng dụng Machine Learning trong phát triển ứng dụng mobile.

<b>3. Công cụ sử dụng: </b>

Trong quá trình xây dựng phần mềm, em đã sử dụng các phần mềm sau:

<b>• Android Studio: phát triển front end • Google Colabs: training model • GitHub: quản lý source code </b>

</div><span class="text_page_counter">Trang 11</span><div class="page_container" data-page="11">

<b>CHƯƠNG 2: CÔNG NGHỆ SỬ DỤNG </b>

<b>1. Giới thiệu ngơn ngữ lập trình Kotlin </b>

Kotlin là một ngơn ngữ lập trình đa năng, được thiết kế để chạy trên nền tảng Java Virtual Machine (JVM), nhưng cũng có thể chạy trên nền tảng Android, JavaScript và Native. Kotlin được phát triển bởi JetBrains, cùng với một số cộng đồng lập trình viên đóng góp.

Một số đặc điểm của Kotlin bao gồm:

• Kotlin là một ngơn ngữ lập trình hướng đối tượng (OOP) và hỗ trợ các khái niệm lập trình hàm (FP), giúp cho việc phát triển ứng dụng linh hoạt hơn. • Kotlin có cú pháp đơn giản, dễ đọc và dễ viết hơn so với Java và nhiều

Kotlin đã được Google chính thức cơng nhận là ngơn ngữ lập trình cho phát triển ứng dụng Android, và được sử dụng rộng rãi trong cộng đồng lập trình viên Android. Ngồi ra, Kotlin cũng được sử dụng trong nhiều dự án khác, bao gồm phát triển trên máy chủ, ứng dụng web và phát triển ứng dụng trên các thiết bị nhúng.

<b>2. Jetpack Compose </b>

Jetpack Compose là một thư viện UI cho phép bạn xây dựng giao diện người dùng Android bằng cách sử dụng Kotlin. Nó cho phép tạo các giao diện người dùng tùy chỉnh và linh hoạt hơn. Bạn có thể tạo các giao diJetpack Compose là một framework để phát triển giao diện người dùng (UI) trên nền tảng Android, được phát triển bởi Google. Nó cho phép lập trình viên tạo giao diện người dùng bằng

</div><span class="text_page_counter">Trang 12</span><div class="page_container" data-page="12">

<b>3. Tại sao lại là Jetpack Compose? </b>

• Khai báo giao diện người dùng: Jetpack Compose sử dụng phong cách lập trình khai báo, cho phép bạn xác định giao diện bằng cách chỉ định những gì bạn muốn nó hiển thị, thay vì xác định cách để đạt được nó. Điều này làm cho mã dễ đọc, dễ hiểu hơn và dễ bảo trì hơn so với phương pháp truyền thống sử dụng XML.

• Dễ dàng tích hợp: Jetpack Compose tích hợp tốt với các thành phần và cơng nghệ hiện có của Android, cho phép bạn sử dụng các tính năng mạnh mẽ khác như ViewModel, LiveData và Room để quản lý trạng thái ứng dụng và tương tác với cơ sở dữ liệu.

• Hiệu suất cao hơn: Jetpack Compose được tối ưu để cung cấp hiệu suất cao hơn so với phương pháp truyền thống. Compose sử dụng một cơ chế gọi là "Recompose" để chỉ cập nhật lại các phần tử giao diện người dùng đã thay đổi thay vì cập nhật tồn bộ giao diện. Điều này giúp giảm tải cho CPU và cải thiện hiệu suất của ứng dụng.

• Tương thích và tái sử dụng tốt hơn: Jetpack Compose được thiết kế để tương thích với các phiên bản Android cũ hơn và mới hơn. Bạn có thể sử dụng Compose trong các dự án hiện có hoặc tích hợp nó với mã Java/Kotlin truyền thống. Ngoài ra, Jetpack Compose cũng khuyến khích tái sử dụng mã và các thành phần giao diện người dùng, giúp tiết kiệm thời gian và cơng sức trong q trình phát triển.

• Cộng đồng mạnh mẽ: Jetpack Compose đã nhận được sự hỗ trợ rất lớn từ cộng đồng phát triển Android. Có nhiều tài liệu, ví dụ mã và nguồn tài nguyên hữu ích được chia sẻ trên trang web của Google và từ các nhà phát triển khác trên Internet. Bạn cũng có thể nhận được sự hỗ trợ từ cộng đồng qua các diễn đàn, nhóm Facebook, và các nhóm Telegram.

Tổng thể, Jetpack Compose là một framework mạnh mẽ và tiện ích cho việc phát triển giao diện người dùng Android. Nó giúp bạn tăng năng suất, cải thiện hiệu suất ứng dụng và tạo ra giao diện linh hoạt, tương tác.

<b>4. Mô hình tư duy trong Jetpack Compose </b>

Jetpack Compose là bộ công cụ khai báo giao diện người dùng hiện đại cho Android. Compose sẽ giúp bạn dễ dàng ghi và duy trì giao diện người dùng ứng dụng bằng cách cung cấp API khai báo cho phép bạn hiển thị giao diện người dùng

</div><span class="text_page_counter">Trang 13</span><div class="page_container" data-page="13">

của ứng dụng mà không cần ra lệnh thay đổi các chế độ xem giao diện người dùng. Thuật ngữ này cần được giải thích đơi chút nhưng có ý nghĩa rất quan trọng đối với cách thiết kế ứng dụng của bạn.

<b>4.1. Mơ hình lập trình khai báo </b>

Trước đây, hệ phân cấp chế độ xem Android được biểu thị dưới dạng cây tiện ích giao diện người dùng. Khi trạng thái của ứng dụng thay đổi vì những yếu tố như hoạt động tương tác của người dùng, bạn cần cập nhật hệ thống phân cấp giao diện người dùng để hiển thị dữ liệu hiện tại. Cách phổ biến nhất để cập nhật giao diện người dùng là hướng dẫn cho cây bằng các hàm như findViewById() và thay đổi các nút bằng những phương thức gọi như button.setText(String), container.addChild(View) hoặc img.setImageBitmap(Bitmap). Các phương thức này thay đổi trạng thái nội bộ của tiện ích.

Việc điều chỉnh chế độ xem theo cách thủ công sẽ làm tăng khả năng xảy ra lỗi. Nếu một phần dữ liệu hiển thị ở nhiều vị trí thì bạn sẽ có thể qn cập nhật một trong các chế độ xem hiển thị dữ liệu đó. Việc tạo các trạng thái không hợp lệ cũng rất dễ xảy ra khi hai lượt cập nhật xung đột theo cách khơng mong đợi. Ví dụ: một lượt cập nhật có thể cố gắng đặt một giá trị nút vừa mới bị xố khỏi giao diện người dùng. Nói chung, độ phức tạp của việc bảo trì phần mềm tăng lên cùng với số chế độ xem cần cập nhật.

Trong vài năm qua, toàn bộ ngành đã bắt đầu chuyển sang mơ hình giao diện người dùng khai báo, giúp đơn giản hoá rất nhiều kỹ thuật liên quan đến việc xây dựng và cập nhật giao diện người dùng. Kỹ thuật này hoạt động bằng cách tạo lại từ đầu tồn bộ màn hình, sau đó chỉ áp dụng những thay đổi cần thiết. Phương pháp này giúp bạn dễ dàng cập nhật hệ phân cấp chế độ xem có trạng thái theo cách thủ cơng. Compose là một khung giao diện người dùng khai báo.

Một khó khăn trong việc tạo lại tồn bộ màn hình là giải pháp này có thể tốn

</div><span class="text_page_counter">Trang 14</span><div class="page_container" data-page="14">

Bằng cách sử dụng Compose, bạn có thể xây dựng giao diện người dùng bằng cách xác định một tập hợp các hàm có khả năng kết hợp lấy dữ liệu và cung cấp các thành phần trên giao diện người dùng. Một ví dụ đơn giản là hàm Greeting lấy String và cung cấp Text hiển thị tin nhắn chào mừng.

<b>Hình 1. Một hàm có khả năng kết hợp đơn giản được chuyển dữ liệu và sử dụng dữ </b>

liệu này để hiển thị một tiện ích văn bản trên màn hình. Vài điều đáng chú ý về hàm này:

• Hàm này được chú thích bằng chú thích @Composable. Tất cả hàm có khả năng kết hợp đều phải có chú thích này; chú thích này sẽ thơng báo cho trình biên dịch Compose rằng hàm này có mục đích chuyển đổi dữ liệu thành giao diện người dùng.

• Hàm này lấy dữ liệu. Các hàm có khả năng kết hợp có thể chấp nhận các thông số cho phép logic của ứng dụng mô tả giao diện người dùng. Trong trường hợp này, tiện ích của chúng tơi chấp nhận String để có thể chào người dùng theo tên.

• Hàm này hiển thị văn bản trong giao diện người dùng. Hàm này thực hiện tính năng này bằng cách gọi hàm có khả năng kết hợp Text() - hàm thực sự tạo ra thành phần văn bản trên giao diện người dùng. Hàm có khả năng kết hợp cung cấp sự phân cấp giao diện người dùng bằng cách gọi các hàm có khả năng kết hợp khác.

</div><span class="text_page_counter">Trang 15</span><div class="page_container" data-page="15">

• Hàm này khơng trả về bất kỳ giá trị nào. Các hàm Compose cung cấp giao diện người dùng khơng cần trả về bất cứ điều gì vì các hàm này mơ tả trạng thái màn hình mong muốn thay vì tạo các tiện ích giao diện người dùng. • Hàm này nhanh chóng, khơng thay đổi giá trị và khơng có tác dụng phụ. • Hàm này hoạt động theo cách tương tự như khi được gọi nhiều lần với cùng

một đối số và không sử dụng các giá trị khác như biến tồn cục hoặc lệnh gọi đến random().

• Hàm này mô tả giao diện người dùng mà không để lại bất kỳ tác dụng phụ nào, chẳng hạn như sửa đổi các thuộc tính hoặc biến tồn cục.

Nhìn chung, bạn phải viết tất cả các hàm có khả năng kết hợp bằng các thuộc tính này vì những lý do được thảo luận trong phần kết hợp lại.

<b>4.3. Thay đởi mơ hình khai báo </b>

Với nhiều bộ công cụ giao diện người dùng hướng đối tượng bắt buộc, bạn khởi chạy giao diện người dùng bằng cách tạo một cây tiện ích. Bạn thường thực hiện việc này bằng cách tăng cường một tệp bố cục XML. Mỗi tiện ích duy trì một trạng thái nội bộ riêng và hiển thị các phương thức getter và setter cho phép logic ứng dụng tương tác với tiện ích đó.

Trong phương pháp khai báo của Compose, các tiện ích tương đối khơng có trạng thái và khơng hiển thị các hàm setter hoặc getter. Trên thực tế, các tiện ích khơng xuất hiện dưới dạng đối tượng. Bạn cập nhật giao diện người dùng bằng cách gọi cùng một hàm có khả năng kết hợp với các đối số khác nhau. Việc này giúp bạn dễ dàng cung cấp trạng thái cho các mẫu kiến trúc, chẳng hạn như ViewModel, như mô tả trong Hướng dẫn về cấu trúc ứng dụng. Sau đó, các yếu tố có thể kết hợp sẽ chịu trách nhiệm chuyển đổi trạng thái ứng dụng hiện tại thành giao diện người dùng mỗi khi cập nhật dữ liệu có thể quan sát.

</div><span class="text_page_counter">Trang 16</span><div class="page_container" data-page="16">

<b>Hình 2: Logic của ứng dụng cung cấp dữ liệu cho hàm có khả năng kết hợp cấp </b>

cao nhất. Hàm đó sử dụng dữ liệu để mô tả giao diện người dùng bằng cách gọi các yếu tố có thể kết hợp khác và chuyển dữ liệu thích hợp cho các yếu tố có thể kết hợp đó và xuống hệ thống phân cấp.

Khi người dùng tương tác với giao diện người dùng, giao diện người dùng sẽ

<i>đưa ra các sự kiện như onClick. Những sự kiện đó sẽ thơng báo cho logic ứng </i>

dụng, qua đó có thể thay đổi trạng thái của ứng dụng. Khi trạng thái thay đổi, các hàm có khả năng kết hợp được gọi lại bằng dữ liệu mới. Việc này khiến các thành phần trên giao diện người dùng được vẽ lại–quy trình này được gọi là

<i>recomposition. </i>

</div><span class="text_page_counter">Trang 17</span><div class="page_container" data-page="17">

<b>Hình 3. Người dùng tương tác với một thành phần trên giao diện người dùng, </b>

khiến sự kiện được kích hoạt. Logic của ứng dụng phản hồi sự kiện, sau đó các hàm có khả năng kết hợp sẽ tự động được gọi lại bằng các thông số mới (nếu cần).

<b>4.4. Nội dung động </b>

Vì các hàm có khả năng kết hợp được viết bằng Kotlin thay vì XML nên các hàm này có thể linh động như mọi mã Kotlin khác. Ví dụ: giả sử bạn muốn tạo giao diện người dùng chào đón một danh sách người dùng:

</div><span class="text_page_counter">Trang 18</span><div class="page_container" data-page="18">

<b>4.5. Recomposition </b>

Trong mơ hình giao diện người dùng bắt buộc, để thay đổi một tiện ích, bạn phải gọi một phương thức setter trên tiện ích đó để thay đổi trạng thái nội bộ của phương thức đó. Trong Compose, bạn gọi lại hàm có khả năng kết hợp với dữ liệu mới. Cách làm này sẽ giúp vẽ lại hàm được kết hợp lại–các tiện ích do hàm cung cấp (nếu cần) cùng với dữ liệu mới. Khung Compose có thể chỉ kết hợp lại các thành phần đã thay đổi một cách thơng minh.

Ví dụ: hãy xem xét hàm có khả năng kết hợp hiển thị một nút này:

<i>Hình 5 </i>

Mỗi khi người dùng nhấp vào nút này, trình gọi sẽ cập nhật giá trị của clicks. Compose gọi lambda bằng hàm Text để hiển thị giá trị mới; quy trình này được gọi là kết hợp lại. Các hàm khác không phụ thuộc vào giá trị không được kết hợp lại.

Như chúng ta đã thảo luận, việc kết hợp lại toàn bộ cây giao diện người dùng có thể sẽ tốn kém vì sẽ mất thêm cơng suất tính tốn và thời lượng pin. Compose giải quyết vấn đề này thông qua phương thức kết hợp lại thơng minh sau.

Kết hợp lại là q trình gọi lại các hàm có khả năng kết hợp khi giá trị nhập thay đổi. Quá trình này được thực hiện khi dữ liệu đầu vào của hàm thay đổi. Khi Compose kết hợp lại dựa trên dữ liệu nhập mới, công cụ này chỉ gọi các hàm hoặc lambda có thể đã thay đổi và bỏ qua các yếu tố còn lại. Bằng cách bỏ qua tất cả các hàm hoặc lambda khơng có thơng số được thay đổi, Compose có thể kết hợp lại một cách hiệu quả.

Không phụ thuộc vào tác dụng phụ của việc thực thi các hàm có khả năng kết hợp vì việc kết hợp lại một hàm có thể bị bỏ qua. Nếu bạn thực hiện điều này, người dùng có thể gặp phải hành vi lạ và khơng thể đốn trước trong ứng dụng. Tác dụng phụ là bất kỳ thay đổi nào hiển thị với phần còn lại của ứng dụng. Ví dụ: những hành động sau đây đều là tác dụng phụ nguy hiểm:

• Ghi vào thuộc tính của đối tượng dùng chung • Cập nhật giá trị có thể quan sát trong ViewModel

</div><span class="text_page_counter">Trang 19</span><div class="page_container" data-page="19">

• Cập nhật các tuỳ chọn dùng chung

Các hàm có khả năng kết hợp có thể được thực thi lại thường xuyên trong mỗi khung hình, chẳng hạn như khi hệ thống hiển thị một ảnh động. Các hàm có khả năng kết hợp phải nhanh chóng để tránh bị giật trong ảnh động. Nếu bạn cần thực hiện các thao tác tốn kém, chẳng hạn như đọc từ các tuỳ chọn dùng chung, hãy thực hiện việc này trong coroutine trong nền và chuyển giá trị kết quả đến hàm có khả năng kết hợp dưới dạng thơng số.

Các phần sau đây sẽ trình bày cách tạo hàm có khả năng kết hợp để hỗ trợ q trình kết hợp lại. Trong mọi trường hợp, phương pháp hay nhất là đảm bảo các hàm có khả năng kết hợp chạy nhanh, không thay đổi giá trị và không có tác dụng phụ.

<b>4.5.1. Các hàm composable có thể thực thi theo thứ tự bất kỳ </b>

Nếu kiểm tra mã cho một hàm có khả năng kết hợp, bạn có thể giả định rằng mã được chạy theo thứ tự nó xuất hiện. Tuy nhiên, điều này khơng nhất thiết đúng. Nếu một hàm có khả năng kết hợp chứa lệnh gọi đến các hàm có khả năng kết hợp khác thì các hàm đó có thể chạy theo thứ tự bất kỳ. Bạn có thể dùng Compose để nhận diện một số thành phần trên giao diện người dùng có mức độ ưu tiên cao hơn các thành phần khác và vẽ ra các thành phần đó trước.

Ví dụ: giả sử bạn có mã như sau để vẽ 3 màn hình trong một bố cục thẻ:

Các lệnh gọi đến StartScreen, MiddleScreen và EndScreen có thể xuất hiện theo thứ tự bất kỳ. Điều này có nghĩa là bạn không thể để StartScreen() đặt một số

</div><span class="text_page_counter">Trang 20</span><div class="page_container" data-page="20">

chạy các hàm có khả năng kết hợp khơng có trên màn hình ở mức độ ưu tiên thấp hơn.

Tính năng tối ưu hố này có nghĩa là một hàm có khả năng kết hợp có thể thực thi trong một nhóm các chuỗi nền. Nếu một hàm có khả năng kết hợp gọi một hàm trên ViewModel thì Compose có thể gọi hàm đó từ nhiều chuỗi cùng lúc.

Để đảm bảo ứng dụng hoạt động chính xác, tất cả hàm có khả năng kết hợp khơng được có tác dụng phụ. Thay vào đó, hãy kích hoạt các tác dụng phụ của lệnh gọi lại, chẳng hạn như onClick luôn thực thi trên chuỗi giao diện người dùng.

Khi một hàm có khả năng kết hợp được gọi, lệnh gọi có thể xảy ra trên một chuỗi khác với phương thức gọi. Điều đó có nghĩa là bạn nên tránh sử dụng mã sửa đổi các biến dưới dạng lambda có thể kết hợp vì loại mã này khơng an tồn cho chuỗi và vì đây là một tác dụng phụ khơng thể chấp nhận được của lambda có thể kết hợp.

Sau đây là ví dụ về một composable cho thấy danh sách và số lượng:

Mã này khơng có tác dụng phụ và chuyển đổi danh sách nhập vào giao diện người dùng. Đây là mã lý tưởng cho việc hiển thị một danh sách nhỏ. Tuy nhiên, nếu hàm này ghi vào một biến cục bộ thì mã này sẽ khơng đúng hoặc khơng an tồn cho chuỗi:

</div><span class="text_page_counter">Trang 21</span><div class="page_container" data-page="21">

Ở ví dụ này, items được sửa đổi với các thành phần kết hợp lại. Đó có thể là mọi khung ảnh động hoặc khi danh sách cập nhật. Dù là điều nào thì giao diện người dùng cũng sẽ hiển thị số lượng khơng chính xác. Do đó, cách ghi này sẽ không được hỗ trợ trong Compose, với việc cấm các cách ghi đó, chúng tơi cho phép khung thay đổi các chuỗi thực thi lambda có thể kết hợp.

<b>4.5.3. Bỏ qua recomposition nhiều nhất có thể </b>

Khi các phần trong giao diện người dùng của bạn không hợp lệ, Compose sẽ cố gắng hết sức để kết hợp lại các phần cần cập nhật. Điều này có nghĩa là cơng cụ này có thể bỏ qua để chạy lại thành phần có thể kết hợp của một nút mà không cần thực thi bất kỳ thao tác có thể kết hợp nào ở trên hoặc bên dưới trong cây giao diện người dùng.

Mọi hàm và lambda có khả năng kết hợp đều có thể tự phân tích lại. Sau đây là ví dụ minh họa cách việc kết hợp lại có thể bỏ qua một số phần tử khi hiển thị một danh sách:

</div><span class="text_page_counter">Trang 22</span><div class="page_container" data-page="22">

Mỗi phạm vi này có thể là nội dung duy nhất cần thực thi trong q trình kết hợp lại. Compose có thể bỏ qua cho đến lambda Column mà không cần thực thi bất kỳ yếu tố gốc nào của yếu tố này khi header thay đổi. Và khi thực thi Column, Compose có thể chọn bỏ qua các mục của LazyColumn nếu names không thay đổi.

Xin nhắc lại, việc thực thi tất cả hàm hoặc lambda có khả năng kết hợp khơng được có tác dụng phụ. Khi bạn cần thực hiện tác dụng phụ, hãy kích hoạt từ lệnh gọi lại.

<b>4.5.4. Recomposition là khả quan </b>

Việc kết hợp lại bắt đầu lại bất cứ khi nào Compose cho rằng các thông số của một thành phần có thể kết hợp có thể đã thay đổi. Việc kết hợp lại là khả quan, nghĩa là Compose sẽ hoàn thành việc kết hợp lại trước khi các thông số thay đổi lần nữa.

</div><span class="text_page_counter">Trang 23</span><div class="page_container" data-page="23">

Nếu một thông số thay đổi trước khi quá trình kết hợp lại hoàn tất thì Compose có thể sẽ hủy q trình kết hợp lại đó và bắt đầu lại bằng thơng số mới.

Khi quá trình kết hợp lại bị hủy, Compose sẽ loại bỏ cây giao diện người dùng khỏi quá trình kết hợp lại. Nếu bạn có bất kỳ tác dụng phụ nào phụ thuộc vào giao diện người dùng đang hiển thị thì tác dụng phụ này sẽ được áp dụng ngay cả khi quá trình kết hợp lại bị hủy. Điều này có thể làm cho trạng thái ứng dụng không nhất quán.

Hãy đảm bảo rằng tất cả hàm và lambda có khả năng kết hợp đều giống nhau và khơng có tác dụng phụ khi xử lý việc kết hợp lại một cách khả quan.

<b>4.5.5. Các hàm có khả năng recomposition có thể chạy khá thường xuyên </b>

Trong một số trường hợp, một hàm có khả năng kết hợp có thể chạy cho mọi khung giao diện người dùng. Nếu thực hiện các thao tác tốn kém (chẳng hạn như đọc từ bộ nhớ của thiết bị) thì hàm này có thể khiến giao diện người dùng bị giật.

Ví dụ: nếu tiện ích cố gắng đọc chế độ cài đặt của thiết bị thì tiện ích đó có thể đọc chế độ cài đặt hàng trăm lần mỗi giây và gây ra ảnh hưởng tiêu cực đến hiệu suất của ứng dụng.

Nếu hàm có khả năng kết hợp của bạn cần dữ liệu thì hàm đó phải xác định các thơng số cho dữ liệu. Sau đó, bạn có thể chuyển thao tác tốn kém sang một chuỗi khác, bên ngoài thành phần kết hợp và chuyển dữ liệu sang Compose bằng

<i>mutableStateOf hoặc LiveData. </i>

<b>5. Bố cục Compose cơ bản 5.1. Mục tiêu </b>

Việc triển khai hệ thống bố cục trong Jetpack Compose có hai mục tiêu chính: • Hiệu suất cao

</div><span class="text_page_counter">Trang 24</span><div class="page_container" data-page="24">

Hàm có khả năng kết hợp là khối xây dựng cơ bản trong Compose. Hàm có khả năng kết hợp là một hàm chèn Unit mô tả một số phần trên giao diện người dùng. Hàm sử dụng một số dữ liệu đầu vào và tạo nội dung hiển thị trên màn hình. Để biết thêm thơng tin về các thành phần kết hợp, hãy xem tài liệu Mơ hình tư duy của Compose.

Một hàm có khả năng kết hợp có thể chuyển phát nhiều thành phần trên giao diện người dùng. Tuy nhiên, nếu bạn không đưa ra cách sắp xếp, thì Compose có thể sắp xếp các thành phần theo cách mà bạn khơng muốn. Ví dụ: mã này tạo ra hai thành phần văn bản:

Nếu bạn không hướng dẫn cách sắp xếp các thành phần này, thì cơng cụ Compose sẽ xếp chồng các thành phần văn bản lên nhau, khiến cho chúng không thể đọc được:

Compose cung cấp một tập hợp các bố cục sẵn sàng sử dụng để giúp bạn sắp xếp các thành phần trên giao diện người dùng và giúp bạn dễ dàng xác định các bố cục của riêng mình, ở mức độ chuyên biệt cao hơn.

<b>5.3. Các bố cục cơ bản </b>

Trong nhiều trường hợp, bạn chỉ cần sử dụng Các thành phần của bố cục tiêu chuẩn trong Compose.

<i>Hãy dùng Column để đặt các mục theo chiều dọc trên màn hình. </i>

</div><span class="text_page_counter">Trang 25</span><div class="page_container" data-page="25">

<i>Tương tự, hãy dùng Row để đặt các mục theo chiều ngang trên màn hình. Cả hai mã Column và Row đều hỗ trợ định cấu hình căn chỉnh các thành phần có trong </i>

mã.

<i>Sử dụng Box để xếp các thành phần chồng lên nhau. Box cũng hỗ trợ định cấu </i>

hình căn chỉnh cụ thể các thành phần có trong mã.

</div><span class="text_page_counter">Trang 26</span><div class="page_container" data-page="26">

Thông thường, các khối xây dựng này là tất cả những gì bạn cần. Bạn có thể viết hàm có khả năng kết hợp của riêng mình để kết hợp các bố cục này vào một bố cục chi tiết hơn phù hợp với ứng dụng của bạn.

<b>Lưu ý: Compose xử lý hiệu quả các bố cục lớp lồng nhau, khiến chúng trở </b>

thành một cách tuyệt vời để thiết kế giao diện người dùng phức tạp. Đây là một sự cải tiến so với Android Views, trong đó bạn cần tránh các bố cục lớp lồng ghép vì lý do hiệu suất.

<i>Để đặt vị trí bố cục con trong Row, hãy đặt các đối số horizontalArrangement và verticalAlignment. Đối với Column, hãy đặt các đối số verticalArrangement và horizontalAlignment: </i>

</div><span class="text_page_counter">Trang 27</span><div class="page_container" data-page="27">

<b>5.4. Mơ hình bố cục </b>

Trong mơ hình bố cục, cây giao diện người dùng được sắp xếp chỉ trong một luồng. Trước tiên, mỗi nút được yêu cầu tự đo lường, sau đó đo lường định kỳ bất cứ thành phần con cháu nào, chuyển các giới hạn kích thước xuống dọc theo cây cho các thành phần con cháu. Sau đó, cần định kích thước và đặt các nút lá, rồi các kích cỡ đã được xác định cùng các câu lệnh hướng dẫn vị trí được chuyển trở lại cây.

Tóm lại, cần đo lường các thành phần mẹ trước các thành phần con cháu, nhưng việc định kích thước và đặt các thành phần mẹ lại diễn ra sau các thành phần con cháu.

<i>Hãy xem xét hàm SearchResult sau. </i>

</div><span class="text_page_counter">Trang 28</span><div class="page_container" data-page="28">

Hàm này tạo cây giao diện người dùng sau đây.

<i>Trong ví dụ SearchResult, bố cục cây giao diện người dùng tuân theo thứ tự </i>

sau:

<i>1. Câu lệnh yêu cầu đo lường nút gốc Row. </i>

<i>2. Nút gốc Row yêu cầu nút con cháu đầu tiên của nó, Image, đo lường. 3. Image là một nút lá (nút khơng có nút con cháu), vì vậy nút này báo cáo </i>

kích thước và trả về các câu lệnh hướng dẫn vị trí.

<i>4. Nút gốc Row yêu cầu nút con cháu thứ hai của nó, Column, đo lường. 5. Nút Column yêu cầu nút con cháu đầu tiên của nó, Text, đo lường. </i>

<i>6. Nút Text đầu tiên là một nút lá. Nút lá chỉ báo cáo kích thước và trả về </i>

câu lệnh hướng dẫn vị trí.

<i>7. Nút Column yêu cầu nút con cháu Text thứ hai của nó đo lường. </i>

<i>8. Nút Text thứ hai là một nút lá, vì vậy nút này báo cáo kích thước và trả về </i>

câu lệnh hướng dẫn vị trí.

<i>9. Hiện tại, nút Column đã đo lường xong, đã định kích thước và đặt các nút </i>

con cháu của nó, nút này có thể xác định kích thước và vị trí của riêng mình.

<i>10. Hiện tại, nút gốc Row đã đo lường xong, đã định kích thước và đặt các </i>

nút con cháu của nó, nút này có thể xác định kích thước và vị trí của riêng mình.

</div><span class="text_page_counter">Trang 29</span><div class="page_container" data-page="29">

<b>5.5. Hiệu suất </b>

Compose đạt được hiệu suất cao bằng cách đo lường nút con cháu một lần duy nhất. Chế độ đo lường một luồng tốt cho hiệu suất, cho phép công cụ Compose xử lý sâu các cây giao diện người dùng một cách hiệu quả. Nếu một thành phần đo lường thành phần con cháu của nó hai lần và thành phần con cháu đó đo lường từng thành phần con cháu của nó hai lần và cứ tiếp tục như vậy, thì một lần thử duy nhất để tạo ra toàn bộ Giao diện người dùng sẽ phải thực hiện rất nhiều việc, khiến ứng dụng của bạn khó duy trì hoạt động.

Nếu bố cục của bạn cần nhiều lần đo lường vì một lý do nào đó, Compose có một hệ thống đặc biệt, phép đo lường hàm nội tại.

Vì việc đo lường và vị trí là các giai đoạn phụ riêng biệt của luồng bố cục, nên các thay đổi chỉ ảnh hưởng đến vị trí của các mục mà không ảnh hưởng đến hoạt động đo lường, đều có thể được thực hiện riêng biệt.

<b>5.6. Sử dụng modifiers </b>

Như đã đề cập ở phần các đối tượng sửa đổi trong Compose, bạn có thể sử dụng đối tượng sửa đổi để trang trí hoặc bổ sung các thành phần kết hợp của bạn. Các đối tượng sửa đổi là thành phần cần thiết để tuỳ chỉnh bố cục của bạn. Ví dụ: ở

<i>đây chúng tơi liên kết một số đối tượng sửa đổi để tuỳ chỉnh ArtistCard: </i>

</div><span class="text_page_counter">Trang 30</span><div class="page_container" data-page="30">

Trong mã trên, hãy chú ý đến các đối tượng sửa đổi có chức năng khác nhau được sử dụng cùng nhau.

<i>• clickable tạo một phản ứng kết hợp với thông tin do người dùng nhập và </i>

hiển thị một hiệu ứng gợn sóng.

<i>• padding đặt khoảng trống quanh một thành phần. </i>

<i>• fillMaxWidth làm cho thành phần kết hợp lấp đầy chiều rộng tối đa mà </i>

thành phần mẹ đã cấp cho nó.

<i>• size() xác định chiều rộng và chiều cao ưu tiên của một thành phần. </i>

<b>Lưu ý: Cùng với một số nội dung khác, đối tượng sửa đổi đóng vai trị tương </b>

tự như vai trị của các thông số bố cục trong bố cục chế độ xem. Tuy nhiên, vì đối tượng sửa đổi đơi khi theo phạm vi cụ thể, nên chúng cung cấp sự an toàn về loại cũng như giúp bạn khám phá và tìm hiểu nội dung có sẵn và phù hợp với một bố cục nhất định. Với bố cục XML, đôi khi khó để biết liệu một thuộc tính bố cục cụ thể có áp dụng cho một chế độ xem nhất định hay khơng.

Ngồi ra cịn có các loại bố cục đặc biệt khác như Scrollable Layout, Reponsive Layout, Slot-base Layout không được đề cập trong tài liệu này

<b>6. Quản lý trạng thái </b>

</div><span class="text_page_counter">Trang 31</span><div class="page_container" data-page="31">

Trạng thái trong ứng dụng là giá trị bất kỳ có thể thay đổi theo thời gian. Đây

<i>là định nghĩa rất rộng và bao gồm mọi thứ từ cơ sở dữ liệu Room cho đến một biến </i>

• Một bài đăng trên blog và các bình luận liên quan.

• Ảnh động gợn sóng trên các nút phát khi người dùng nhấp vào. • Hình dán mà người dùng có thể vẽ lên hình ảnh.

Jetpack Compose giúp bạn hiểu rõ vị trí và cách thức lưu trữ cũng như dùng trạng thái trong một ứng dụng Android. Tài liệu hướng dẫn này tập trung vào hoạt động kết nối giữa các trạng thái và thành phần kết hợp (composable), đồng thời tập trung vào những API mà Jetpack Compose cung cấp để xử lý trạng thái dễ dàng hơn.

<b>6.1. Trạng thái và composition </b>

Compose mang tính khai báo và vì vậy, cách duy nhất để cập nhật Compose là gọi cùng một thành phần kết hợp (composable) với đối số mới. Các đối số này là đại diện cho trạng thái giao diện người dùng. Mỗi khi một trạng thái được cập nhật, một lượt tái cấu trúc (recomposition) diễn ra. Do đó, những thành phần như TextField sẽ không tự động cập nhật như đối với khung hiển thị dựa trên XML bắt buộc. Một thành phần kết hợp phải được thông báo rõ ràng về trạng thái mới để cập nhật tương ứng.

</div><span class="text_page_counter">Trang 32</span><div class="page_container" data-page="32">

Nếu thực hiện việc này, bạn sẽ thấy rằng khơng có điều gì xảy ra. Nguyên nhân là do TextField không tự cập nhật, mà sẽ cập nhật khi tham số value của nó thay đổi. Lý do nằm ở cách hoạt động của tính năng cấu trúc (composition) và tái cấu trúc (recomposition) trong Compose.

<b>6.2. Trạng thái trong composition </b>

Các hàm có khả năng kết hợp có thể sử dụng API remember để lưu trữ đối tượng trong bộ nhớ. Một giá trị do remember tính tốn được lưu trữ trong Cấu trúc (Composition) trong quá trình cấu trúc ban đầu. Giá trị đã lưu trữ được trả về trong quá trình tái cấu trúc. Bạn có thể dùng remember để lưu trữ cả đối tượng có thể thay đổi và khơng thể thay đổi.

<i>mutableStateOf tạo ra MutableState<T> có thể quan sát. Đây là một loại đối </i>

tượng có thể quan sát được tích hợp với thời gian chạy Compose.

Mọi thay đổi đối với value sẽ lên lịch tái cấu trúc mọi hàm có khả năng kết hợp có thể đọc value. Trong trường hợp ExpandingCard, bất cứ khi nào expanded thay đổi, hệ thống sẽ tái cấu trúc ExpandingCard.

Có 3 cách để khai báo đối tượng MutableState trong một thành phần kết hợp:

<i>• val mutableState = remember { mutableStateOf(default) } • var value by remember { mutableStateOf(default) } </i>

</div><span class="text_page_counter">Trang 33</span><div class="page_container" data-page="33">

<i>• val (value, setValue) = remember { mutableStateOf(default) } </i>

Các thông tin khai báo này là tương đương và được cung cấp dưới dạng cú pháp dễ hiểu theo mục đích sử dụng của trạng thái. Bạn nên chọn định dạng tạo ra mã dễ đọc nhất trong thành phần kết hợp mà bạn đang viết.

<i>Cú pháp uỷ quyền (delegate syntax) by yêu cầu các import sau: </i>

Bạn có thể sử dụng giá trị đã ghi nhớ làm tham số cho các thành phần kết hợp khác hoặc thậm chí là logic trong các câu lệnh để thay đổi thành phần kết hợp được hiển thị. Ví dụ: nếu bạn không muốn hiện lời chào nếu phần tên trống, hãy sử dụng

<i>trạng thái trong câu lệnh if: </i>

<i>Mặc dù remember giúp bạn giữ lại trạng thái trên các lần tái cấu trúc, trạng </i>

thái này sẽ không được giữ lại khi bạn thay đổi cấu hình. Để làm được điều này,

</div><span class="text_page_counter">Trang 34</span><div class="page_container" data-page="34">

nó trở thành khơng trạng thái. Mơ hình chung để di chuyển trạng thái lên trên trong Jetpack Compose là thay thế biến trạng thái bằng 2 tham số:

<i>• value: T: giá trị hiện tại để hiển thị </i>

<i>• onValueChange: (T) -> Unit: một sự kiện yêu cầu thay đổi giá trị này, trong đó T là giá trị mới được đề xuất </i>

<i>Tuy nhiên, bạn không bị giới hạn ở onValueChange. Nếu các sự kiện cụ thể </i>

hơn phù hợp với thành phần kết hợp, bạn nên xác định sự kiện bằng cách sử dụng

<i>lambda như ExpandingCard với onExpand và onCollapse. </i>

Trạng thái được di chuyển lên trên theo cách này có một số thuộc tính quan trọng:

• Một nguồn đáng tin cậy (single source of truth): Bằng cách di chuyển trạng thái thay vì sao chép, chúng tơi đảm bảo rằng chỉ có một nguồn thông tin duy nhất. Điều này giúp tránh các lỗi.

• Được đóng gói (encapsulated): Chỉ các thành phần kết hợp có trạng thái mới có thể sửa đổi trạng thái của chúng. Nó có tính nội bộ hồn tồn. • Có thể chia sẻ (shareable): Bạn có thể chia sẻ trạng thái được di chuyển

<i>lên trên với nhiều thành phần kết hợp. Nếu bạn muốn đọc name trong </i>

một thành phần kết hợp khác, việc di chuyển trạng thái lên trên sẽ cho phép bạn làm việc đó.

• Có thể chắn (interceptable): phương thức gọi đến các thành phần kết hợp khơng trạng thái có thể quyết định bỏ qua hoặc sửa đổi các sự kiện trước khi thay đổi trạng thái.

<i>• Được tách riêng (decoupled): trạng thái của ExpandingCard khơng có </i>

trạng thái có thể được lưu trữ ở bất cứ đâu. Ví dụ: bạn hiện có thể di

<i>chuyển name sang ViewModel. </i>

<i>Trong trường hợp ví dụ, bạn trích xuất name và onValueChange ra HelloContent rồi di chuyển chúng lên trên đến một thành phần kết hợp HelloScreen bằng lệnh gọi HelloContent. </i>

</div><span class="text_page_counter">Trang 35</span><div class="page_container" data-page="35">

<i>Bằng cách nâng trạng thái ra khỏi HelloContent, sẽ dễ dàng cho việc lý giải thành phần kết hợp, tái sử dụng trong nhiều tình huống và kiểm thử. HelloContent </i>

được tách riêng khỏi cách lưu trữ trạng thái của tệp. Việc tách riêng có nghĩa là nếu

<i>sửa đổi hoặc thay thế HelloScreen, bạn không phải thay đổi cách thức triển khai HelloContent. </i>

</div><span class="text_page_counter">Trang 36</span><div class="page_container" data-page="36">

Mơ hình mà trạng thái giảm và các sự kiện tăng lên được gọi là luồng dữ liệu một chiều (unidirectional). Trong trường hợp này, trạng thái giảm từ HelloScreen

<i>xuống HelloContent và các sự kiện tăng từ HelloContent lên HelloScreen. Bằng </i>

cách làm theo luồng dữ liệu một chiều, bạn có thể phân tách các thành phần có thể kết hợp hiển thị trạng thái trong giao diện người dùng khỏi các phần của ứng dụng lưu trữ và thay đổi trạng thái.

Lưu ý quan trọng: Khi chuyển trạng thái lên trên, có ba quy tắc giúp bạn tìm ra vị trí di chuyển trạng thái:

1. Trạng thái phải được di chuyển lên ít nhất đến trạng thái mẹ phổ biến thấp nhất trong tất cả thành phần kết hợp đang sử dụng trạng thái (đọc). 2. Trạng thái phải được di chuyển lên ít nhất đến cấp cao nhất mà nó có

thể được thay đổi (ghi).

3. Nếu 2 trạng thái thay đổi để phản hồi cùng một sự kiện, thì chúng phải được di chuyển lên trên cùng nhau

Bạn có thể chuyển trạng thái lên cao hơn so với yêu cầu trong các quy tắc này, nhưng trạng thái được chuyển lên trên không đúng mức sẽ làm cho việc đi theo luồng dữ liệu một chiều trở nên khó khăn hoặc bất khả thi.

<b>6.4. Khôi phục trạng thái </b>

<i>Sử dụng rememberSaveable để khôi phục trạng thái trên giao diện người dùng sau khi tái tạo một hoạt động hoặc quy trình. rememberSaveable giữ lại trạng thái trên các q trình tái cấu trúc Ngồi ra, rememberSaveable còn giữ nguyên trạng </i>

thái trên quá trình tái tạo một hoạt động hoặc quy trình.

Tất cả loại dữ liệu được thêm vào Bundle sẽ được lưu tự động. Nếu muốn lưu nội dung nào đó mà khơng thể thêm vào Bundle, bạn có thể làm theo các cách sau:

</div><span class="text_page_counter">Trang 37</span><div class="page_container" data-page="37">

<b>6.4.2. MapSaver </b>

Nếu vì lý do nào đó @Parcelize khơng phù hợp, bạn có thể sử dụng mapSaver để xác định quy tắc của riêng mình nhằm chuyển đổi một đối tượng thành một tập hợp giá trị mà hệ thống có thể lưu vào Bundle.

<b>6.5. Phần tử giữ trạng thái </b>

Có thể quản lý q trình chuyển trạng thái lên trên đơn giản trong chính các hàm có khả năng kết hợp. Tuy nhiên, nếu số lượng trạng thái cần theo dõi tăng lên

</div><span class="text_page_counter">Trang 38</span><div class="page_container" data-page="38">

<i>Ở đây, việc sử dụng hàm remember giúp giá trị MutableState vẫn tiếp tục có </i>

hiệu lực khi kết hợp lại.

Nói chung, remember nhận tham số lambda calculation. Trong lần chạy đầu

<i>tiên, remember sẽ gọi hàm lambda calculation và lưu trữ kết quả. Trong quá trình </i>

kết hợp lại, remember sẽ trả về giá trị được lưu trữ gần đây nhất.

<i>Ngoài trạng thái lưu vào bộ nhớ đệm, bạn cũng có thể sử dụng remember để </i>

lưu trữ đối tượng bất kỳ hoặc kết quả của một thao tác trong Cấu trúc (Composition) nếu tốn kém khi khởi chạy hoặc tính tốn. Bạn có thể khơng muốn lặp lại phép tính này trong mỗi q trình kết hợp lại. Ví dụ như việc tạo đối tượng

<i>ShaderBrush này có thể là một thao tác tốn kém: </i>

<i>remember lưu trữ giá trị cho đến khi thốt khỏi Cấu trúc. Tuy nhiên, có một cách để vơ hiệu hố giá trị đã lưu vào bộ nhớ đệm. API remember cũng lấy tham số </i>

key hoặc keys. Nếu bất kỳ khoá nào trong số này thay đổi thì lần tiếp theo khi hàm

<i>kết hợp lại, remember sẽ vơ hiệu hố bộ nhớ đệm và thực thi lại khối lambda tính </i>

tốn. Cơ chế này cho phép bạn kiểm sốt tồn thời gian của một đối tượng trong Cấu trúc. Phương pháp tính tốn này vẫn có hiệu lực cho đến khi giá trị nhập thay đổi, thay vì cho đến khi giá trị được ghi nhớ bị xố khỏi Cấu trúc.

Các ví dụ sau đây minh hoạ cách hoạt động của cơ chế này.

<i>Trong đoạn mã này, ShaderBrush được tạo và sử dụng làm màu nền của thành phần kết hợp Box. remember lưu trữ thực thể ShaderBrush vì việc tạo lại rất tốn kém, như chúng tôi đã giải thích trước đó. remember lấy avatarRes làm tham số key1, là hình nền đã chọn. Nếu avatarRes thay đổi, bút vẽ sẽ kết hợp lại với hình </i>

</div><span class="text_page_counter">Trang 39</span><div class="page_container" data-page="39">

<i>ảnh mới và áp dụng lại cho Box. Điều này có thể xảy ra khi người dùng chọn một </i>

hình ảnh khác trong bộ chọn làm hình nền.

Trong đoạn mã tiếp theo, trạng thái được chuyển lên trên lớp phần tử giữ trạng

<i>thái thuần tuý MyAppState. Phương thức này hiển thị một hàm rememberMyAppState để khởi chạy một phiên bản của lớp bằng cách dùng remember. Việc hiển thị các hàm như vậy để tạo phiên bản tiếp tục có hiệu lực </i>

trong quá trình kết hợp lại là một mẫu phổ biến trong Compose. Hàm

<i>rememberMyAppState nhận windowSizeClass, đóng vai trò là tham số key cho remember. Nếu tham số này thay đổi, ứng dụng cần tạo lại phần tử giữ trạng thái </i>

thuần tuý có giá trị mới nhất. Điều này có thể xảy ra nếu người dùng xoay thiết bị chẳng hạn.

Compose sử dụng cách triển khai tương đương của lớp để quyết định xem một

</div><span class="text_page_counter">Trang 40</span><div class="page_container" data-page="40">

hợp. Một thành phần Compose là cấu trúc dạng cây của các thành phần kết hợp mô tả Giao diện người dùng.

Khi Jetpack Compose chạy các thành phần kết hợp lần đầu tiên, trong quá trình kết hợp ban đầu, bộ công cụ này sẽ theo dõi các thành phần kết hợp bạn gọi để mô tả Giao diện người dùng trong một thành phần Compose. Sau đó, khi trạng thái ứng dụng thay đổi, Jetpack Compose sẽ lên lịch kết hợp lại. Quá trình kết hợp lại xảy ra khi Jetpack Compose tái thực thi các thành phần kết hợp có thể thay đổi theo thay đổi về trạng thái, tiếp đến cập nhật thành phần Compose để phản ánh mọi thay đổi.

Thành phần Compose chỉ có thể được q trình kết hợp ban đầu tạo ra và cập nhật bằng quá trình kết hợp lại. Phương pháp duy nhất để chỉnh sửa thành phần Compose là kết hợp lại.

<b>Hình 1. Vịng đời của một thành phần kết hợp trong thành phần Compose. </b>

Thành phần kết hợp này sẽ được nhập vào thành phần Compose, kết hợp lại từ 0 lần trở lên, cuối cùng ra khỏi thành phần Compose.

Quá trình kết hợp lại thường được kích hoạt khi có thay đổi đối với đối tượng

<i>State<T>. Compose sẽ theo dõi quá trình thay đổi này, đồng thời chạy tất cả thành phần kết hợp trong thành phần Compose có khả năng đọc State<T> và bất kỳ thành </i>

phần kết hợp q trình này gọi mà khơng thể bỏ qua.

</div>

×