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
GIỚI THIỆU TỔNG QUAN
Thông tin sinh viên
20520985 Bùi Lê Hoài An 20520985@gm.uit.edu.vn
Tổng quan đề tài
Chúng ta không thể phủ nhận sự hữu ích của các framework coss- 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
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 hoàn toà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
2.2 Phạm vi nghiên cứu: Đồ án tập trung vào nghiên cứu tổng quan cách thức hoạt động và cách sử dụng các thư viện - package cũng như cách lưu trữ dữ liệu của Jetpack Compose Đồng thời, em áp dụng các nghiên cứu trên vào một ứng dụng thực tế để giúp em có thể hiểu sâu hơn các kiến thức đã tìm hiểu
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
2.4 Kết quả hướng tới:
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 quá 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.
Công cụ sử dụng
Trong quá trình xây dựng phần mềm, em đã sử dụng các phần mềm sau:
• Android Studio: phát triển front end
• GitHub: quản lý source code
CÔNG NGHỆ SỬ DỤNG
Giới thiệu ngôn ngữ lập trình Kotlin
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 ngôn ngữ lập trình khác
• Kotlin hỗ trợ null safety, giúp tránh được các lỗi runtime liên quan đến null pointer
• Kotlin hỗ trợ extension function, giúp mở rộng tính năng của một lớp đối tượng mà không cần thay đổi mã nguồn gốc
• Kotlin có thể tương thích với mã nguồn Java, cho phép lập trình viên dễ dàng chuyển đổi từ Java sang Kotlin hoặc sử dụng cả hai ngôn ngữ trong một dự án
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 Ngoà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.
Jetpack Compose
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 cách sử dụng các hàm và phương thức trong ngôn ngữ lập trình Kotlin, thay vì sử dụng XML như trước đây
Với Jetpack Compose, người lập trình có thể tạo các giao diện người dùng động và tương tác với người dùng một cách dễ dàng Framework này cũng cung cấp cho người dùng các tính năng như tạo theme, animation, và layout cực kỳ linh hoạt Jetpack Compose được xây dựng trên cơ sở của các công nghệ hiện đại như Kotlin, coroutines, và Android Jetpack
Tại sao lại là Jetpack Compose?
• 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 toà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 quá 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.
Mô hình tư duy trong Jetpack Compose
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
Trang 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
4.1 Mô hình lập trình khai báo
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ể quên 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ị xoá 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 toà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 toàn bộ màn hình là giải pháp này có thể tốn kém về mặt thời gian, khả năng tính toán và mức sử dụng pin Để giảm thiểu chi phí này, Compose sẽ lựa chọn một cách thông minh những phần cần vẽ lại trên giao diện người dùng bất cứ lúc nào Điều này có một số ảnh hưởng đến cách bạn thiết kế các thành phần trên giao diện người dùng, như đã thảo luận trong bài viết về tính năng kết hợp lại
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
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ữ 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
• 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 toà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 toà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
4.3 Thay đổi mô hình khai báo
Bố cục Compose cơ bản
Việc triển khai hệ thống bố cục trong Jetpack Compose có hai mục tiêu chính:
• Khả năng viết các bố cục tuỳ chỉnh một cách dễ dàng
Lưu ý: Khi sử dụng hệ thống Android View, bạn có thể gặp phải một số vấn đề về hiệu suất khi lồng một số hệ thống View nhất định, chẳng hạn như RelativeLayout Vì Compose tránh nhiều chế độ đo lường, bạn có thể tạo các lớp lồng nhau nhiều nội dung theo ý muốn mà không ảnh hưởng đến hiệu suất
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
5.3 Các bố cục cơ bản
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
Hãy dùng Column để đặt các mục theo chiều dọc trên màn hình
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 mã
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 hình căn chỉnh cụ thể các thành phần có trong mã
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
Lưu ý: Compose xử lý hiệu quả các bố cục lớp lồng nhau, khiến chúng trở 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 Để đặ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:
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
Hãy xem xét hàm SearchResult sau
Hàm này tạo cây giao diện người dùng sau đây
Trong ví dụ SearchResult, bố cục cây giao diện người dùng tuân theo thứ tự sau:
1 Câu lệnh yêu cầu đo lường nút gốc Row
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 kích thước và trả về các câu lệnh hướng dẫn vị trí
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
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ề câu lệnh hướng dẫn vị trí
7 Nút Column yêu cầu nút con cháu Text thứ hai của nó đo lường
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ề câu lệnh hướng dẫn vị trí
9 Hiện tại, nút Column đã đo lường xong, đã định kích thước và đặt các 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
10 Hiện tại, nút gốc Row đã đo lường xong, đã định kích thước và đặt các 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
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
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ụ: ở đây chúng tôi liên kết một số đối tượng sửa đổi để tuỳ chỉnh ArtistCard:
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
• 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à hiển thị một hiệu ứng gợn sóng
• padding đặt khoảng trống quanh một thành phần
• fillMaxWidth làm cho thành phần kết hợp lấp đầy chiều rộng tối đa mà thành phần mẹ đã cấp cho nó
• size() xác định chiều rộng và chiều cao ưu tiên của một thành phần
Quản lý trạng thái
Trạng thái trong ứng dụng là giá trị bất kỳ có thể thay đổi theo thời gian Đây 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 trên một lớp (class)
Tất cả ứng dụng Android đều cho người dùng thấy trạng thái Sau đây là một số ví dụ về trạng thái trong ứng dụng Android:
• Một thanh thông báo nhanh cho biết thời điểm không thể thiết lập kết nối mạng
• 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
6.1 Trạng thái và composition
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
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
6.2 Trạng thái trong composition
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 toá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 mutableStateOf tạo ra MutableState có thể quan sát Đây là một loạ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:
• val mutableState = remember { mutableStateOf(default) }
• var value by remember { mutableStateOf(default) }
• val (value, setValue) = remember { mutableStateOf(default) }
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
Cú pháp uỷ quyền (delegate syntax) by yêu cầu các import sau:
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 trạng thái trong câu lệnh if:
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 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, bạn phải sử dụng rememberSaveable rememberSaveable tự động lưu mọi giá trị có thể lưu trong Bundle Đối với các giá trị khác, bạn có thể chuyển vào một đối tượng lưu tuỳ chỉnh
6.3 Chuyển trạng thái lên trên
Tính năng chuyển trạng thái lên trên (state hoisting) trong Compose là một dạng chuyển đổi trạng thái cho phương thức gọi của một thành phần kết hợp khiến
Trang 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ố:
• value: T: giá trị hiện tại để hiển thị
• 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
Tuy nhiên, bạn không bị giới hạn ở onValueChange Nếu các sự kiện cụ thể 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 lambda như ExpandingCard với onExpand và onCollapse
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ộ hoàn toàn
• Có thể chia sẻ (shareable): Bạn có thể chia sẻ trạng thái được di chuyển 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 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
• Được tách riêng (decoupled): trạng thái của ExpandingCard không có trạng thái có thể được lưu trữ ở bất cứ đâu Ví dụ: bạn hiện có thể di chuyển name sang ViewModel
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
Vòng đời của Composable
Như đã đề cập trong phần Quản lý trạng thái, thành phần Compose mô tả Giao diện người dùng của ứng dụng và được tạo ra bằng cách chạy các thành phần kết
Trang 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 quá 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
Hình 1 Vòng đời của một thành phần kết hợp trong thành phần Compose
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
State 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 và bất kỳ thành phần kết hợp quá trình này gọi mà không thể bỏ qua
Nếu một thành phần kết hợp được gọi nhiều lần thì nhiều thực thể sẽ được đặt trong thành phần Compose Mỗi lệnh gọi có vòng đời riêng trong thành phần Compose
Hình 2 Giá trị đại diện của MyComposable trong thành phần Compose Nếu một thành phần kết hợp được gọi nhiều lần, thì nhiều thực thể sẽ được đặt trong thành phần Compose Phần tử có màu riêng biểu thị một thực thể riêng biệt
7.2 Phân tích một Composable trong Composition
Thực thể của một thành phần kết hợp trong thành phần Compose được xác định qua vị trí gọi Trình biên dịch Compose xem mỗi vị trí gọi hoàn toàn riêng biệt với nhau Thao tác gọi các thành phần kết hợp từ nhiều vị trí gọi sẽ tạo ra nhiều thực thể của thành phần kết hợp trong thành phần Compose
Nếu trong quá trình kết hợp lại một thành phần kết hợp gọi các thành phần kết hợp khác những thành phần đã gọi trong quá trình kết hợp trước, Compose sẽ xác định các thành phần kết hợp nào đã được gọi Đồng thời, đối với các thành phần được gọi trong cả hai quá trình kết hợp, Compose sẽ tránh kết hợp lại nếu giá trị đầu vào không đổi
Việc bảo tồn mã nhận dạng là yếu tố quan trọng nhằm liên kết các hiệu ứng phụ với thành phần kết hợp Nhờ đó, các hiệu ứng phụ này có thể hoàn tất thành công thay vì phải tái khởi động cho mỗi quá trình kết hợp lại
Hãy xem ví dụ sau đây:
Trong đoạn mã trên, LoginScreen sẽ gọi thành phần kết hợp LoginError theo điều kiện và sẽ luôn gọi thành phần kết hợp LoginInput Mỗi lệnh gọi có một vị trí gọi và địa điểm nguồn duy nhất mà trình biên dịch sẽ sử dụng để xác định
Hình 3 Giá trị đại diện của LoginScreen trong thành phần Compose khi trạng thái thay đổi và quá trình kết hợp lại diễn ra Cùng màu có nghĩa mã này vẫn chưa được kết hợp lại
Mặc dù mức độ ưu tiên của việc gọi LoginInput chuyển từ thứ nhất sang thứ hai, nhưng LoginInput vẫn sẽ được giữ nguyên trong các thành phần kết hợp lại
Ngoài ra, do LoginInput không có bất kỳ tham số nào thay đổi trong quá trình kết hợp lại, Compose sẽ bỏ qua lệnh gọi LoginInput
7.2.1 Thêm thông tin hỗ trợ quá trình recomposition
Việc gọi một thành phần kết hợp nhiều lần cũng sẽ thêm thành phần đó vào thành phần kết hợp Compose nhiều lần Khi gọi thành phần kết hợp nhiều lần từ cùng một vị trí gọi, Compose không có bất kỳ thông tin nào để nhận dạng từng lệnh gọi đến thành phần trên Do đó, thứ tự thực thi được sử dụng cùng vị trí gọi để tách biệt các thực thể Hành vi này đôi khi cần thiết, nhưng trong một số trường hợp có thể gây ra hành vi không mong muốn
Trong ví dụ trên, Compose sử dụng thứ tự thực thi ngoài vị trí gọi để tách biệt thực thể trong thành phần Compose Nếu một movie mới được thêm vào cuối của danh sách thì Compose có thể tái sử dụng các thực thể có sẵn trong thành phần Compose vì vị trí của chúng trong danh sách chưa thay đổi, do đó, giá trị đầu vào của những thực thể trên movie vẫn còn nguyên vẹn
Hình 4 Giá trị đại diện của MoviesScreen trong thành phần Compose khi thêm một phần tử mới vào cuối danh sách Có thể tái sử dụng thành phần kết hợp
MovieOverview trong thành phần Compose MovieOverview cùng màu có nghĩa thành phần kết hợp chưa được kết hợp lại
Các giai đoạn trong Jetpack Compose
Giống như hầu hết bộ công cụ giao diện người dùng khác, ứng dụng Compose sẽ hiển thị một khung qua nhiều giai đoạn (phase) riêng biệt Nếu chúng ta xem xét hệ thống Android View, thì thấy hệ thống này có ba giai đoạn chính: đo lường (measure), bố cục (layout) và bản vẽ (drawing) Compose thì rất giống nhưng có thêm một giai đoạn quan trọng gọi là sáng tác (composition) khi bắt đầu
8.1 3 giai đoạn của một frame
Compose có ba giai đoạn chính:
1 Thành phần (Composition): Nội dung mà giao diện người dùng sẽ hiển thị Compose chạy các hàm có khả năng kết hợp và tạo nội dung mô tả giao diện người dùng
2 Bố cục (Layout): Vị trí để đặt giao diện người dùng Giai đoạn này bao gồm hai bước: đo lường và đặt vị trí Các thành phần bố cục đo lường và đặt vị trí cho chính nó và cho mọi thành phần con trong các toạ độ 2D vào mỗi nút trong cây bố cục
3 Bản vẽ (Drawing): Cách hiển thị Các thành phần trên giao diện người dùng vẽ vào Canvas, thường là màn hình thiết bị
Thứ tự của các giai đoạn này thường giống nhau, cho phép dữ liệu truyền theo một hướng từ thành phần đến bố cục đến bản vẽ để tạo một khung (còn gọi là luồng dữ liệu một chiều) BoxWithConstraints và LazyColumn và LazyRow là các trường hợp ngoại lệ đáng chú ý, trong đó thành phần của tệp con phụ thuộc vào giai đoạn bố cục của tệp mẹ
Bạn có thể yên tâm giả định rằng ba giai đoạn này xảy ra hầu như đối với mọi khung Tuy nhiên, khi xét về hiệu suất, Compose sẽ tránh lặp lại các công việc cho ra cùng một kết quả với dữ liệu đầu vào giống nhau trong tất cả các giai đoạn này Compose không chạy một hàm có thể kết hợp nếu nó có thể sử dụng lại kết quả cũ, và giao diện người dùng Compose sẽ không tạo lại bố cục hoặc vẽ lại toàn bộ cây
Trang 49 nếu không cần thiết Compose chỉ thực hiện lượng công việc tối thiểu cần thiết để cập nhật giao diện người dùng Quá trình tối ưu hoá này có thể diễn ra vì Compose theo dõi việc đọc trạng thái trong các giai đoạn khác nhau
Khi bạn đọc giá trị của trạng thái tổng quan nhanh (snapshot state) của một trong các giai đoạn được liệt kê ở trên, Compose sẽ tự động theo dõi trạng thái của hoạt động khi giá trị được đọc Tính năng theo dõi này cho phép Compose thực thi lại trình đọc khi giá trị trạng thái thay đổi và là cơ sở để quan sát trạng thái trong Compose
Trạng thái thường được tạo bởi mutableStateOf(), sau đó truy cập bằng một trong hai cách: truy cập trực tiếp thuộc tính value hoặc sử dụng một đại diện thuộc tính Kotlin Bạn có thể đọc thêm điều này trong phần Trạng thái trong các hàm có thể kết hợp Theo mục đích của hướng dẫn này, lệnh "đọc trạng thái" ("state read") tham chiếu cho một trong các phương thức truy cập tương đương đó
Trong chế độ đại diện thuộc tính (properties delegates), các hàm "getter" và
"setter" được dùng để truy cập và thiết lập value của trạng thái Các hàm getter và setter này chỉ được gọi khi bạn tham chiếu thuộc tính dưới dạng một giá trị, chứ không phải khi thuộc tính này được tạo Đó là lý do tại sao 2 cách trên lại tương đương nhau
Mỗi khối mã có thể được thực hiện lại khi trạng thái đọc thay đổi được gọi là phạm vi khởi động lại Compose theo dõi các thay đổi về giá trị của trạng thái và khởi động lại các phạm vi ở các giai đoạn khác nhau
8.3 Đọc trạng thái theo giai đoạn
Như đã đề cập ở trên, có ba giai đoạn chính trong Compose và Compose theo dõi trạng thái nào được đọc trong mỗi giai đoạn Điều này cho phép Compose chỉ thông báo cho các giai đoạn cần thực hiện công việc cho từng thành phần bị ảnh hưởng trong giao diện người dùng
Lưu ý: Vị trí mà một phiên bản trạng thái được tạo và lưu trữ có ít liên quan đến các giai đoạn Nó chỉ quan trọng với thời điểm và nơi mà trạng thái giá trị được đọc
Hãy xem qua từng giai đoạn và mô tả các sự việc xảy ra khi giá trị của Trạng thái được đọc trong mỗi giai đoạn đó
Các trạng thái đọc trong hàm @Composable hoặc khối lambda ảnh hưởng đến thành phần và có thể là các giai đoạn tiếp theo Khi giá trị trạng thái thay đổi, trình soạn thảo lại sẽ lên lịch chạy lại tất cả hàm có thể kết hợp đã đọc giá trị trạng thái đó Lưu ý rằng thời gian chạy có thể bỏ qua một vài hoặc tất cả hàm có thể kết hợp nếu dữ liệu đầu vào không thay đổi Hãy xem phần Bỏ qua nếu dữ liệu đầu vào không thay đổi để biết thêm thông tin
Tuỳ thuộc vào kết quả của composition, giao diện người dùng Compose sẽ thực hiện giai đoạn bố cục và vẽ Tính năng này có thể bỏ qua các giai đoạn trên nếu nội dung được giữ nguyên và kích thước cũng như bố cục không thay đổi
Giai đoạn bố cục bao gồm hai bước: đo lường và đặt vị trí Bước đo lường sẽ chạy lambda đo lường được chuyển đến thành phần kết hợp Layout, phương thức MeasureScope.measure của giao diện LayoutModifier, v.v Bước đặt vị trí sẽ chạy khối vị trí của hàm layout, khối lambda của Modifier.offset { … }, v.v
Tensorflow
TensorFlow Lite là một biến thể nhẹ của thư viện học máy TensorFlow, được thiết kế đặc biệt để chạy trên các thiết bị di động và nhúng Nó giúp nhà phát triển triển khai mô hình học máy trên thiết bị với kích thước nhỏ và hiệu suất cao
• Tập trung vào di động và thiết bị nhúng: TensorFlow Lite tập trung vào việc đơn giản hóa việc triển khai mô hình học máy trên thiết bị có tài nguyên hạn chế, như điện thoại di động, thiết bị IoT, và các thiết bị nhúng Nó giúp tối ưu hóa kích thước mô hình và tăng hiệu suất để đáp ứng các yêu cầu cụ thể của các nền tảng này
• Kích thước nhẹ và hiệu suất cao: TensorFlow Lite tối ưu hóa mô hình để có kích thước nhỏ, đồng thời vẫn duy trì hiệu suất cao Điều này quan trọng để đảm bảo rằng ứng dụng có thể chạy mượt mà trên các thiết bị với tài nguyên hạn chế và băng thông ít
• Hỗ trợ đa nền tảng: TensorFlow Lite không chỉ hỗ trợ Android và iOS, mà còn có thể chạy trên nhiều nền tảng khác nhau như Raspberry Pi và các thiết bị nhúng khác Điều này mang lại tính linh hoạt cao cho các ứng dụng chạy trên nhiều loại thiết bị
• Tích hợp dễ dàng: TensorFlow Lite tích hợp mạnh mẽ với TensorFlow và cung cấp các công cụ và tài liệu để chuyển đổi mô hình từ TensorFlow sang TensorFlow Lite một cách thuận lợi Điều này giúp giảm độ phức tạp của quá trình triển khai
• Hỗ trợ đa dạng mô hình: TensorFlow Lite hỗ trợ nhiều loại mô hình khác nhau bao gồm mô hình học sâu (deep learning), học máy cổ điển, và thậm chí mô hình tối ưu hóa cho ngữ cảnh đặc biệt như xử lý ngôn ngữ tự nhiên (NLP)
• Cộng đồng: Cộng đồng TensorFlow Lite rất đông đảo và nhiệt tình, cung cấp tài nguyên học tập, thảo luận, và các dự án mã nguồn mở Tài liệu chi tiết giúp nhà phát triển nhanh chóng làm quen và sử dụng TensorFlow Lite một cách hiệu quả
TensorFlow Lite đóng vai trò quan trọng trong việc đưa lực lượng của học máy và thị giác máy tính đến trực tiếp trên các thiết bị di động và nhúng Với tính
Trang 58 nhẹ, hiệu suất cao và tích hợp dễ dàng, TensorFlow Lite là một công cụ mạnh mẽ giúp mở rộng khả năng của ứng dụng trí tuệ nhân tạo trên nhiều loại thiết bị
CHƯƠNG 3: XÂY DỰNG ỨNG DỤNG
Tổng quan
1.1 Tên ứng dụng: Recipely – Just a simple recipe app
1.2 Lý do chọn ứng dụng
Hiện nay thiết bị di dộng smart phone ngày càng phổ biến Việc đưa một ứng dụng lên các kho lưu trữ tương đối dễ dàng Hơn nữa, người sử dụng cũng sẵn sàng chi trả một khoảng tiền phù hợp cho những ứng dụng mà họ cảm thấy cần thiết hơn so với trước đây
Hiện nay có có rất nhiều ứng dụng hỗ trợ nấu ăn trên nền tảng Android như Cookpad, Tasty, SideChef,… Những ứng dụng này chỉ tập trung vào một tính năng duy nhất đó chính là chia sẻ công thức món ăn
Việc các ứng dụng có thể giúp đỡ người nội trợ có thể tìm kiếm ý tưởng cho món ăn gia đình là cần thiết, tuy vậy vẫn là chưa đủ Có một tính năng mà tất cả các ứng dụng nêu trên chưa có, đó là nhận dạng nguyên liệu qua hình ảnh Đối với người nội trợ, việc có một người trợ thủ có thể giải quyết yêu cầu trên là một điều vô cùng cần thiết Vì thế tôi đã tạo ra chọn đề tài “Ứng dụng chia sẻ công thức món ăn” để giải quyết những vấn đề trên
Phần mềm tạm gọi với tên “Recipely”
1.3 Đối tượng hướng đến Đối tượng của ứng dụng hướng đến người đầu bếp ở mọi cấp độ từ nội trợ đến chuyên nghiệp
- Những người muốn tìm công thức món ăn phù hợp với nhu cầu
- Những người muốn quản lý thời gian cho công việc nấu ăn
- Những người muốn quản lý hiệu quả trong việc mua sắm hàng ngày
- Những người chưa hài lòng với ứng dụng hỗ trợ nấu ăn hiện tại
1.4 Môi trường phát triển ứng dụng
- Hệ điều hành phát triển: Microsoft Windows
- Hệ điều hành cài đặt: Android
- Cơ sở dữ liệu: Room
- Công cụ phân tích thiết kế: Figma
- Công cụ phân chia nhiệm vụ: Notion
- Công cụ xây dựng ứng dụng: Android Studio
- Ngôn ngữ sử dụng: Kotlin
- Framework: Jetpack Compose, Tensorflow Lite
- Công cụ quản lý source code: Github
- Học hỏi cách phát triển một ứng dụng trên di động qua quá trình làm đề tài
- Phát triển khả năng của phát triển ứng dụng native
- Triển khai được một sản phẩm hoàn thiện, có ích cho người dùng
- Hiểu được và áp dụng quy trình phát triển phần mềm
1.6 Quy trình thực hiện các công việc chính
Hiện nay có rất nhiều quy trình phát triển phần mềm khác nhau Tuy nhiên theo yêu cầu của đề tài, nhóm đã sử dụng mô hình thác nước cải tiến Mô hình trên bao gồm các trình tự: xác định yêu cầu, phân tích, thiết kế, cài đặt, kiểm thử, bảo trì Trong đó, kết quả của giai đoạn trước là cơ sở đầu vào của giai đoạn sau Vì vậy, nếu như có lỗi xảy ra, nhóm có thể quay lui để sửa lỗi và tối ưu phần mềm trong khi tiến độ hiện tại vẫn được duy trì
Cụ thể các trình tự phát triển phần mềm của nhóm như sau:
- Xác định yêu cầu: Khảo sát yêu cầu người dùng, lập ra bảng các yêu cầu và quy định cụ thể cho phần mềm
- Phân tích: Phân loại các yêu cầu và lập sơ đồ Use-case
- Thiết kế: Mô tả các thành phần của phần mềm một cách rõ ràng, gồm các bước:
• Thiết kế hệ thống, kiến trúc, các đối tượng
• Thiết kế cơ sở dữ liệu
• Cài đặt: Dựa theo những thiết kế và phân tích, tiến hành xây dựng ứng dụng thực tế
• Kiểm thử: Chạy thực nghiệm và đánh giá, tìm và sửa lỗi.
Phân tích, thiết kế hệ thống
2.1 Xác định và mô hình hóa các yêu cầu phần mềm
2.1.1.1 Một số yêu cầu phần mềm phải có
- Xem danh sách các món ăn, công thức của món ăn
- Tạo giỏ hàng từ nguyên liệu các món ăn
- Nhận thông báo, quản lý thông báo
- Tạo món ăn, quản lý món ăn mình đã tạo
- Quản lý cài đặt ứng dụng
- Tra cứu nguyên liệu bằng camera
2.1.1.2 Ràng buộc logic ban đầu
Phải có cơ sở dữ liệu món ăn ban đầu (tự tạo)
Tương thích với hệ điều hành Android 13 trở lên
Hệ thống phải hoạt động liên tục 24/7
Xét trong điều kiện mạng ổn định
STT Nghiệp vụ Tốc độ xử lý
2 Quản lý công thức Không quá 1s
3 Tìm kiếm công thức Không quá 1s
4 Quản lý đơn hàng Không quá 1s
5 Quản lý giỏ hàng Không quá 1s
6 Quản lý ứng dụng Không quá 1s
7 Quản lý thông báo Không quá 1s
- Hệ thống phải có cơ chế toàn vẹn dữ liệu
- Đảm bảo người dùng không có quyền chỉnh sửa thông tin của người dùng khác hay truy cập vào những dữ liệu chi tiêu không thuộc quyền sở hữu của mình
2.1.2 Mô hình hóa yêu cầu
Tên use case Mô tả
Xác thực Đăng nhập vào hệ thống và sử dụng chức năng tương ứng Quản lý công thức Người dùng có thể xem, thêm và xóa công thức
Tìm kiếm công thức Người dùng có thể tìm kiếm theo tên, theo nguyên liệu và theo hình ahr
Quản lý đơn hàng Người dùng có thể tạo đơn hàng từ giỏ hàng và thay đổi trạng thái
Quản lý giỏ hàng Người dùng có thể thêm các nguyên liệu từ món ăn vào giỏ hàng, thay đổi số lượng, xóa vật phẩm, thay đổi địa chỉ giao
Quản lý ứng dụng Người dùng có thể thay đổi các cài đặt như thông báo, ngôn ngữ Quản lý thông báo Người dùng có thể nhận và xem thông báo
Quản lý tài khoản Người dùng có thể thay đổi các thông tin của tài khoản Đặc tả use-case Đặc tả use case đăng nhập
Tên use case Đăng nhập
Tác nhân sử dụng Người dùng
Mục đích Đăng nhập vào hệ thống và sử dụng chức năng quản lý tương ứng
Tiền điều kiện Người dùng có tài khoản và mật khẩu đăng nhập vào hệ thống, ứng dụng chưa được đăng nhập
2 Bỏ qua màn hình on board và chọn đăng nhập
3 Nhập thông tin đăng nhập
4 Màn hình chinh được mở ra
Ngoại lệ Nếu thông tin đăng nhập không hợp lệ, một thông báo sẽ hiện ra Đăng nhập
Tên use case Đăng kí
Tác nhân sử dụng Người dùng
Mục đích Tạo một tài khoản và sử dụng các chức năng của ứng dụng Tiền điều kiện Khong có
2 Bỏ qua màn hình onboard và chọn tạo tài khoản
3 Nhập các thông tin cần thiết và nhấn tạo tài khoản
4 Màn hình chính được mở ra Ngoại lệ Nếu email bị trùng, một thông báo sẽ hiện ra Đăng xuất
Tên use case Đăng xuất
Tác nhân sử dụng Người dùng
Mục đích Đăng xuất tài khoản hiện tại khỏi ứng dụng
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
Diễn biến chính 1 Chọn mục quản lý tài khoản, chọn cài đặt
Ngoại lệ Không có Đặc tả use case quản lý công thức
Tên use case Xem công thức
Tác nhân sử dụng Người dùng
Mục đích Xem một công thức món ăn
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
Diễn biến chính 1 Tại màn hình chính bấm chọn một công thức bất kì
2 Màn hình công thức món ăn sẽ mở ra Ngoại lệ Không có
Tên use case Tương tác công thức
Tác nhân sử dụng Người dùng
Mục đích Lưu công thức vào mục yêu thích và tăng lượt yêu thích của công thức Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính, bấm chọn một công thức bất kì
2 Tại man hình công thức món ăn, nhấn nút trái tim
3 Công thức đã được tương tác, công thức được lưu vào mục yêu thích trong cài đặt, một thông báo được gửi đến chủ công thức
Tên use case Tạo công thức món ăn
Tác nhân sử dụng Người dùng
Mục đích Tạo công thức cho một món ăn
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
Diễn biến chính 1 Tại màn hình chính, nhấn nút chức năng, sau đó chọn
2 Màn hình tạo công thức được mở ra, nhập các thông tin cần thiết
3 Công thức mới đã được thêm vào Ngoại lệ Không có
Tên use case Xóa công thức
Tác nhân sử dụng Người dùng
Mục đích Xóa công thức của chính mình
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Chọn mục quản lý tài khoản
3 Chọn công thức của tôi
4 Trượt để xóa công thức cần xóa Ngoại lệ Không có Đặc tả use case Quản lý bộ sưu tập
Tìm kiếm công thức theo tên
Tên use case Tìm kiếm công thức theo tên
Tác nhân sử dụng Người dùng
Mục đích Người dùng tìm kiếm công thức theo tên
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
2 Nhập tên món ăn muốn tìm
3 Món ăn được tìm thấy sẽ xuất hiện Ngoại lệ Không có
Tìm kiếm theo nguyên liệu
Tên use case Tìm kiếm theo nguyên liệu
Tác nhân sử dụng Người dùng
Mục đích Người dùng muốn tìm kiếm công thức theo tên nguyên liệu Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
2 Nhấn vào biểu tượng pizza (nếu in đậm thì đang tìm kiếm theo nguyên liệu)
3 Nhập tên nguyên liệu cần tìm
4 Các món ăn chứa nguyên liệu được nhập sẽ hiển thị Ngoại lệ Không có
Tìm kiếm theo hình ảnh
Tên use case Tìm kiếm theo hình ảnh
Tác nhân sử dụng Người dùng
Mục đích Tìm kiếm công thức theo hình ảnh
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính, nhấn nút chức năng
2 Chọn tìm kiếm bằng hình ảnh
3 Màn hình camera được mở ra, phía dưới là nguyên liệu được tìm thấy
Ngoại lệ Nếu chưa chấp nhận quyền camera, một thông báo sẽ mở ra
Tên use case Gửi thông báo
Tác nhân sử dụng Người dùng
Mục đích Gửi một thông báo đến người khác
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
Diễn biến chính 1 Tại màn hình chi tiết món ăn, nhấn nút tương tác
2 Một thông báo sẽ được gửi cho người sở hữu công thức Ngoại lệ Người sở hữu sẽ không nhận được thông báo nếu đã từ chối quyền gửi thông báo ứng dụng
Tên use case Xem thông báo
Tác nhân sử dụng Người dùng
Mục đích Tìm kiếm công thức theo hình ảnh
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính, chọn quản lý thông báo
2 Màn hình thông báo được hiển thị Ngoại lệ Đặc tả use case quản lý ứng dụng
Tên use case Quản lý thông báo ứng dụng
Tác nhân sử dụng Người dùng
Mục đích Người dùng thay đổi cài đặt thông báo của ứng dụng
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính, chọn quản lý tài khoản
3 Bật hoặc tắt nút thông báo theo mong muốn Ngoại lệ
Tên use case Ngôn ngữ
Tác nhân sử dụng Người dùng
Mục đích Người dùng thay đổi ngôn ngữ của ứng dụng
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính, chọn quản lý tài khoản
4 Chọn ngôn ngữ mong muốn trong hộp thoại hiện ra Ngoại lệ
Trang 74 Đặc tả use case quản lý giỏ hàng
Tên use case Quản lý giỏ hàng
Tác nhân sử dụng Người dùng
Mục đích Người dùng thay đổi thông tin trong giỏ hàng
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
Diễn biến chính 1 Tại màn hình chính, chọn giỏ hàng
2 Thay đổi thông tin mua hàng Ngoại lệ
Tên use case Quản lý địa chỉ
Tác nhân sử dụng Người dùng
Mục đích Thay đổi địa chỉ giao của tài khoản hiện tại
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính chọn giỏ hàng
2 Chọn phần thông tin giao hàng
3 Thay đổi thông tin và lưu Ngoại lệ Đặc tả use case quản lý đơn hàng
Tên use case Tạo đơn hàng
Tác nhân sử dụng Người dùng
Mục đích Người dùng tạo đơn hàng từ giỏ hàng
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
1 Tại màn hình chính, chọn giỏ hàng
2 Nhấn nút tạo đơn hàng
3 Đơn hàng đã được tạo và hiển thị ở mục đơn hàng của tôi
Tên use case Xem đơn hàng
Tác nhân sử dụng Người dùng
Mục đích Người dùng xem một đơn hàng đã tạo
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng, đơn hàng đã tồn tại
1 Tại màn hình chính, chọn quản lý tài khoản
2 Chọn đơn hàng được hiển thị
3 Màn hình chi tiết đơn hàng được hiện ra Ngoại lệ
Tên use case Hủy đơn hàng
Tác nhân sử dụng Người dùng
Mục đích Người dùng muốn hủy đơn hàng
Tiền điều kiện Người dùng đã đăng nhập vào ứng dụng
Diễn biến chính 1 Chọn đơn hàng cần hủy
2 Nhấn nút hủy đơn hàng
3 Thông tin trạng thái đơn hàng được cập nhật, đơn hàng bị hủy Ngoại lệ
2.2.1 Kiến trúc hệ thống: Multi module architecture
2.2.1.1 Vấn đề về độ lớn của mã
Trong khi cơ sở mã không ngừng phát triển, khả năng mở rộng quy mô, khả năng đọc và chất lượng mã tổng thể thường giảm dần theo thời gian Điều này là kết quả của việc kích thước cơ sở mã tăng lên mà không có các trình bảo trì thực hiện các biện pháp chủ động để tạo ra một cấu trúc dễ bảo trì Mô-đun hoá là một phương pháp để cơ cấu cơ sở mã theo cách cải thiện khả năng bảo trì và giúp tránh các vấn đề nói trên
Mô-đun hoá là phương pháp sắp xếp cơ sở mã thành các phần được khớp nối lỏng lẻo và có khả năng tự chứa Mỗi phần là một mô-đun Mỗi mô-đun thường độc lập và phục vụ một mục đích rõ ràng Bằng cách chia nhỏ vấn đề để dễ giải quyết hơn, bạn sẽ giảm bớt được độ phức tạp của việc thiết kế và duy trì một hệ thống lớn
2.2.1.3 Tránh các lỗi phổ biến
Mức độ chi tiết của cơ sở mã là mức độ mà cơ sở mã đó được cấu tạo bởi các mô-đun Cơ sở mã càng có nhiều mô-đun nhỏ hơn thì càng chi tiết hơn Khi thiết kế cơ sở mã được mô-đun hoá, bạn nên quyết định mức độ chi tiết Để thực hiện việc này, hãy tính đến quy mô và độ phức tạp tương đối của cơ sở mã Việc triển khai quá chi tiết sẽ khiến chi phí vận hành trở thành gánh nặng, còn việc triển khai sơ sài sẽ làm giảm lợi ích của quá trình mô-đun hoá
2.2.2 Kiến trúc ứng dụng
2.2.2.1 UI Layer Vai trò của lớp giao diện người dùng (hoặc lớp bản trình bày) là hiển thị dữ liệu ứng dụng trên màn hình Bất cứ khi nào dữ liệu thay đổi, do sự tương tác của người dùng (chẳng hạn như nhấn một nút) hoặc đầu vào bên ngoài (chẳng hạn như phản hồi mạng), giao diện người dùng sẽ cập nhật để phản ánh các thay đổi đó
Lớp giao diện người dùng gồm hai nội dung:
• Các thành phần trên giao diện người dùng hiển thị dữ liệu trên màn hình Bạn tạo các phần tử này bằng cách sử dụng các hàm View (Thành phần hiển thị) hoặc Jetpack Compose
• Các chủ thể trạng thái (chẳng hạn như các lớp ViewModel) chứa dữ liệu, hiển thị thông tin đó tới giao diện người dùng và xử lý logic
2.2.2.2 Data Layer Lớp dữ liệu của ứng dụng chứa logic nghiệp vụ Logic nghiệp vụ là yếu tố tạo ra giá trị cho ứng dụng — logic này được tạo ra từ các quy tắc xác định cách ứng dụng tạo, lưu trữ và thay đổi dữ liệu
Lớp dữ liệu được tạo thành từ các kho lưu trữ, mỗi kho dữ liệu có thể chứa từ 0 đến nhiều nguồn dữ liệu Bạn nên tạo một lớp kho lưu trữ cho từng loại dữ liệu khác nhau mà bạn xử lý trong ứng dụng
Các lớp kho lưu trữ chịu trách nhiệm về:
• Hiển thị dữ liệu cho phần còn lại của ứng dụng
• Tập trung các thay đổi vào dữ liệu
• Giải quyết xung đột giữa nhiều nguồn dữ liệu
• Tóm tắt các nguồn dữ liệu từ phần còn lại của ứng dụng
Mỗi lớp nguồn dữ liệu nên có trách nhiệm làm việc với chỉ một nguồn dữ liệu duy nhất, có thể là một tệp, nguồn mạng hoặc cơ sở dữ liệu cục bộ Các lớp nguồn dữ liệu là cầu nối giữa ứng dụng và hệ thống để thao tác dữ liệu
Lớp miền là lớp không bắt buộc nằm giữa giao diện người dùng và các lớp dữ liệu
Lớp miền chịu trách nhiệm về việc tổng hợp các logic nghiệp vụ phức tạp, hoặc logic nghiệp vụ đơn giản được sử dụng lại trong nhiều ViewModel Lớp này là không bắt buộc vì không phải ứng dụng nào cũng có những yêu cầu này Bạn chỉ nên sử dụng thuộc tính này khi cần, ví dụ: để xử lý độ phức tạp hoặc ưa chuộng khả năng tái sử dụng
Các lớp này thường được gọi là trường hợp sử dụng (use case) hoặctrình tương tác (interactor) Mỗi trường hợp sử dụng phải có trách nhiệm đối với một chức năng Ví dụ: ứng dụng có thể có một lớp GetTimeZoneUseCase nếu nhiều ViewModel dựa vào múi giờ để hiển thị thông báo thích hợp trên màn hình
Thực tế ứng dụng sử dụng phần common như Domain Layer
Danh sách chi tiết các table trong dữ liệu
STT Tên quan hệ Ý nghĩa
1 Recipe Công thức món ăn
5 Contain Chi tiết công thức
6 Recent Lượt tìm kiếm gần đây
11 OrderStatus Trạng thái đơn hàng
12 OrderDetail Chi tiết đơn hàng
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã người dùng
2 Title String Tên món ăn
4 Description String Mô tả món ăn
5 Servings Int Số người phục vụ
6 Owner String FK Mã người sở hữu
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã bước làm
2 Time_in_minute Int Số phút
3 Recipe_id String FK Mã người dùng
5 Media_url String Link hình ảnh
7 Order Int Số thự tự
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
2 Recipe_id String FK Mã món ăn
3 Ingredient_id String FK Mã nguyên liệu
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã nguyên liệu
2 Name String Tên nguyên liệu
3 imageUrl String Link hình ảnh
STT Tên thuộc tính Kiểu Rảng buộc Ý nghĩa
1 _id String PK Mã lượt thích
2 Recipe_id String FK Mã công thức
3 Account_id String FK Mã tài khoản
STT Tên thuộc tính Kiểu Rảng buộc Ý nghĩa
1 _id String PK Mã lượt thích
2 Recipe_id String FK Mã công thức
3 Account_id String FK Mã tài khoản
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String FK Mã tài khoản
12 Province String Tỉnh/ Thành phố
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã giỏ hàng
2 Ingredient_id String FK Mã nguyên liệu
3 Account_id String FK Mã người dùng
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã thông báo
2 User_id String FK Mã người dùng
3 notificationType String Loại thông báo
7 ImageUrl String Link hình ảnh
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã đơn hàng
2 Account_id String FK Mã người dùng
3 Delivery_info String Thông tin giao hàng
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã trạng thái đơn hàng
4 Order_id String FK Mã đơn hàng
STT Tên thuộc tính Kiểu Ràng buộc Ý nghĩa
1 _id String PK Mã chi tiết đơn hàng
2 Order_id String FK Mã đơn hàng
3 Ingredient_id String FK Mã nguyên liệu
2.4.1 Sơ đồ liên kết màn hình
2.4.2 Danh sách các màn hình
STT Tên màn hình Mô tả
1 Giới thiệu Màn hình mở đầu
2 Đăng nhập Màn hình đăng nhập
3 Đăng kí Màn hình đăng kí
4 Quên mật khẩu Màn hình quên mật khẩu
6 Giỏ hàng Màn hình lịch nấu ăn
7 Giao hàng Màn hình thông tin địa chỉ giao hàng
8 Tạo công thức Các màn hình tạo công thức
9 Tìm kiếm bằng hình ảnh Màn hình camera
10 Tìm kiếm Màn hình tìm kiếm thực đơn
11 Thông báo Màn hình thông báo
12 Tài khoản Màn hình tài khoản người dùng
13 Công thức Màn hình chi tiết công thức
14 Cách làm Màn hình cách làm của công thức
15 Chi tiết đơn hàng Màn hình chi tiết đơn hàng
16 Cài đặt Màn hình cài đặt
17 Chỉnh sửa tài khoản Màn hình chỉnh sửa tài khoản
18 Đơn hàng của tôi Màn hình tất cả đơn hàng của tài khoản
19 Công thức của tôi Màn hình tất cả công thức của tài khoản
2.4.3.9 Tìm kiếm bằng hình ảnh
2.4.3.18 Đơn hàng của tôi
Cài đặt và thử nghiệm
Source code: AnBuiii/Recipely: Just a recipe app (github.com)
STT Chức năng Mức độ hoàn thành