Category: Android

  • Splash Screen

    Splash Screen

    Hôm nay mình quay lại với chủ đề khá quen thuộc liên quan đến Splash Screen. Như chúng ta đã biết splash screen (màn hình khởi động ứng dụng) là trải nghiệm và là thứ đầu tiên người dùng nhìn thấy đối với mỗi ứng dụng.

    Những chức năng cụ thể của nó là show lên hình ảnh của App, một kiểu nhận dạng của App đó. Nhưng có thể sâu xa hơn của việc tạo ra màn hình này là thực hiện một số việc trước khi vào giao diện chính của ứng dụng, có thể liệt kể ở đâu là lấy và đồng bộ dữ liệu ở đâu đó (có thể là server, database, … ), chuẩn bị data hiển thị ở màn hình chính, …

    Bài viết dưới đây mình xin giới thiệu qua một số cách tiếp cận và triển khai khi thực hiện việc implement một Splash screen. Và một số ưu, nhược điểm của nó. Mình có thể liệt kê 3 cách chính mà thông thường mình thấy trong thời điểm hiện tại.

    1. Tạo một Activity và 1 layout XML tương ứng của nó

    Cách này theo mình thấy là cách phổ biến nhất mà các app trước khi có Android 12 đang sử dụng. Thực hiện cách này, lập trình viên chỉ cần tạo một giao diện XML theo đúng Splash UI theo requirement và setContentView cho nó. Khi muốn kết thúc, chúng ta chỉ cần start Activity chính tương bằng Intent và nhớ finish() Splash Screen hiện tại

    2. Tạo một Activity và set them background cho chúng

    Nếu thật sự các bạn thực hiện giống ở cách một, nhiều khả năng các bạn sẽ gặp phải trường hợp khi bập bật màn hình nên bạn thường thấy màn hình trắng một lát trước khi ảnh được hiện lên là do layout của bạn chỉ được hiển thị sau khi ứng dụng đã khởi tạo xong. Vì vậy để tránh việc này thay vì bạn tạo một layout cho màn hình này thì bạn sẽ tạo theme cho riêng Splash Activity này. Background mà bạn thiết lập giống như layout của Splash Screen mà bạn muốn triển khai.

    Các bước thực hiện

    Để có thể làm splash screen bằng theme, đầu tiên bạn cần tạo một XML drawable splash_background.xml bên trong thư mục res/drawable

    image

    Bước tiếp theo ta set splash_background.xml cho hình nền của theme. Ta thêm SplashTheme cho splash activity như sau:

    image

    Sau đó ta cấu hình SplashTheme cho Activity Splash screen trong AndroidManifest.xml

    image

    Bước cuối cùng ta tạo một activity SplashActivity.java mà không cần setContentView cho nó

    image

    Bằng cách làm trên View sẽ hiển thị từ theme mà không cần khỏi tạo layout nên ứng dụng sẽ chạy nhanh hơn, và thỉnh thoảng sẽ không bị xuất hiện hiện tượng màn hình trắng. Đây là một cách hợp lý mà các bạn có thể cân nhắc nếu vẫn muốn khởi tạo một Activity riêng biệt cho Splash Screen.

    3. Sử dụng API Splash Screen Android 12

    Như tên gọi của nó, từ Android 12 ra mắt một cách triển khai để tạo ra Splash Screen mới. Trải nghiệm mới mang đến các yếu tố thiết kế tiêu chuẩn cho mọi lần khởi chạy ứng dụng, nhưng nó cũng có thể tùy chỉnh để ứng dụng của bạn có thể duy trì thương hiệu riêng của ứng dụng.

    Vậy Splash Screen hoạt động thế nào Khi một app khởi chạy thì có thể rơi vào 1 trong các giai đoạn sau:

    • Cold Start (app chạy lần đầu). Ví dụ như ứng dụng của bạn được khởi chạy lần đầu tiên kể từ khi thiết bị khởi động hoặc kể từ khi hệ thống kill app
    • Warm Start (Khởi chạy các tiến trình nhưng chưa tạo Activity). Ví dụ: Khi đưa app xuống background một thời gian dài, hệ thống tự loại bỏ ứng dụng ra khỏi bộ nhớ Stack hiện tại, khi đó các tác vụ và process cần khởi động lại.
    • Hot start Khi mới bắt đầu, tất cả những gì hệ thống làm là đưa Activity của chúng ta lên hàng đầu của Stack. Nếu tất cả các Activity của ứng dụng vẫn nằm trong bộ nhớ, thì ứng dụng có thể tránh phải lặp lại quá trình khởi tạo đối tượng, lạm phát bố cục và hiển thị. Màn Splash này sẽ không chạy khi ở giai đoạn Hot start.

    Bài tiếp theo mình sẽ giới thiệu tiếp liên quan đến các yếu tố, cơ chế và triển khai cụ thể kèm code của API Splash Screen nhé.

  • Android Notification

    Android Notification

    Xin chào mọi người. Hôm nay có dịp đọc lại phần liên quan đến notification của code cũ mình đã từng implement. Mình muốn chia sẻ một chút kiến thức liên quan đến phần này. Về bản chất việc gửi nhận của notification và các implement nó. Cụ thể hơn mình xin giới thiệu 2 Platform support việc này là Firebase và Baidu (dành riêng cho thị trường Trung Quốc, vì bển đó hầu như không support các dịch vụ Google Service)

    1. Cơ chế

    Đầu tiên là cơ chế: Bạn có thể tham khảo mô hình cơ chế của thằng Firebase Cloud Messaging (FCM) với phương thức truyền nhận image 1. Thiết bị tích hợp GCM / FCM sẽ gửi yêu cầu cung cấp Registration ID đến GCM / FCM server 2. GCM trả lời dựa trên thông tin App và trả về registration ID riêng cho từng thiết bị App. 3. Thiết bị App sẽ gửi ID lên server yêu cầu lưu thông tin của ID vừa nhận được tương ứng cho thiết bị lên Server đó 4. Mỗi khi server của chúng ta cần gửi yêu cầu push, nó sẽ gửi push message lên GCM, kèm cái ID mà thiết bị App đã gửi lên để lưu ở bước 3 5. GCM / FCM sẽ xem tin có hợp lệ hay không, và xem registration ID có tồn tại không, rồi gửi gói tin message. Và thiết bị App sẽ nhận được tin push.

    1. FCM

      1. Message Các bạn lưu ý có 2 loại chính trong việc gửi nhận với FCM. Từ những message này, lập trình viên chúng ta sẽ định dạng, nhận và handle hiển thị nó tương ứng trên Application.

        1. Notification Message: Message này được xử lý bởi Firebase SDK tích hợp trong ứng dụng. Chúng bao gồm tin nhắn, icon, tiêu đề. Các tin này có thể được gửi từ Firebase Console UI. Message kiểu này có dạng:

        <pre> { "to" : “registration ID …”, "notification" : { "body" : “Body “, "title" : "Title Message“, "icon" : “Icon Message“ } } </pre>

        1. Data Message: Message loại này sẽ cần được xử lý bởi lập trình viên. Loại Message này không thể được gửi từ Firebase Console như trên. Với message loại này, các bạn cần tự xây dựng backend riêng của mình, message có dạng <pre> { "to" : "registration ID …", "data" : { "name" : “Name message “, “Message content” : “Content”, “Icon” : "Icon"
          } } </pre>
      2. Đối tượng nhận tin

        1. Global: Notification sẽ được gửi đến tất cả các đối tượng đã đăng kí với Server (Ví dụ thông báo tin nhắn chung mà tất cả những ai đã cài app đều có thể nhận được)
        2. Topic: Tin nhắn sẽ được gửi cho các đối tượng đã đăng ký một topic nào đó (Ví dụ bài toán này là việc khi app muốn chỉ những người tại một ngôn ngữ, hay một nhóm nhất định, chỉ nhóm đó mới nhận được, hoặc để từng nhóm có thể nhận được các loại khác nhau. Trong trường hợp này Topic sẽ phát huy tác dụng của nó)
        3. Individual: Đây là việc nhận gửi tin nhắn trực tiếp đến một đối tượng của thể thông qua ID đăng kí duy nhất của nó (Ví dụ bài toán này có thể kể tới là các app chat, gửi củ thể đến từng người riêng biệt)
      3. Thực hiện

        1. Config FCM trên Firebase Các bạn có thể theo hướng dẫn https://firebase.google.com/docs/cloud-messaging/android/client. Điều lưu ý là các bạn kiểm tra kĩ file json config được thêm dưới Project App.

        2. Đăng kí Topic: Sau đây mình xin giới thiệu về việc đăng kí theo loại Topic và handle chúng. image

          image

          image

          Sau khi lấy dữ liệu, bạn có thể sử dụng đối tượng NotificationCompat.Builder để thực hiện setup hiển thị data lên UI Notification tương ứng.

    2. Baidu Notification

      Để phục vụ cho thị trường China chặn hầu hết dịch vụ Google Service. Chúng ta sử dụng Baidu Notification như là một công cụ thay thế.

      Bước 1: Tạo tài khoản Nhà phát triển mới trên nền tảng Baidu. (Chú ý cần sử dụng Số điện thoại của China để tạo tài khoản)

      Bước 2: Tải xuống Android SDK cho các dịch vụ đẩy Baidu từ link dưới đây: http://push.baidu.com/sdk/push_client_sdk_for_android

      Bước 3: Tích hợp SDK đã tải xuống ở trên vào ứng dụng của bạn giống như cách bạn làm đối với bất kỳ SDK nào có tệp * .so (Thư viện tệp đối tượng được chia sẻ). Việc chúng ta cần đưa chúng vào jniLibs folder image

      Bước 4: Sau đó, giống như trong GCM console, bạn tạo một ứng dụng mới với Id ứng dụng / Tên gói, trong Baidu bạn cũng cần làm như vậy. Đó là truy cập vào URL bên dưới (Đảm bảo bạn đã đăng nhập): http://push.baidu.com/console/app

      Trên trang bảng điều khiển này Tạo ứng dụng mới (Sử dụng plugin Google dịch trên Chrome).

      Bước 5: Sau khi ứng dụng mới được tạo, ứng dụng của bạn sẽ được cấp một API Key và Secret Key. Để nhận Thông báo đẩy, bạn cần sử dụng API Key theo hướng dẫn:

      http://push.baidu.com/doc/android/api

      Tất cả các bước phát triển được chỉ định trong liên kết trên. Chỉ cần dịch trang để làm theo cùng một.

      Bước 6: Trong GCM, bạn có thể gửi thông báo đẩy bằng bảng điều khiển GCM hoặc bạn có thể sử dụng máy chủ của riêng mình để gửi thông báo GCM tới thiết bị. Nó cũng vậy trên Baidu. Sau đây là đoạn code việc nhận và hiển thị Baidu notification các bạn có thể tham khảo. image

      image

      image

      Trong link hướng dẫn của Baidu document. Cũng có 1 project sample, các bạn cũng có thể tham khảo.

      Trên đây là bài viết của mình liên quan đến việc xử lý Notification của FCM và Baidu. Hy vọng có thể ít nhiều giúp các bạn có thể hiểu thêm và có thêm một nơi tham khảo cho việc xử lý này.

      Hẹn gặp lại mọi người ở bài viết sắp tới.

  • Android Scoped Storage

    Android Scoped Storage

    Chào mọi người,

    Hôm nọ mình có có làm task liên quan đến việc maintain một project được phát triển từ năm 2019 với API support là 29 (Android 9). Một loạt các functions phải cập nhật lại trong đó có functions liên quan đến storage. Chắc hẳn nhiều bạn cũng đoán được vấn đề này liên quan đến Scoped Storage – một tính năng mới ra mắt từ Android 10. Mình muốn chia sẻ một chút kiến thức của mình liên quan đến nó và các giải pháp xử lý khi gặp phải bài toán như của mình.

    Sau đây, là một số ý chính mình sẽ đề cập

    1. Vấn đề
    2. Scoped Storage là gì?
    3. Các tính năng chính của Scoped Storage
    4. Cách xử lý với những thay đổi

    Nào mình bắt đầu

    1. Vấn đề

      Trước Android 10, chúng ta có khái niệm về Shared Storage. Mỗi ứng dụng trong thiết bị đều có một số bộ nhớ riêng trong bộ nhớ trong và bạn có thể tìm thấy ứng dụng này trong thư mục android / data / your_package_name. Ngoài bộ nhớ trong này, phần còn lại của bộ nhớ được gọi là Shared Storage. Một phần bộ nhớ mà mọi ứng dụng có quyền lưu trữ đều có thể truy cập, bao gồm Media Collections và các tập tin khác của các ứng dụng khác nhau. Từ việc này phát sinh ra một số vấn đề:

      1. Thứ nhất, ứng dụng nào đó chỉ cần thực hiện một số thao tác nhỏ trên một phần bộ nhớ (ví dụ như chỉ lấy ảnh từ phần bộ nhớ này và tải lên và không làm gì khác). Câu hỏi ở đây là tại sao phải cũng cấp cho ứng dụng đó toàn bộ quyền truy cập vào bộ lưu trữ chung đó?

      2. Vấn đề thứ hai là khi ứng dụng có khả năng ghi rộng như vậy trên bộ lưu trữ thì các tệp do ứng dụng tạo ra bị phân tán và khi người dùng gỡ cài đặt ứng dụng thì các tệp do ứng dụng tạo ra chỉ còn trong bộ lưu trữ và không bị xóa và mất rất nhiều không gian.

    2. Scoped Storage là gì?

      Scoped Storage là một tính năng có tác dụng phân chia dung lượng lưu trữ thành các bộ sưu tập được chỉ định để giới hạn quyền truy cập vào bộ lưu trữ rộng. Hiểu một cách ngắn gọn, với Scoped Storage, mỗi ứng dụng sẽ được cung cấp thư mục riêng nhằm lưu trữ các tệp cần thiết dành cho dữ liệu người dùng và những ứng dụng khác không thể truy cập thư mục đó.

    3. Các tính năng chính của Scoped Storage

      1. Unrestricted access: Mọi ứng dụng đều có quyền truy cập không hạn chế vào bộ lưu trữ của riêng nó, tức là lưu trữ nội bộ cũng như bên ngoài. Vì vậy, với Android 10, bạn không cần cung cấp quyền lưu trữ để ghi tệp vào thư mục ứng dụng của riêng bạn trên thẻ SD.

      2. Unrestricted media: Bạn có quyền truy cập không hạn chế để đóng góp các tệp vào bộ sưu tập phương tiện và tải xuống ứng dụng của riêng bạn. Vì vậy, không cần phải xin phép nếu bạn muốn lưu bất kỳ hình ảnh, video hoặc bất kỳ tệp phương tiện nào khác trong bộ sưu tập phương tiện. Bạn có thể đọc hoặc ghi các tệp phương tiện do bạn tạo nhưng để đọc tệp phương tiện của ứng dụng khác, bạn cần phải có quyền READ_EXTERNAL_STORAGE từ người dùng. Ngoài ra, quyền WRITE_EXTERNAL_STORAGE sẽ không được chấp nhận trong bản phát hành Android tiếp theo và bạn sẽ có quyền truy cập đọc nếu bạn sử dụng WRITE_EXTERNAL_STORAGE. Bạn phải yêu cầu người dùng chỉnh sửa rõ ràng các tệp phương tiện không được đóng góp bởi ứng dụng của bạn.

      3. Media location metadata: Có quyền mới được giới thiệu trong Android 10, tức là ACCESS_MEDIA_LOCATION. Nếu bạn muốn có được vị trí của phương tiện truyền thông thì bạn phải có sự cho phép này

    4. Cách xử lý với những thay đổi

      1. Xử lý tạm thời
      • Lưu ý: Cách xử lý này chỉ nên dùng để chuyển dữ liệu (migrate data) của ứng dụng từ phiên bản cũ lên phiên bản mới dùng Scoped Storage.

      • Vẫn triển khai thiết lập targetSdkVersion 29

      • Khai báo thêm trong thẻ application của manifest.xml, bổ sung thuộc tính requestLegacyExternalStorage bằng true

        image

      Lưu ý: Nếu bạn đang sử dụng Scoped Storage, thì bạn nên di chuyển tất cả các tệp media hoặc tất cả các tệp có trong Shared Storage vào thư mục của ứng dụng. Nếu không, bạn sẽ mất quyền truy cập vào các tập tin đó.

      1. Xử lý tương thích cho Android 10 và version cao hơn

        Bạn cần sử dụng bộ nhớ dành riêng cho mỗi ứng dụng (bộ nhớ trong và bộ nhớ ngoài). Các file lưu trong đó sẽ bị xóa bỏ khi gỡ cài đặt ứng dụng. Các file được tạo ra thường được mặc định là chỉ dùng cho ứng dụng, không nên dùng cho việc chia sẻ truy cập cho các ứng dụng khác. Bạn có thể tham khảo ở link hướng dẫn

        https://developer.android.com/training/data-storage/app-specific

        Các thao tác trên file: Save, Delete, Share

        1. Delete and Update file

          Việc xóa, update dữ liệu file không thuộc quyền quản lý của một ứng dụng hoặc không do ứng dụng tạo ra bây giờ sẽ cần xin quyền xác nhận từ User. Sau khi được User xác nhận cấp quyền, khi đó ứng dụng mới có thể thực hiện xóa file theo thao tác sau:

          image

        2. Read file

          Việc đọc file sẽ phải thông qua Content Uri. Việc truy cập file bằng đường dẫn trực tiếp ở bộ nhớ chia sẻ (SDCard) sẽ không thể thực hiện, trả về SecurityException. Để truy cập file thông qua Uri, bạn có thể sử dụng cách tạo Uri từ file ID như ví dụ dưới đây:

          image

        3. Share file

          Các ứng dụng cần thực hiện public file ra bộ nhớ chia sẻ. Các phương pháp cũ sử dụng MediaScannerConnection.scanFile() không còn hoạt động được nữa. Ví dụ dưới đây là thao tác lưu một file âm thanh ra bộ nhớ chia sẻ.

          image

          image

          image

    Kết luận: Mong là chia sẻ của mình đâu đấy sẽ giúp được một số bạn hiểu rõ hơn và có thể giải quyết một số vấn đề có thể các bạn sẽ gặp phải trong quá trình làm việc. Hẹn gặp lại các bạn trong các bài viết tiếp theo.

    Source Code https://github.com/android/storage-samples/tree/main/MediaStore

  • Android Local Maven (Android Library Part 2)

    Android Local Maven (Android Library Part 2)

    Chào mọi người. Bài viết trước mình đã giới thiệu về Android Library và cách publish lên remote (cụ thể ở đây là Jitpack.io)

    Tiếp nối chuỗi bài liên quan đến Library này.

    Hôm nay mình đưa ra một tình huống. Khi bạn muốn thay đổi (add, update, fix, …) thứ gì đó trên Library. Sau khi thay đổi source xong, theo thứ tự bạn phải update version của nó -> tạo bản release trên git -> send chúng lên JitPack -> Mong đợi chúng không có lỗi gì. Tiếp đến là bên Project chính, các bạn update version của Library trên Project chính và tiếp đó check nó chạy ổn không. Các bước này sẽ tiếp tục lặp lại nếu như source mà bạn code trong Lib của bạn bị fail.

    Tình huống trên gây ra sự mất time và sự chờ đợi. Để giải quyết bài toán trên mình thấy có một giải pháp đó là Local Maven Repository.

    Tất nhiên Lib của bạn thường chứa Sample App để sử dụng tất cả các tính năng của Library, vì vậy bạn có thể kiểm tra xem nó có hoạt động hay không mà không cần toàn bộ quá trình mình liệt kê ở trên, nhưng đôi khi vẫn chưa đủ và bạn cần phải kiểm tra trên dự án mà bạn thật sự muốn triển khai Library trên đó.

    Những lợi ích mà Local Maven Repository có thể thấy được là:

    1. Đưa cho bạn 1 lựa chọn nếu bạn chỉ muốn lưu Android Library hoặc module dưới local như là một maven repository trong máy tính của bạn. Từ đó bạn có import dependency một cách trực tiếp vào Project của bạn như thể nó đã từng được publish.
    2. Đưa ra giải pháp phù hợp để tiết kiệm về mặt thời gian, tránh sự chờ đợi không cần thiết, cũng như tạo ra sự chủ động cho Developer trong quá trình phát triển.

    Nào chúng ta cùng đi vào các bước để triển khai.

    1. Đầu tiên như thiết lập 2 project mà chúng ta đã setup trước đó: CalculatedApp (Main Project) and CalculatedLib (Lib)

    2. Library

      1. Trong file build.gradle, thêm plugin

      image

      1. Định nghĩa artifactId và groupId

      image

      1. Add Config

        1. Nếu Lib của bạn là single module, hoặc là có nhiều modules nhưng các modules không có sự phụ thuộc lẫn nhau.

        image

        1. Nếu Project của bạn có nhiều module là các thư viện độc lập và một module là là tập hợp của các lib đó.

        image

        1. Tip: Nếu project của bạn chứa nhiều modules. Để config chúng, chúng ta phải tạo cho mỗi một Lib một config giống như trên. Để đơn giản hơn chúng ta có thể tạo 1 file publish_local.gradle file. Trong file này mình cài đặt trong một config chung cho các Lib.

        image

        image

        Trong mỗi lib mình chỉ cần set-up groupId và artifactId tương ứng

        image

        Đặc biệt là add link dẫn đến file config tổng

        image

        1. Publish To Maven Local

        Tất cả những gì cần lúc này là run task: publishToMavenLocal

        image

        Hoặc chạy lệnh Terminal:

        <pre> ./gradlew clean ./gradlew build ./gradlew publishToMavenLocal
        </pre>

        image

        Cụ thể ở đây là 2 file:

        1. File .pom chứa thông tin của Lib (dạng XML)

        2. File .aar là file Lib được build ra

        3. Cuối cùng là Add dependency và config vào Project chính sử dụng thư viện của bạn

        image

        • Enable mavenLocal() repository vào file Build Systems
        • Chú ý là add mavenLocal() vào đầu danh sách. Cần lưu ý rằng việc có mavenLocal ở đầu danh sách sẽ giúp bạn luôn chọn các thư viện có sẵn trong thư mục ~/ .m2 / repository / trước tiên

        Tiếp Theo, Add dependency tương ứng vào Project chính và sử dụng functions Library trong source code chính.

        image

        Vâng. Đó là chia sẻ nhỏ của mình liên quan đến Library, publish remote vs local của chúng.

        Các bạn có thể tham khảo source code mình để bên dưới.

        Mong rằng bài viết của mình đâu đấy sẽ giúp các bạn trong cộng đồng Android GST mình. Hẹn gặp lại trong bài viết sắp tới.

        Source code:

        Library

        Main Project

  • Android Publish Library (Android Library Part 1)

    Android Publish Library (Android Library Part 1)

    Chào các bạn, là một lập trình viên Android chắc hẳn bạn đã từng sử dụng qua Library (Thư viện) trong Android. Nó có thể là một thư viện mở được chia sẻ trên internet, hoặc một thư viện ra chính bạn tạo ra.

    Hôm nay mình sẽ chia sẻ kiến thức nhỏ mà mình biết được liên quan đến việc publish thư viện Android dưới lên Remote (Online) và Local. Với những khái niệm như Publish Library, Local Maven,….

    I. Khái niệm

    Đầu tiên, Android Library là gì?

    • Android Library có cấu trúc giống như Module ứng dụng Android.
    • Android Library có thể bao gồm mọi thứ cần thiết để xây dựng một ứng dụng xây dựng: Source code, Resource file và một Android Manifest.
    • Thay vì, tệp App build thành APK file, thì Library build thành AAR file(Android Archive) và sử dụng như một phần phụ thuộc của module ứng dụng Android.

    II. Sử dụng và Lợi ích

    Vậy, khi nào sử dụng Android Library? Khi sử dụng nhiều ứng dụng mà bạn có một số thành phần giống nhau, chẳng hạn như activity, service hoặc UI layout người dùng.

    Một trong những lợi ích của việc sử dụng Library mà chúng ta có thể kể đến như là:

    • Tăng tốc developemnt time.
    • Tái sử dụng lại source code được phân chia chức năng cụ thể.

    III. Truy cập

    • Tạo và keep Library trong Project
    • Tạo and Publish it to global to truy cập chúng một cách remote hoặc share chúng tới các project khác. Một số cách thông dụng để publish Library lên remote. Một số nơi có thể kể tới
      1. Jitpack.io
      2. Maven Publish
      3. Jfrog Bintray Artifactory (Refer)

    IV. Tạo và Publish Library

    1. Tạo Library: Để tạp library module bạn làm theo cách sau: Vào File > New > New Module. Trong cửa sổ Create New Module xuất hiện, nhấp vào Thư viện Android, sau đó nhấp vào Tiếp theo. Ở màn hình tạo Module mới xuất hiện, bạn chọn Thư viện Android, sau đó nhấn Next.

    image

    Sau khi hoàn tất các thao tác tạo Lib và implement functions cần thiết

    image

    Bạn cũng có thể tạo 1 project thuần tuý là thư viện mà không có App Module kèm theo bằng cách đổi plugin trong Gradle file thành

    plugins {

    id 'com.android.library'
    

    }

    image

    Vì bài này tập trung vào các publish Library nên mình chỉ làm một ví dụ đơn giản về nội dung của Library này về chức năng tính toán.

    1. Push Project lên Github

    image

    -> git init

    -> git add

    -> git commit -m “calculated Library 1.0.0”

    -> git remote add origin “Paste Your-Repository-Address”

    -> git remote -v

    -> git push origin master

    1. Tạo và publish version release trên Github

    image

    1. Thêm repository vào Jitpack.io và click "Get it" version mong muốn.

    image

    1. Apply vào Main Project theo hướng dẫn

    image

    Cụ thể trong Project:

    image

    image

    Sau khi Sync và Download xong thư viện. Giờ chúng ta có thể sử dụng nó trong code.

    Bài tiếp theo. Mình sẽ tiếp tục chia sẻ thêm về Local Maven và về tình huống cụ thể sử dụng nó.

    Cảm ơn mọi người.

    Source code:

    Library

    Main Project

  • Android Publish Library (Android Library Part 1)

    Chào các bạn, là một lập trình viên Android chắc hẳn bạn đã từng sử dụng qua Library (Thư viện) trong Android. Nó có thể là một thư viện mở được chia sẻ trên internet, hoặc một thư viện ra chính bạn tạo ra.

    Hôm nay mình sẽ chia sẻ kiến thức nhỏ mà mình biết được liên quan đến việc publish thư viện Android dưới lên Remote (Online) và Local. Với những khái niệm như Publish Library, Local Maven,….

    I. Khái niệm

    Đầu tiên, Android Library là gì?

    • Android Library có cấu trúc giống như Module ứng dụng Android.
    • Android Library có thể bao gồm mọi thứ cần thiết để xây dựng một ứng dụng xây dựng: Source code, Resource file và một Android Manifest.
    • Thay vì, tệp App build thành APK file, thì Library build thành AAR file(Android Archive) và sử dụng như một phần phụ thuộc của module ứng dụng Android.

    II. Sử dụng và Lợi ích

    Vậy, khi nào sử dụng Android Library? Khi sử dụng nhiều ứng dụng mà bạn có một số thành phần giống nhau, chẳng hạn như activity, service hoặc UI layout người dùng.

    Một trong những lợi ích của việc sử dụng Library mà chúng ta có thể kể đến như là:

    • Tăng tốc developemnt time.
    • Tái sử dụng lại source code được phân chia chức năng cụ thể.

    III. Truy cập

    • Tạo và keep Library trong Project
    • Tạo and Publish it to global to truy cập chúng một cách remote hoặc share chúng tới các project khác. Một số cách thông dụng để publish Library lên remote. Một số nơi có thể kể tới
      1. Jitpack.io
      2. Maven Publish
      3. Jfrog Bintray Artifactory (Refer)

    IV. Tạo và Publish Library

    1. Tạo Library: Để tạp library module bạn làm theo cách sau: Vào File > New > New Module. Trong cửa sổ Create New Module xuất hiện, nhấp vào Thư viện Android, sau đó nhấp vào Tiếp theo. Ở màn hình tạo Module mới xuất hiện, bạn chọn Thư viện Android, sau đó nhấn Next.

    image

    Sau khi hoàn tất các thao tác tạo Lib và implement functions cần thiết

    image

    Bạn cũng có thể tạo 1 project thuần tuý là thư viện mà không có App Module kèm theo bằng cách đổi plugin trong Gradle file thành

    <pre> plugins { id ‘com.android.library’ } </pre>

    image

    Vì bài này tập trung vào các publish Library nên mình chỉ làm một ví dụ đơn giản về nội dung của Library này về chức năng tính toán.

    1. Push Project lên Github

    image

    <pre> -> git init -> git add -> git commit -m “calculated Library 1.0.0”

    -> git remote add origin “Paste Your-Repository-Address” -> git remote -v -> git push origin master </pre>

    1. Tạo và publish version release trên Github

    image

    1. Thêm repository vào Jitpack.io và click "Get it" version mong muốn.

    image

    1. Apply vào Main Project theo hướng dẫn

    image

    Cụ thể trong Project:

    image

    image

    Sau khi Sync và Download xong thư viện. Giờ chúng ta có thể sử dụng nó trong code.

    Bài tiếp theo. Mình sẽ tiếp tục chia sẻ thêm về Local Maven và về tình huống cụ thể sử dụng nó.

    Cảm ơn mọi người.

    Source code:

    Library

    Main Project

  • Localization: Tối ưu hoá quá trình khi làm ứng hỗ trợ đa ngôn ngữ

    Localization: Tối ưu hoá quá trình khi làm ứng hỗ trợ đa ngôn ngữ

    Như mọi người đã biết, hầu hết các ứng dụng ngày nay đều hỗ trợ tính năng đa ngôn ngữ, nhằm mục đích tiếp cận được nhiều người dùng hơn, cho mọi người sử dụng ứng dụng dễ dàng hơn. Tuy nhiên hiện nay hầu hết các dự án đều làm một cách tự phát, chưa có một quy trình chuẩn. Điều này làm cho quá trình phát triển sinh ra nhiều bug UI sai Message, sai chính tả, nó làm tốn thời gian không đáng có của các bên.

    Trong quá trình làm việc ở các dự án, khi tài liệu được cập nhật đồng nghĩa với việc các Dev phải quay lại kiểm tra các thay đổi message, rồi bắt đầu loay hoay tìm kiếm trong source code để thực hiện thay đổi.

    Hay mỗi khi code một màn hình mới việc phải định nghĩa một đống key, định nghĩa enum/constant và copy/paste nó sang các file localization thật nhàm chán và tiềm tàng nhiều rủi ro về lỗi sai chính tả, copy nhầm. Trước đây, có một member trong dự án của mình chỉ vì copy sai text làm thừa một dấu “;” làm lỗi cả file đa ngôn ngữ, việc này làm cho toàn bộ text trên ứng dụng khi release cho bị sai. Do file đa ngôn ngữ là string nên những lỗi sai chính tả như này mất rất nhiều thời gian để điều tra lỗi.

    Sau nhiều năm làm việc với các dự án khác nhau, cùng các anh em, cộng sự hoàn thành các ứng dụng to nhỏ khác nhau. Mình đã đúc kết được một quy trình giúp cải thiện năng suất làm việc của mọi người, giúp giảm các bug UI về sai message, sai chính tả, giảm thời gian thực hiện tính năng đa ngôn ngữ, giúp quá trình bảo trì, thay đổi message trở nên dễ dàng hơn.

    Quy trình

    Brainstoming

    Trước tiên, để đạt được hiệu quả Leader của các bên gồm BA, Mobile, Web … ngồi lại với nhau để thống nhất về cách làm việc theo quy trình trên. Sau đó phổ biến lại cho member và đảm bảo việc member thực hiện đúng quy trình làm việc.

    Tạo file excel chung lưu các message cần làm đa ngôn ngữ

    File message template các bạn tải ở đây nhé:

    Trong file này mình có định nghĩa sẵn các trường cần nhập và có sẵn công thức để gen ra code của swift và android. Nếu dự án của các bạn sử dụng ngôn ngữ khác thì có thể chỉnh lại công thức cho phù hợp với dự án của mình.

    Nhập nội dung

    Thêm/sửa message

    Screen/Feature: Điền vào tiên màn hình hoặc tính năng.

    MessageKey: Điền vào tên item trên màn hình, nếu SRS có mô tả thì ưu tiên sử dụng key trong SRS, nếu không hãy đặt tên sao cho đúng ý nghĩa của item.

    Tiếng việt: Text được hiển thị khi ngôn ngữ là Tiếng Việt

    Tiếng anh: Text được hiển thị khi ngôn ngữ là Tiếng Anh

    Note:
    TH1. Tài liệu đã định nghĩa sẵn các message cho các ngôn ngữ: thì BA có thể cử 1 bạn ra điền hết vào file này trước khi bên dev thực hiện code. Trường hợp này thì việc làm đa ngôn ngữ khá nhàn, mình sẽ giải thích chi tiết ở các phần phía dưới.
    TH2: Tài liệu chưa định nghĩa message cho các ngôn ngữ(các dự án thường rơi vào trường hợp này): Khi này khi các Dev thực hiện code sẽ tự insert thêm message vào file trên teams, để file tự tạo ra code tương ứng với các ngôn ngữ.

    Công thức tạo code tự động

    Công thức tạo code từ excel

    Các Developers sẽ không phải làm các thao tác lặp đi lặp lại nhàm chán. Ngoài ra nó giúp giảm lỗi đánh máy, lỗi copy paste, lỗi sai chính tả và giúp việc làm đa ngôn ngữ nhanh hơn. Trên file excel mình có tạo ra công thức để tự tạo ra code của swift/android. Khi bạn điền thông tin vào sheet MessageList thì code sẽ được gen tự động, việc của mọi người là chỉ cần copy code vào project và sử dụng.

    Đưa ra các luật lệ

    Tạo ra các luật yêu cầu member phải tuân thủ như sau:

    1. Đặt tên màn hình theo quy định của dự án, cách đặt tên cần thống nhất giữa tài liệu SRS và các class trong code của Dev.
    2. Dev của các bên(mobile, web) và BA khi thực hiện thêm mới, hay chỉnh sửa sẽ thực hiện trực tiếp trên files Localize chung của dự án, khi chỉnh sửa cần tìm đúng tên màn hình, message để sửa, nếu thêm mới thì thêm xuống cuối cùng của của danh sách list message của màn hình đó.
    3. Để tránh việc conflict hoặc giẫm chân nhau khi các bên chạy song song, thì mỗi màn hình những member liên quan tới màn hình đó sẽ cử ra một bạn điển vào file message trước.

    Cách thực hiện code

    TH1: đối với dự án không cho sử dụng thư viện mã nguồn mở

    Khi thực hiện chúng ta chỉ cần vào file excel và copy code vào project của mình rồi sử dụng bình thường.

    TH2: Dự án cho phép sử dụng thư viện mã nguồn mở

    Chúng ta sẽ sử dụng thư viện để tự tạo ra enum nhanh gọn lẹ hơn, ví dụ bên iOS Swift thì chúng ta có thể sử dụng Swiftgen để thực hiện. Link hướng dẫn sử dụng Swiftgen mình cũng có viết môt bài rồi các bạn có thể tham khảo link dưới đây:

    Ưu điểm

    • Giảm thời gian làm việc, tăng năng suất làm việc của team
    • Giảm khả năng bị lỗi UI/Message hay các lỗi về copy/paste khi thực hiện
    • Giảm thời gian bảo trì ứng dụng khi thay đổi message, khi có thay đổi BA sẽ update file message list và báo lại cho Devs, lúc này Dev không cần phải tìm xem thay đổi ở đâu, mà chỉ cần copy code từ file message list là xong.
    • Giảm thời gian implement khi có yêu cầu hỗ trợ thêm ngôn ngữ khác. Vì khi này chúng ta chỉ cần thêm 1 cột nữa ở file excel rồi copy code sang project là xong.
    • Giúp tối ưu được effort khi các team không start cùng một thời điểm. Vì team start sau sẽ sử dụng lại được phần đã làm của team start trước.

    Nhược điểm

    • Cần quản lí, phân chia công việc tốt, member có khả năng làm việc nhóm.
    • Thường chỉ hiệu quả cao đối với các dự án mới

    Tổng kết

    Phía trên là nội dung chia sẻ về cách tối ưu khi làm ứng dụng hỗ trợ đa ngôn ngữ, mình hi vọng bài viết có thể giúp mọi người phần nào trong quá trình thực hiện các ứng dụng có hỗ trợ đa ngôn ngữ. Cảm ơn mọi người đã dành thời gian đọc bài viết này.

  • Flow trong Coroutines Andorid

    Flow trong Coroutines Andorid

    Flow Trong Coroutines

    Trong coroutines , Flow là một kiểu có thể trả về nhiều giá trị một cách tuần tự trái ngược với việc dừng lại các hàm chỉ trả về 1 giá trị duy nhất để dễ hiểu thì khi bạn nhận giá trị trả về từ database là một list thì bạn sẽ phải tốn thời gian đợi database select hết giá trị rồi truyền vào list còn trong flow thì lấy được giá trị nào bạn có thể collect luôn được giá trị đó, rất tiện phải không nào , giả sử cái database của bạn có hàng chục triệu bản ghi xem , sài List là vã mồ hôi ngay =))

    Về cơ bản thì mọi anh em sẽ thấy Flow với Sequence khá giống nhau như kiểu cùng cha khác mẹ với ,thằng Flow thì nó xử lý bất đồng bộ còn Sequence là xử lý đồng bộ. Nếu bạn không hiểu thì cứ hiểu rằng Flow nó chạy song song với MainThread còn thằng Sequence sẽ Block MainThread lại để nó chạy xong đã .

    Code minh họa nhé

    Đây là sequence

    fun foo(): Sequence<Int> = sequence { 
        for (i in 1..3) {
            Thread.sleep(1000)
            yield(i) 
        }
    }
    
    fun main() = runBlocking {
        
        launch {
            println(Thread.currentThread().name)
            for (k in 1..3) {
                delay(1000)
                println("I'm blocked $k")
            }
        }
        val time = measureTimeMillis {
            foo().forEach { value -> println(value) }
        }
        println("$time s")
    }
    

    Output

    1
    2
    3
    3000 s
    main
    I'm blocked 1
    I'm blocked 2
    I'm blocked 3
    

    Anh em có thể thấy khi thằng Foo() chạy sẽ Lock MainThread lại không cho dòng For kia chạy mà đợi nó chạy xong thì mới unlock MainThread còn Flow thì sao nhìn ví dụ nhé

    fun foo(): Flow<Int> = flow {
        for (i in 1..3) {
            delay(1000)
            emit(i) 
        }
    }
    
    fun main() = runBlocking {
        launch {
            println(Thread.currentThread().name)
            for (k in 1..3) {
                delay(900)
                println("Running after Flow collect $k")
            }
        }
      
        val time = measureTimeMillis {
            foo().collect { value -> println(value) }
        }
        println("$time s")
    }
    

    Output

    main
    1
    Running after Flow collect 1
    2
    Running after Flow collect 2
    3
    Running after Flow collect 3
    3000 s
    

    Rõ ràng nó sẽ không Block MainThread lại mà cả hàm Foo và For đều chạy song song với nhau.

    1.Thêm Flow vào Andorid

    Vì nó nằm trong coroutines nên anh em import coroutines vào là xong, dễ mà vào build.gradle mà implementation thôi

    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.3"
    

    2.Flow là các Cold Stream

    Trong RxJava, mỗi Observables đại diện cho một luồng dữ liệu, và phần thân của nó không được thực thi cho đến khi nó được đăng ký (subcribed) bởi một người đăng ký (subcriber) và sẽ nhận được dữ liệu khi nguồn phát ra dữ liệu.

    Flow hoạt động tương tự như vậy, nó cũng không nhận được dữ liệu cho đến khi collect dữ liệu.

    runBlocking {
        coroutinesFlow.foo()
        println("Delay 2s")
        delay(2000)
        coroutinesFlow.foo().collect {
           println(it)
        }
    }
    
    fun foo(): Flow<Int> = flow {
        println("Flow started")
        emit(1)
        delay(500)
        emit(2)
    }
    
    Delay 2s
    Flow started
    1
    2
    

    3.Flow cancellation

    Flow tuân thủ việc các nguyên tắc cancellation chung của coroutines. Việc collect của flow chỉ có thể bị hủy khi và chỉ khi flow đang bị suspend (chẳng hạn như gặp hàm delay) và ngược lại flow không thể bị hủy.

    Khi chúng ta sẽ sử dụng scope launch{} sẽ trả về 1 Job, từ Job này chúng ta có thể cancel scope đó, và nếu chúng ta đặt flow bên scope và khi cancel thì flow cũng bị cancel theo.

    fun cancelFlow() = runBlocking {
       val job = launch(Dispatchers.Default) {
           flow {
               (1..9).forEach {
                   delay(1000)
                   emit(it)
               }
           }.collect {
               println("value $it")
           }
       }
       delay(5000)
       job.cancel()
    }
    
    value 1
    value 2
    value 3
    value 4
    

    Kết quả chính xác sẽ là in ra từ value 1 -> value 9, nhưng do chúng ta delay 5s rồi cancel job nên flow cũng bị cancel từ đó luôn.

    Từ ví dụ này chúng ta biết được các scope lồng nhau thì khi scope cha bị cancel thì các scope con cũng bị cancel theo

    fun cancelScope() = runBlocking {
       val startTime = System.currentTimeMillis()
       val job = launch(Dispatchers.Default) {
           var nextPrintTime = startTime
           var i = 0
           while (i < 5) {
               if (System.currentTimeMillis() >= nextPrintTime) {
                   println("job: I'm sleeping ${i++} ...")
                   nextPrintTime += 1000L
               }
           }
       }
       delay(1300L)
       println("main: I'm tired of waiting!")
       job.cancel()
       println("main: Now I can quit.")
    }
    
    job: I'm sleeping 0 ...
    job: I'm sleeping 1 ...
    main: I'm tired of waiting!
    main: Now I can quit.
    job: I'm sleeping 2 ...
    job: I'm sleeping 3 ...
    job: I'm sleeping 4 ...
    

    Mặc dù đã gọi cancel để huỷ coroutine rồi nhưng vòng while kia vẫn chạy bất chấp, đó là bởi vì khi gọi cancel thì nó sẽ set lại 1 property là isActive từ true sang false, và mọi hàm support từ coroutine như delay, emit đều check xem isActive còn bằng true hay không? Nếu bằng false thì nó sẽ huỷ bỏ tiến trình đó luôn.

    Vậy sửa đoạn code trên ta chỉ cần thay điều kiện while (i < 5) sang while (isActive) là được.

    Qua ví dụ này chúng ta biết được để xem 1 coroutine bị cancel hay chưa, chỉ cần check property isActive

    4.Cách tạo Flow

    flowOf () – Nó được sử dụng để tạo luồng từ một tập giá trị nhất định.

    flowOf(4, 2, 5, 1, 7).onEach { delay(500) }
    

    asFlow () – Đây là một hàm mở rộng giúp chuyển đổi kiểu thành các luồng.

    (1..5).asFlow().onEach{ delay(500)}
    

    flow {} – Đây là một scope trình tạo để xây dựng các luồng tùy ý như các ví dụ trên.

    channelFlow {} (cold stream) – Cách này tạo ra luồng dữ liệu bằng cách sử dụng hàm send, giống như onNext trong RxJava Ví dụ:

    channelFlow {
        send(1)
    }.flowOn(Dispatchers.Default)
    

    Kết luận

    Anh em có thể thấy Flow rất mạnh trong việc xử lý bất đồng bộ cũng không kém gì Rxjava cả , các bạn có thể thấy Flow thường hay được sử dụng với LiveData trong Android Jetpack. Ở bài viết này còn rất nhiều thứ mình muốn đề cập đến như các toán tử , context , exception trong flow nhưng mình xin đề cập ở phần sau.

    Tài liệu tham khảo

    1. Kotlin Coroutines Flow

    2. Developer Kotlin Flow

    3. LiveData with Flow

  • Android – Room Persistence Library

    Android – Room Persistence Library

    1. Room database là gì?

    Room là một Persistence Library được google giới thiệu trong sự kiện google I/O mới đây, nó là một abstract layer cung cấp cách thức truy câp thao tác với dữ liệu trong cơ sở dữ liệu SQLite. Các thành phần: Database, DAO (Data Access Object) và Entity.

    Các ứng dụng sử dụng một lượng lớn dữ liệu có cấu trúc có thể hưởng lợi lớn từ việc lưu lại dữ liệu trên local thông qua Room Database. Trường hợp thường gặp nhất là chỉ cache những dữ liệu có liên quan. Nếu làm vậy thì khi thiết bị không có kết nối internet thì user vẫn có thể truy cập data đấy khi đang offline. Mọi dữ liệu được phát sinh hay thay đổi do user sau đó sẽ được đồng bộ với server khi họ online trở lại.

    Room Database đơn giản hóa việc mã hóa và giảm thiểu các hoạt động liên quan đến cơ sở dữ liệu. Nếu các bạn đã chán ngán với việc phải khai báo các câu lệnh rất dài mới có thể xây dựng được database thì hãy sử dụng Room ngay nhé!

    2. Đặc điểm của Room database

    • Trong trường hợp SQLite, Không có xác minh thời gian biên dịch của các truy vấn SQLite thô. Nhưng trong Room có ​​xác thực SQL tại thời điểm biên dịch.
    • Khi lược đồ của bạn thay đổi, bạn cần cập nhật các truy vấn SQL bị ảnh hưởng theo cách thủ công. Room giải quyết vấn đề này.
    • Bạn cần sử dụng nhiều mã soạn sẵn để chuyển đổi giữa các truy vấn SQL và các Data objects. Tuy nhiên, Room ánh xạ các đối tượng cơ sở dữ liệu của chúng tôi tới Data objects mà không cần mã soạn sẵn.
    • Room được xây dựng để hoạt động với LiveData để quan sát dữ liệu, trong khi SQLite thì không.

    3. Cách import Room

    Mở build.gradle (app) và thêm dòng lệnh sau:

    dependencies {
        def room_version = "2.4.0"
    
        implementation "androidx.room:room-runtime:$room_version"
        annotationProcessor "androidx.room:room-compiler:$room_version"
    
        // optional - RxJava2 support for Room
        implementation "androidx.room:room-rxjava2:$room_version"
    
        // optional - RxJava3 support for Room
        implementation "androidx.room:room-rxjava3:$room_version"
    
        // optional - Guava support for Room, including Optional and ListenableFuture
        implementation "androidx.room:room-guava:$room_version"
    
        // optional - Test helpers
        testImplementation "androidx.room:room-testing:$room_version"
    
        // optional - Paging 3 Integration
        implementation "androidx.room:room-paging:2.4.0"
    }

    4. Các thành phần chính trong Room

    Có 3 thành phần chính trong Room:

    • Database

    Có thể dùng componenet này để tạo database holder. Annotation sẽ cung cấp danh sách các thực thể và nội dung class sẽ định nghĩa danh sách các DAO (đối tượng truy cập CSDL) của CSDL. Nó cũng là điểm truy cập chính cho các kết nối phía dưới.

    @Database(
        entities = [
            School::class,
            Student::class
        ],
        version = 2,
        exportSchema = false
    )
    abstract class SchoolDataBase : RoomDatabase() {
    
        abstract fun schoolDAO(): ISchoolDAO
    
        abstract fun studentDAO(): IStudentDAO
    
        companion object {
            @Volatile
            private var INSTANCE: SchoolDataBase? = null
    
            fun getInstance(context: Context): SchoolDataBase = INSTANCE ?: synchronized(this) {
                return Room.databaseBuilder(
                    context.applicationContext,
                    SchoolDataBase::class.java,
                    Constant.SCHOOL_DATABASE
                ).build().also {
                    INSTANCE = it
                }
            }
        }
    }
    • Entity

    Component này đại diện cho một class chứa một row của database. Với mỗi một entity thì một database table sẽ được tạo để giữ các items tương ứng. Nên tham chiếu lớp enity thông qua mảng entities trong class Database.

    Mỗi Object phải xác định ít nhất 1 trường làm khóa chính. Ngay cả khi chỉ có 1 trường, bạn vẫn cần chú thích trường này bằng anotation @PrimaryKey. Ngoài ra, nếu bạn muốn Room gán ID tự động cho các thực thể, bạn có thể đặt thuộc tính autoGenerate của @PrimaryKey.(Trường hợp thuộc tính là int, long).

    @Entity(tableName = SCHOOL_TABLE)
    data class School(
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = SCHOOL_ID)
        val schoolId: Int = 0,
        @ColumnInfo(name = SCHOOL_NAME)
        val schoolName: String,
        @ColumnInfo(name = SCHOOL_ADDRESS)
        val schoolAddress: String
    )

    Trường hợp các bạn muốn đánh index cho một số trường trong database để tăng tốc độ truy vấn các bạn có thể sử dụng như sau:

    @Entity(indices = {@Index(value = {"first_name", "last_name"}})
    

    Định nghĩa foreignKeys. Ví dụ bạn có đối tượng khác là Student và bạn có thể định nghĩa relationship tới đối tượng School thông qua @ForeignKey annotation như sau:

    @Entity(
        tableName = STUDENT_TABLE,
        foreignKeys = [ForeignKey(
            entity = School::class,
            parentColumns = [SCHOOL_ID],
            childColumns = [SCHOOL_ID],
            onDelete = ForeignKey.CASCADE
    
        )]
    )
    data class Student(
        @PrimaryKey(autoGenerate = true)
        @ColumnInfo(name = STUDENT_ID)
        val studentId: Int = 0,
        @ColumnInfo(name = SCHOOL_ID)
        val schoolId: Int,
        @ColumnInfo(name = STUDENT_NAME)
        val studentName: String,
        @ColumnInfo(name = STUDENT_GRADE)
        val studentGrade: Float
    )
    • DAO (Data Access Object)

    Đây là component đại diện cho lớp hoặc interface như một đối tượng truy cập dữ liệu (DAO). DAO là thành phần chính của Room là chịu trách nhiệm trong việc định nghĩa các phương thức truy cập CSDL.

    @Dao
    interface ISchoolDAO {
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        suspend fun insertSchool(school: School)
    
        @Delete
        suspend fun deleteSchool(school: School)
    
    
        @Update
        suspend fun updateSchool(school: School)    
    
        @Transaction
        @Query("SELECT * FROM $SCHOOL_TABLE")
        fun getAllSchool(): Flow<List<SchoolAndStudent>>
    }

    5. Kết luận

    Trên đây là phần giới thiệu cơ bản về Room database của mình.

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

    Tham khảo: https://developer.android.com/training/data-storage/room#groovy

  • Dependence Injection với Dagger trong Android

    Dependence Injection với Dagger trong Android

    Giới thiệu

    bài trước mình đã giới thiệu về việc tiêm phụ thuộc thủ công trong android. Việc chèn phụ thuộc thủ công hoặt service locators trong ứng dụng Android có thể có vấn đề tùy thuộc vào quy mô dự án của bạn. Bạn có thể giới hạn sự phức táp dự án của bạn khi nó tăng lên bằng cách sử dụng Dagger để quản lý các sự phụ thuộc.

    Dagger tự động tạo code mà bắt chước code bạn sẽ viết thủ công. Bời vì code được sinh tự động tại compile time. Dagger hilt được giới thiệu trong android jetpack nó giúp đơn giản hóa code DI hơn so với Dagger. Nhưng do nó dựa trên Dagger nên trong bài này mình sẽ giới thiệu sơ lược về Dagger.

    Lợi ích khi sử dụng Dagger

    Dagger giúp bạn từ viết mã tẻ nhạc và mã soạn sẵn dễ tạo ra lỗi bằng cách:

    • Sinh AppContainer code(application graph) mà bạn đã triển khai thủ công trong phân DI.
    • Tạo factories cho các class có sẵn trong application graph.
    • Quyết định xem có nên sử dụng lại một phụ thuộc hoặc tạo mới một instance mới thông qua việc sử dụng các scopes

    Dagger tư động làm tất cả điều này tại thời giơn build miễn là bạn khai báo các sự phụ thuộc của một class và chỉ định cách đáp ứng chúng bằng cách sử dụng các annotation. Dagger sinh ra code tương tự những gì bạn viết thủ công. Dagger tạo một graph của các đối tượng, cái mà nó có thể tham chiếu để tìm kiếm cách cung cấp một instance của một class. đối với mọi lớp trong graph Dagger tạo một factory-type class mà nó sử dụng trong nội bộ để lấy các instance của loại đó.

    Tại thời điểm xây dựng, Daggeer xem qua mã của bạn và:

    • Builds và Validates các biểu đồ phụ thuộc, đảm bảo rằng:

      • Mọi đối trượng phụ thuộc có thể được thỏa mãn, vì vậy không có runtime exception
      • Không có chu trình phụ thuộc nào tồn tại, vì vậy không có vòng lặp vô hạn.
    • Sinh các class mà được sử dụng tại runtime để tạo các đối tượng thực tế và các phụ thuộc của chúng.

    Một usecase đơn giản trong Dagger: Generating một factory

    Tạo một factory đơn giản cho class UserRepository hiển thị trong sơ đồ sau:

    Screenshot from 2021-09-30 23-41-28.png

    Định nghĩa UserRepository như sau:

    class UserRepository(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
    ) { ... }
    

    Thêm một @Inject annotation vào contructor của UserRepository vì thế Dagger biết cách tạo một UserRepository:

    // @Inject lets Dagger know how to create instances of this object
    class UserRepository @Inject constructor(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
    ) { ... }
    

    Trong đoạn mã ở trên, bạn đang nói với Dagger rằng:

    1. Cách để tạo một instance UserRepository bằng phương thức khởi tạo có annotate @Inject.
    2. Các phụ thuộc của nó là: UserLocalDataSourceUserRemoteDataSource.

    Bây giờ Dagger biết cách tạo một instance của UserRepository, nhưng nó không biết cách tạo các phụ thuộc của nó. Nếu bạn cũng chú thích các lớp khác, Dagger biết cách để tạo chúng:

    class UserLocalDataSource @Inject constructor() { ... }
    class UserRemoteDataSource @Inject constructor() { ... }
    

    Dagger component

    Dagger có thể tạo một graph các phụ thuộc trong dự án của bạn mà nó có thể sử dụng để tìm ra nơi mà nó nên lấy những phụ thuộc đó khi họ cần. Để Dagger làm được điều này, bạn cần tạo một interface và annotate nó với @Component. Dagger tạo một container khi bạn sẽ làm việc với việc tiêm phụ thuộc thủ công.

    Bên trong interface Component, bạn có thể định nghĩa các function mà trả về các instance của class mà bạn cần(ví dụ. UserRepository). @Component nói Dagger sinh ra một container với tất cả các phụ thuộc được yêu cầu để đáp ứng. Đây được gọi là Dagger component ; nó chứa một graph mà bao gồm các đối tượng mà Dagger biết cách để cung cấp và các phụ thuộc tương ứng của chúng.

    // @Component makes Dagger create a graph of dependencies
    @Component
    interface ApplicationGraph {
        // The return type  of functions inside the component interface is
        // what can be provided from the container
        fun repository(): UserRepository
    }
    

    Khi bạn build project, Dagger sinh ra một implementation của interface ApplicationGraphcho bạn là: DaggerApplicationGraph. với bộ xử lý annotation của nó, Dagger tạo một dependence graph mà bao gồm các quan hệ giữa ba class(UserRepository, UserLocalDataSource, UserRemoteDataSource) với chỉ một ertry point: getting một instance UserRepository. Bạn có thể sử dụng nó như sau:

    // Create an instance of the application graph
    val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
    // Grab an instance of UserRepository from the application graph
    val userRepository: UserRepository = applicationGraph.repository()
    

    Dagger tạo một new instance của UserRepository mỗi khi nó được yêu cầu.

    val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()
    
    val userRepository: UserRepository = applicationGraph.repository()
    val userRepository2: UserRepository = applicationGraph.repository()
    
    assert(userRepository != userRepository2)
    

    Thi thoảng, bạn cần có một instance duy nhất của một dependency trong một container. Bạn có thể muốn điều này cho một vài lý do:

    1. Bạn muốn các loại khác mà có loại này làm một dependency để chia sẻ cùng một instance, như là nhiều đối tượng ViewModel trong luông đăng nhập sử dụng cùng LoginUserData.
    2. Một đối tượng là đắt giá để tạo và bạn không muốn tạo một new instance mỗi lần nó được khai báo như một phụ thuộc(cho ví dụ, một JSON parser).

    Trong ví dụ, bạn có thể muốn có một instance UserRepository duy nhất có sẵn trong graph để mỗi khi bạn yêu cầu một UserRepository, bạn luôn lấy cùng một instance. Điều này hữu ích trong ví dụ của bạn vì trong một ứng dụng thực tế có graph phức tạp hơn, bạn có thể có nhiều đối tượng ViewModel phụ thuộc vào UserRepository và bạn không muốn tạo một instance mới của UserLocalDataResourceUserRemoteDataResource mỗi lần UserRepository cần được cung cấp.

    Trong tiêm phụ thuộc thủ công, bạn đã làm điều này bằng cách pass sung một instance của UserRepository vào contructors của các classs ViewModel; nhưng trong Dagger, bởi vì bạn không viết thủ công, bạn nói cho Dagger biết răng bạn muốn sử dụng same instance. Điều này có thể hoàn thành với scope annotations.

    Scoping với Dagger

    Bạn có thể sử dụng scope annotations để giới hạn tồn tại của một object trong suốt thời gian tồn tại component của nó. Điều này có nghĩa là cùng instance của một phụ thuộc được sử dụng mỗi khi kiểu đó cần được cung cấp cho một class nhận nó làm phụ thuộc.

    Dể có một instance duy nhất của UserRepository khi bạn yêu cầu repository trong ApplicationGraph, sử dụng same scope annotation cho interface @ComponentUserRepository. Bạn có thể sử dụng annotation @Singleton cái mà đã đi kèm với gói javax.inject mà Dagger sử dụng.

    @Singleton
    @Component
    interface ApplicationGraph {
        fun repository(): UserRepository
    }
    
    // Scope this class to a component using @Singleton scope
    @Singleton
    class UserRepository @Inject constructor(
        private val localDataSource: UserLocalDataSource,
        private val remoteDataSource: UserRemoteDataSource
    ) { ... }
    

    Trong cả hai trường hợp, đối tượng được cung cấp cùng một phạm vi được sử dụng để chú thích giao diện @Component. Do đó, mỗi khi bạn gọi applicationGraph.repository(), bạn sẽ nhận được cùng một phiên bản của UserRepository.

    val applicationGraph: ApplicationGraph = DaggerApplicationGraph.create()

    val userRepository: UserRepository = applicationGraph.repository()
    val userRepository2: UserRepository = applicationGraph.repository()
    
    assert(userRepository == userRepository2)
    

    Chú ý: Đối với các class như Activity hay fragment không thể inject qua contructor bời vì các class này là hệ thống tự gọi => sử dụng field inject:

    class LoginActivity: Activity() {
        @Inject lateinit var loginViewModel: LoginViewModel
    }
    

    Kết luận

    Dagger giúp việc tiêm phụ thuộc trở lên đơn giản hơn bằng cách sinh code tự động như chúng ta làm bằng tay, việc quản lý các phụ thuộc cũng dễ dàng hơn khi sử dụng Dagger. Bài viết này đã nếu một vài điều cơ bản về cách hoạt động của Dagger, bài viết sau mình sẽ giới thiệu về Dagger Hilt cái mà giúp cho việc tiêm phụ thuộc còn đơn giản hơn Dagger.