Android Jetpack

Công ty cổ phần thương mại Vạn Tín Việt

Thông Thạo Jetpack – Phần 1 – Jetpack Là Gì?

Xin chào tất cả các bạn.

Chào mừng các bạn đến với bài viết đầu tiên về chủ đề Jetpack. Đây là bài viết đánh dấu việc mình cố gắng quay trở lại nhịp viết bài thường xuyên sau hơn 1 năm nghỉ “dịch”.

Với việc quay trở lại viết, không gì tốt hơn bằng tạm thời cứ mở ra một chủ đề mới, cho nó có khí thế, sau này mình sẽ hoàn thành các bài viết còn dang dở sau các bạn nhé.

Nhân tiện khi nói về các bài dang dở, mình xin chia sẻ một chút rằng tại sao mình lại viết mà không xong? Một phần là các bài đó, cũng như chủ đề mới này của mình, tất cả mình đều không có sẵn kịch bản hay sườn bài dài lâu cho chúng. Mình chỉ đơn thuần phân các kiến thức lớn thành các module nhỏ, mỗi module này sẽ có những mục nhỏ hơn, được chia thành các bài viết, sắp xếp theo một chủ ý nào đó mà mình nghĩ khi đọc qua các bạn sẽ dễ dàng theo dõi và cùng trải qua các bài viết với mình. Do vậy mà các phần được viết ngày một thêm vào, mà không biết liệu phần nào là kết thúc. Tuy vậy mình vẫn thường xuyên tìm cách kết thúc các phần đã được mở ra này, các bài viết trước và cả Jetpack này cũng vậy nhé. Một vài chia sẻ cho bạn nào còn nhiều thắc mắc với mình về vấn đề này.

Quay lại Jetpack. Chủ đề này không mới, mình đọc sơ qua cũng đã thấy biết bao nhiều tài liệu nói về nó rồi. Ngay cả trước đây mình cũng từng viết vài bài về các chủ đề nhỏ thôi của Jetpack. Vậy mình xin được viết lại câu chuyện về Jetpack này, có vay mượn các kiến thức từ các bậc “tiền bối”, sửa chữa lại các bài mà mình đã viết, kết hợp với tiêu chí “lập trình dễ dàng” của mình vào nữa, để làm sao cho khái niệm và kiến thức Jetpack trở nên rõ ràng, gần gũi và dễ dàng tiếp cận hơn nhé. Mời các bạn cùng bắt đầu tìm hiểu.

Jetpack Là Gì?

Jetpack là một bộ các thư viện được Google giới thiệu từ tháng 5 năm 2018. Vâng rất nhiều thư viện. Và bộ thư viện này vẫn đang được Google bổ sung thêm cho tới ngày nay. Một trong số chúng là các thư viện dùng để thay thế các thư viện cũ mà trước đây các lập trình viên Android vẫn quen dùng. Phần lớn còn lại là các thành phần (hay còn gọi là component) mới. Không có một ràng buộc nào bắt bạn phải sử dụng hoàn toàn Jetpack cho việc xây dựng ứng dụng của bạn cả, nhưng nếu biết đến nó và nắm bắt được nó, như logo hay ý nghĩa mà Jetpack mang lại, nó sẽ là một “bệ phóng tên lửa”, sẽ tạo một lực đẩy mạnh mẽ đẩy bạn cũng như ứng dụng của bạn tiến nhanh về phía trước.

Ý nghĩa thú vị của Jetpack
Ý nghĩa thú vị của Jetpack

Thực ra nếu bạn có thử trải nghiệm xây dựng ứng dụng thông qua một vài thành phần ở Architecture Component mà mình đã giới thiệu, bạn sẽ thấy rằng các thư viện trong Jetpack nó giải quyết các bài toán khó khăn trong lập trình Android như thế nào.

Bên cạnh những thành phần mới mà Jetpack mang lại này, Google còn muốn thông qua đó giới thiệu thêm các nguyên tắc lập trình mới. Các nguyên tắc này vừa giúp sử dụng các thành phần trong Jetpack hiệu quả, vừa giúp xây dựng ứng dụng nhanh chóng và ít lỗi hơn. Chúng ta sẽ cùng nhau xem xét các thành phần Jetpack và các nguyên tắc ở những bài sau nhé.

Tại Sao Phải Dùng Jetpack?

Như mình cũng nói sơ qua trên đây rồi, việc sử dụng Jetpack là không hoàn toàn bắt buộc. Jetpack được Google thiết kế thành các gói thư viện “mở”. Có nghĩa là nó không có sẵn trong Android SDK, khi nào bạn muốn dùng thư viện Jetpack nào thì thêm thư viện đó vào. Có một số thứ buộc chúng ta phải dùng, như thư viện tương thích ngược androidx.appcompat, phần còn lại là tùy thuộc vào sự mong muốn sử dụng của bạn, thích hay biết thư viện Jetpack nào thì thêm thư viện đó. Bạn vẫn có thể thích sử dụng cách cũ hơn nên “né” Jetpack, điều đó là tùy bạn. Với thuận lợi ban đầu này thì bạn có thấy cảm tình với Jetpack chưa nào.

Dù việc sử dụng Jetpack hay không là “tùy duyên”, thì mình vẫn mong muốn chúng ta đều phải biết Jetpack và các thư viện kèm theo, biết càng nhiều càng tốt. Vì sao thì mình liệt kê như dưới đây.

Trước hết, theo kinh nghiệm của mình, nếu bạn không biết Jetpack là gì, bạn sẽ là một lập trình viên lỗi thời, và sự nghiệp đi phỏng vấn xin việc của bạn chắc chắn sẽ gặp vô vàn khó khăn do nhà tuyển dụng sẽ luôn thích hỏi đến các kiến thức về chủ đề này. Và bạn sẽ không hề muốn cứ luôn phải trả lời “dạ vấn đề này em chưa biết nhưng em sẽ tìm hiểu sau”. Sau là bao giờ nếu không phải bây giờ?

Nhưng nếu bạn không có ý định tìm việc mới? Thì việc không biết Jetpack sẽ khiến bạn vẫn loay hoay với những vấn đề đau đầu trong quá trình xây dựng một ứng dụng Android, trong khi các lập trình viên khác biết Jetpack sẽ dễ dàng và nhanh chóng hơn trong việc giải quyết các vấn đề mà bạn đang vướng phải này. Các vấn đề này là gì? Mình tóm lược lại như sau.

  • Loay hoay quản lý các vòng đời của Activity hay Fragment. Dĩ nhiên là một lập trình viên Android, điều tiên quyết là bạn phải hiểu rõ vòng đời của Activity hay Fragment rồi. Nhưng nếu logic của code đang nằm trong chính Activity hay Fragment thì không có gì để nói, vấn đề là bạn có thể phải cần xây dựng các logic ở các lớp khác, ở thread khác, nhưng vẫn muốn bám sát vào vòng đời của Activity hay Fragment, đó mới là vấn đề đau đầu. Và Jetpack sẽ giúp bạn giải quyết nó.
  • Lưu giữ dữ liệu của Activity hay Fragment trong suốt quá trình sống của màn hình đó, dù cho user có xoay ngang hay dọc thiết bị, hay các Fragment được thay thế, thì dữ liệu này vẫn được bảo toàn. Jetpack sẽ giúp bạn giải quyết nó.
  • Tránh các lỗi crash ứng dụng liên quan đến memory leak. Jetpack cũng giúp giải quyết tốt chuyện này.
  • Và còn nhiều lỗi khác nữa mà Jetpack cũng sẽ giúp bạn xây dựng các ứng dụng Android một cách nhanh hơn, ít lỗi hơn, dễ quản lý hay sửa chữa hơn, và cũng dễ xây dựng các Unit Test hơn.

Tổng Quan Về Android Jetpack

Tuy thư viện của Jetpack khá nhiều, nhưng khi vừa mới giới thiệu, Google đã nhóm chúng làm 4 phần để các lập trình viên dễ dàng tiếp cận hơn. Chúng được thể hiện theo mô hình sau.

4 phần chính trong Android Jetpack (mô hình ban đầu của Google)
4 phần chính trong Android Jetpack (mô hình ban đầu của Google)

Nên nhớ đây chỉ là mô hình ở lần giới thiệu đầu tiên của Google về Jetpack. Những thư viện được đánh dấu là new! là những thứ mới toanh từ ngày ra mắt, số còn lại thì đã có sẵn rồi.

Một năm sau đó, Google bổ sung vào bộ sưu tập Jetpack này một loạt thư viện nữa. Bao gồm CameraX hay các thư viện nâng cấp cho các thư viện trước đây. Và đặc biệt, thời gian này còn có một khái niệm mới ra đời, đó là Jetpack Compose (đây là gì thì mình sẽ có loạt bài viết riêng, mặc dù chúng cũng nằm trong họ Jetpack nhưng kiến thức về chúng là rất nhiều, do đó mình phải tách ra nói riêng thôi).

Không dừng lại, Google vẫn thường liên tục giới thiệu lần lượt các thư viện mới cho Jetpack. Và mình cũng không biết rằng liệu đến giai đoạn hiện tại, Google đã cho ra hết các thành phẩm Jetpack hay chưa.

Chúng ta cùng xem sơ qua lần lượt chức năng của từng nhóm chính nhé, còn các thành phần cụ thể như thế nào thì mình hẹn các bạn ở các bài viết riêng.

Foundation

Các thành phần ở nhóm này được gọi là các thành phần nền tảng. Cũng như tên gọi, chúng sẽ là các thư viện giúp tác động đến nền tảng của Android, như thư viện tương thích ngược, các thư viện hỗ trợ Kotlin cũng như hỗ trợ cho việc testing. Chúng bao gồm App CompatAndroid KTXMultidexTest,…. Chắc chắn chúng ta sẽ có thời gian để đào sâu vào từng thành phần này sau nhé.

Architecture

Các thành phần ở nhóm này sẽ tập trung vào việc làm sao có thể xây dựng một ứng dụng nhanh chóng, dễ dàng kiểm lỗi cũng như dễ bảo trì, sữa chữa sau này. Mình cũng đã có một loạt bài viết về một số thành phần trong Architecture Component này. Giờ thì chúng ta có dịp tổng hợp lại cho nó đầy đủ hơn. Có thể kể đến các thành phần này bao gồm Data BindingLifecyclesLiveDataNavigationPagingRoomViewModelWorkManager,….

Behavior

Các thành phần ở nhóm này sẽ giúp ứng dụng của chúng ta có thể kết nối dễ dàng đến các dịch vụ của hệ thống. Bao gồm Download ManagerMedia & PlaybackCameraXNotificationsPermissionsPreferencesSharingSlices,…. Nếu những thứ này quá lạ lẫm với bạn thì… không sao, chúng ta sẽ nói đến nó từ từ.

UI

Nhóm thành phần cuối cùng này cũng khá quan trọng. Nó giúp chúng ta xây dựng nên ứng dụng với giao diện đẹp hơn, chuyên nghiệp hơn và sáng sủa hơn. Như Animation and transitionsAutoEmojiFragmentLayoutPaletteTVWear,…. Và dĩ nhiên chúng ta cũng sẽ đi đến từng thành phần này sau nhé.

Kết Luận

Chúng ta vừa mới dạo qua một chút, vâng một chút về Jetpack thôi. Kiến thức về Jetpack nó rộng lớn như chính hệ điều hành Android vậy. Do đó mình cũng không hoàn toàn tự tin rằng kiến thức của mình là đủ. Mình chỉ hi vọng mình biết đến đâu thì nó đúng đến đó, vừa chia sẻ vừa học hỏi. Nên nếu có những thắc mắc, phản biện thì các bạn hãy để lại bình luận, hoặc chat với mình nhé, mình sẽ nhanh chóng sửa lỗi để các bài viết của mình luôn mang đến kiến thức kịp thời và đúng đắn nhất.

Thông Thạo Jetpack – Phần 2 – Android KTX

Như được giới thiệu ở phần đầuAndroid KTX được Google trình làng cùng thời điểm với đợt giới thiệu lần đầu tiên về Android Jetpack. Chính vì lẽ đó mà mình cũng chọn em nó cho bài viết cụ thể về Jetpack đầu tiên này.

Android KTX này thực sự rất thú vị, tuy đôi lúc nó gây ra sự bối rối đối với các lập trình viên nếu không biết về thể loại thư viện này, bởi thay vì dùng các cách được khuyến cáo của từng loại thư viện, Android KTX lại cho ra một cách viết khác. Nguyên nhân bởi vì Android KTX không phải một dạng thư viện phục vụ cụ thể cho một mục đích nào đó như anh em của nó trong họ Jetpack, mà nó giúp tăng cường tính “thẩm mỹ” cho các thư viện họ hàng, giúp các lập trình viên dễ dàng thao tác với các thư viện Jetpack và cả không phải Jetpack khác, bằng cách cung cấp những dòng code đẹp hơn, dễ nhìn hơn. Và dĩ nhiên cũng giúp giảm lỗi hơn với việc code ngắn gọn và xúc tích hơn kiểu này. Ngoài việc mang đến một ngoại hình mới cho các dòng code của các thư viện đã có, Android KTX còn giúp chúng ta có những ý tưởng thú vị hơn trong việc xây dựng nên những Kotlin Extension cho project của chúng ta. Tất cả những ý đẹp đẽ trên đây cụ thể như thế nào mời các bạn cùng vào bài viết với mình.

Android KTX Là Gì?

Trước hết là ở cái tên. Thực sự ngay khi vừa đọc đến, dù biết là khá nhảm, nhưng cái chữ KTX gợi cho mình cái tên Ký Túc Xá!!! Nói cho vui thôi, thực chất Android KTX là một từ viết ngắn hơn và có một tí chơi chữ của từ Android Kotlin Extensions. Đọc cái tên cũng mở ra cho chúng ta một tí ý niệm, đó là đây sẽ là các Tiện ích mở rộng của Kotlin để giúp xây dựng ứng dụng Android.

Tiện ích này là các thành phần được xây dựng sẵn, được tổng hợp lại thành các gói thư viện riêng lẻ để mở rộng, để bổ sung cho các thư viện sẵn có. Giúp chúng ta xây dựng các chức năng thay vì dùng các code cũ thì với Android KTX, code Kotlin của chúng ta sẽ trở nên đẹp đẽ, quyến rũ và dễ đọc hơn.

Dưới đây là video của Google về Android KTX, nếu bạn không thích xem video thì cứ đọc các mục của mình bên dưới cũng được.

Video của Google về Android KTX

Một Ví Dụ Áp Dụng Android KTX

Để dễ hình dung hơn về Android KTX, chúng ta hãy nhìn qua một ví dụ sau. Ví dụ này đơn giản là sử dụng Shared Preferences để lưu trữ một giá trị Boolean vào bộ nhớ của thiết bị, đây là cách viết truyền thống hẳn nhiều bạn không hề xa lạ.

val sharedPref = this.getSharedPreferences("com.myapp.shared_preferences", Context.MODE_PRIVATE)
val editor = sharedPref.edit()
editor.putBoolean(SHOW_DELETED_WORDS_KEY, enable)
editor.apply()

Nếu bạn đã quen dùng các dòng code này rồi thì có nhận ra rằng đôi khi chúng ta quên mất dòng 

editor.apply()

 cuối cùng, hoặc để có thể lưu trữ các key-value này thì dòng code khai báo 

editor = sharedPref.edit()

 thể hiện sự dư thừa trong code hay không. Và đây là cách viết bằng Android KTX, bạn so sánh nhé.

val sharedPref = this.getSharedPreferences("com.myapp.shared_preferences", Context.MODE_PRIVATE)
sharedPref.edit {
putBoolean(SHOW_DELETED_WORDS_KEY, enable)
}

Rõ ràng là chúng ta không cần phải để ý đến editor, và như vậy sẽ không sợ quên dòng 

editor.apply()

 đúng không nào.

Bạn đã hiểu cách mà Android KTX giúp chúng ta viết code như thế nào chưa nào.

Android KTX Xây Dựng Các Tiện Ích Mở Rộng Như Thế Nào

Mục này mình nói thêm cho bạn nào thích tìm hiểu sâu, nếu bạn không muốn quan tâm Android KTX thực chất là gì thì có thể xem qua mục tiếp theo để biết cách dùng Android KTX ở những trường hợp cụ thể nhé.

Như ví dụ trên, bạn có thể lầm tưởng rằng Android KTX can thiệp vào đối tượng SharedPreferences và làm thay đổi nó. Thực chất không phải vậy, phần lớn sự chỉnh sửa của Android KTX là dựa vào một chức năng của ngôn ngữ Kotlin có tên Extension functions. Tính năng này của Kotlin giúp chúng ta mở rộng các function cho các lớp Kotlin có sẵn mà không cần phải sửa chữa hay kế thừa lớp đó.

Nếu nhìn vào các dòng code đã xây dựng nên SharedPreferences.edit(), bạn sẽ hiểu rõ hơn.

inline fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor.() -> Unit
) {
val editor = edit()
action(editor)
if (commit) {
editor.commit()
} else {
editor.apply()
}
}

Bạn thấy Extension functions này cho phép chúng ta truyền vào 2 tham số. Tham số commit có giá trị mặc định là false nên nếu bạn không truyền vào giá trị cho tham số này thì việc code sẽ như ví dụ trên kia, còn nếu bạn muốn truyền tham số vào thì có thể viết tường minh ra như thế này.

val sharedPref = this.getSharedPreferences("com.myapp.shared_preferences", Context.MODE_PRIVATE)
sharedPref.edit(commit = false) {
putBoolean(SHOW_DELETED_WORDS_KEY, enable)
}

Bạn đã hiểu Android KTX hoạt động như thế nào chưa. Với mục này thì mình “mổ xẻ” một em ra làm ví dụ thôi nhé. Mục tiếp theo đây mình chỉ liệt kê các cách cũ và cách Android KTX để bạn so sánh và áp dụng thôi, bạn tự tìm hiểu kỹ chúng nha.

Áp Dụng Android KTX Cho Một Số Thư Viện

Android KTX được xây dựng cho hơn 20 thư viện khác nhau, cả cho Jetpack lẫn các thư viện ngoài Jetpack. Do đó việc áp dụng Android KTX cho thư viện nào là hoàn toàn tùy ở bạn. Dưới đây là nguyên tắc nếu bạn muốn dùng Android KTX cho một thư viện nào đó.

Có một nguyên tắc nếu bạn đang xây dựng ứng dụng bằng ngôn ngữ Kotlin, đó là hãy thêm -ktx vào sau các thư viện được khai báo. Thì bạn sẽ vừa có thư viện đó, vừa có Android KTX bổ sung cho thư viện đó. Tất nhiên là chỉ áp dụng -ktx đươc với các thư viện được Android KTX hỗ trợ thôi nhé, chi tiết những thư viện nào thì bạn có thể xem thêm ở link này của Google.

Mình giả sử như nếu project bạn đang code bằng Java và có khai báo sử dụng thư viện ViewModel.

implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version"

Thì với project viết bằng Kotlin, bạn hãy sử dụng thư viện ViewModel với -ktx hỗ trợ luôn như sau.

implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"

Dạo Qua Một Vài Android KTX

Như mục trên có nói rằng Android KTX được xây dựng cho hơn 20 thư viện khác nhau. Thế thì nếu chúng ta nói hết về Android KTX này thì nhiều lắm. Chính vì vậy mình sẽ tuyển chọn ra vài em thường dùng để chúng ta cùng dạo qua. Không chỉ các thư viện mở rộng cho Jetpack mà còn cho các thành phần quen thuộc khác nữa nhé. Mình nhắc lại rằng nếu bạn nào quan tâm có bao nhiêu thư viện Android KTX mà Google hỗ trợ thì có thể thể “ngâm cứu” thêm ở link này của Google nhé.

Core KTX

Với Android KTX của nhóm này thì bạn chỉ cần xem project của mình đã khai báo thư viện sau trong build.gradle của module chưa nhé.

dependencies {
implementation "androidx.core:core-ktx:1.3.2"
}

SharedPreferences

Như ví dụ trên kia, mình không nói nhiều, mỗi khi bạn muốn sử dụng SharedPreferences trong Kotlin được ngắn gọn và thú vị hơn, hãy dùng Android KTX.

val sharedPref = this.getSharedPreferences("com.myapp.shared_preferences", Context.MODE_PRIVATE)
sharedPref.edit(commit = false) {
putBoolean(SHOW_DELETED_WORDS_KEY, enable)
}

String to Uri

Mỗi khi bạn parse một String thành Uri. Thay vì dùng cách bình thường như nào giờ vẫn dùng.

val uri = Uri.parse(myUriString)

Thì hãy thử với Android KTX xem có dễ nhìn hơn không nhé.

val uri = myUriString.toUri()

TextWatcher

Nếu bạn đã từng lắng nghe sự kiện text thay đổi trên một EditText, như sau.

edtWatcher.addTextChangedListener(object : TextWatcher {
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
tvWatcher.text = s.toString()
}
 
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
 
override fun afterTextChanged(s: Editable?) {
}
})

Thì bạn có thấy rằng có quá nhiều dòng code dư thừa do chúng ta buộc phải implement hết tất cả các function bên trong interface TextWatcher. Sự thực là chúng ta chỉ cần override mỗi onTextChanged mà thôi. Vậy hãy xem Android KTX giúp chúng ta giải quyết bài toán này như sau.

edtWatcher.doOnTextChanged { text, start, before, count -> tvWatcher.text = text }

Tạo Bundle

Nếu bạn muốn truyền dữ liệu nhỏ giữa các Activity hay Fragment thông qua Bundle, thay vì tạo Bundle theo cách cũ.

val bundle = Bundle()
bundle.putString("name", "My Name")
bundle.putInt("age", 18)

Thì Android KTX giúp code này trông đẹp hơn như sau.

val bundle = bundleOf(
"name" to "My Name",
"age" to 18
)

Activity KTX

Thư viện cần khai báo.

dependencies {
implementation "androidx.activity:activity-ktx:1.2.2"
}

Khai báo ViewModel

Mặc dù chức năng này hỗ trợ cho việc khai báo ViewModel, nhưng nó không thuộc thư viện mở rộng cho ViewModel, mà là mở rộng cho Activity nhé.

Nếu trong Activity, bạn hay khai báo một ViewModel như sau.

class MainActivity : AppCompatActivity() {
 
private lateinit var viewModel: MainViewModel
 
override fun onCreate(savedInstanceState: Bundle?) {
// ...
 
viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
 
// ...
}
}

Thì hãy chuyển sang sự hỗ trợ của Android KTX xem như thế nào nhé.

class MainActivity : AppCompatActivity() {
 
private val viewModel: MainViewModel by viewModels()
 
// ...
}

Fragment KTX

Để dùng Android KTX ở nhóm này, bạn hãy khai báo thư viện sau.

dependencies {
implementation "androidx.fragment:fragment-ktx:1.3.2"
}

Fragment Transaction

Nếu bạn đã từng dùng qua việc add fragment động vào một FrameLayout như sau.

supportFragmentManager
.beginTransaction()
.replace(R.id.container, SampleFragment.newInstance(), SampleFragment.TAG)
.commitAllowingStateLoss()

Vậy thì cách dùng này của Android KTX có làm bạn cảm thấy thích hơn không nào.

supportFragmentManager.commit(allowStateLoss = true) {
replace(R.id.container, SampleFragment.newInstance(), SampleFragment.TAG)
}

Khai Báo ViewModel

Nếu Android KTX hỗ trợ khai báo ViewModel bên trong Activity như trên kia, thì với Fragment cũng vậy.

Bạn khai báo ViewModel bằng cách cũ trong Fragment như sau.

class HomeFragment : Fragment() {
 
private lateinit var viewModel: MainViewModel
 
// ...
 
 
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
 
viewModel = ViewModelProvider(requireActivity()).get(MainViewModel::class.java)
 
// ...
}
}

Việc khai báo trên đây rất dễ dẫn đến một điều là các viewModel ở các Fragment bỗng dưng chẳng liên quan gì với nhau, bạn đã set giá trị cho viewModel ở Fragment này, mà qua Fragment khác giá trị đó vẫn như cũ. Đó là vì bạn rất dễ bị nhầm giữa việc khai báo 

ViewModelProvider(requireActivity())

 thành 

ViewModelProvider(this)

 lắm nhé.

Để tránh lỗi trên và code được gọn hơn nhiều, Android KTX viết như sau.

class HomeFragment : Fragment() {
 
private val viewModel: MainViewModel by activityViewModels()
 
// ...
}

LiveData KTX

Khai báo thư viện.

dependencies {
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
}

Observer

Bạn có nhớ với LiveData bạn thường xuyên implement Observer như thế nào không.

viewModel.articles.observe(this, Observer { articles ->
articles?.forEach {
// Do your job
}
})

Giờ thì bạn có thể viết ngắn hơn một chút.

viewModel.articles.observe(this) { articles ->
articles?.forEach {
// Do your job
}
}

ViewModel KTX

Khai báo thư viện.

dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1"
}

CoroutineScope

Mỗi khi bạn dùng đến Coroutines bên trong một ViewModel, hãy thoải mái gọi đến viewModelScope(). Khi này một Coroutines sẽ được tạo với Scope gắn liền với đời sống của ViewModel đó.

class MainViewModel : ViewModel() {
private fun makeNetworkRequest() {
viewModelScope.launch {
// ...
}
}
}

Kết Luận

Mặc dù mình cảm thấy những liệt kê trên của mình về Android KTX là chưa đầy đủ lắm, thậm chí là khá ngắn ngủi so với sự hỗ trợ hùng hậu từ thư viện này. Vậy thì một lần nữa, mình muốn các bạn hãy tự vào trang chính thống này của Google, thử nghiên cứu và suy nghĩ xem mỗi một hỗ trợ này của Android KTX sẽ được dùng như thế nào vào các project của riêng bạn nhé.

Thông Thạo Jetpack – Phần 3 – Navigation

Lại một chủ đề nữa của Jetpack được chúng ta mang ra mổ xẻ, để hiểu và sử dụng thành thạo công năng của nó. Và cái tên hôm nay được nhắc đến chính là Navigation.

Thực tế thì khối lượng kiến thức về Navigation này khá là nhiều, và lợi ích mà thư viện này mang lại nó cũng nhiều như độ lớn của kiến thức này vậy. Thư viện này giúp chúng ta xây dựng các màn hình của ứng dụng, cụ thể là việc bố trí các Fragment, sắp xếp chúng, tạo cho chúng sự tương tác, các kịch bản tới/lui giữa chúng, một cách dễ dàng và trực quan hơn với cách cũ trước đây. Và mình cũng cá một điều rằng rất nhiều bạn cũng đã nhanh chóng làm quen và nắm được cách thức vận dụng Navigation này rồi. Bài viết hôm nay của mình sẽ cố gắng mang đến cái nhìn ban đầu về loại thư viện này, sẽ có các phần sau nữa sẽ là các phần mà chúng ta cùng nhau xây dựng một ứng dụng đơn giản, rồi nâng cấp với việc sử dụng sâu hơn các công cụ hỗ trợ khác của thư viện.

Giới Thiệu Navigation

Bạn có thể xem thông tin giới thiệu về Navigation dưới đây.

Video giới thiệu về Navigation Component

Để nói về Navigation, thì điều đầu tiên chúng ta cần mang ra bàn luận, đó là “triết lý thiết kế ứng dụng Android”.

Chà, nói đến triết lý, thì cũng mệt với ông Android này. Thật vậy, một khi bạn theo đuổi nền tảng này một thời gian khá lâu, hoặc khi bạn tạm thời không còn làm Android nữa, đột nhiên một ngày nọ bạn vào trang chính Developer của Android, bạn sẽ thấy mọi thứ thay đổi, như thể bạn vừa bị tẩy não vậy, lạ lẫm và mò mẫm như ngày mới tiếp cận.

Ở những ngày đầu khi thiết kế ứng dụng Android, bạn được giới thiệu rằng một ứng dụng nên có nhiều Activity. Mỗi Activity là một màn hình độc lập. Thời điểm này hễ cần thiết thì chúng ta cứ tạo một Activity cho một màn hình mới. Màn hình đứng và ngang là những Activity khác nhau. Điều chúng ta quan tâm hơn nữa là đường đi giữa các Activity, Activity nào sẽ start Activity nào, khi một Activity kết thúc thì nó sẽ trở về Activity nào. Rồi cả việc trao đổi dữ liệu giữa các Activity với nhau nữa.

Về sau, đùng một phát chúng ta được Google giới thiệu đến Fragment. Và triết lý thiết kế ứng dụng cũng từ đó thay đổi theo. Thay vì tập trung vào việc tạo ra nhiều Activity. Các ứng dụng Android khi này được khuyến cáo là nên “tiết kiệm” Activity lại. Một ứng dụng chỉ cần thậm chí một Activity. Các màn hình lúc này cũng được các Fragment đảm nhiệm. Một Activity đang sống có khả năng thêm vào/thay thế/gỡ bỏ các Fragment cho phù hợp với ngữ cảnh của ứng dụng. Thậm chí với các màn hình xoay ngang hay dọc cũng chỉ là các thay đổi về Fragment. Việc trao đổi dữ liệu lúc bấy giờ có phần dễ thở hơn do bản thân các Activity sẽ sống lâu hơn và đảm nhiệm vai trò truyền đạt dữ liệu giữa các Fragment mà nó nắm giữ với nhau.

Rồi về sau nữa. À không, bây giờ. Jetpack ra đời. Cung cấp một vài thư viện cao cấp trong bộ Architecture Component, giúp cho việc quản lý về dữ liệu giữa các Fragment được dễ thở và thậm chí có phần tuyệt vời hơn các cách cũ trước đây, có thể kể ra đây như ViewModel và LiveData. Chưa dừng lại ở sự hỗ trợ về mặt dữ liệu, Jetpack còn mang đến một thư viện mới mẻ có cái tên Navigation. Từ đây, chúng ta có thể dễ dàng nắm bắt, điều khiển đường đi của các Fragment. Chúng ta quản lý được một kịch bản đường đi của các Fragment, biết chúng bắt đầu từ Fragment nào, đi tiếp qua Fragment nào, kết thúc ở Fragment nào,… một cách trực quan bằng giao diện.

Triết lý thiết kế ứng dụng Android như bạn thấy là thay đổi rất nhanh. Mặc dù việc thay đổi luôn khiến chúng ta lại phải lao vào học hỏi và tìm hiểu chúng. Nhưng rõ ràng lợi ích mà chúng mang lại là rất lớn, và việc tiếp cận chúng cũng khá thú vị đúng không nào (vi vọng các bạn cũng đang cảm thấy vậy). Vậy hãy xem Jetpack với Navigation sẽ thay đổi cách chúng ta thiết kế ứng dụng như thế nào ở mục tiếp theo nhé.

Lợi Ích Của Navigation

Có lẽ bạn đã biết được lợi ích của Navigation này thông qua mục trên đây rồi. Nhưng đó là nói sơ qua thôi. Có thể liệt kê các lợi ích của Navigation ở đây, bạn hãy từ từ làm quen và trải nghiệm.

  • Dĩ nhiên lợi ích đầu tiên là giúp bạn có một công cụ quản lý các tương tác giữa các Fragment rồi.
  • Nhưng nhờ có sự trợ giúp của công cụ này mà bạn còn được “miễn phí” luôn các xử lý của việc nhấn back của người dùng (ngày xưa hệ thống cũng căn cứ vào back stack để giúp chúng ta rồi nhưng nhờ Navigation mà nay việc back này sẽ hợp lý hơn).
  • Chúng ta còn có các resource cũng như các chuyển động theo chuẩn Android nữa.
  • Một điều khá hay là Navigation này cũng sẽ hỗ trợ tốt cho deep linking.
  • Chúng ta có sẵn Navigation UI, thành phần này giúp Navigation có thể làm việc tốt với các thể loại giao diện được xây dựng sẵn như ToolbarNavigation Drawers hay Bottom Navigation.
  • Được hỗ trợ Safe Args giúp truyền dữ liệu qua các thành phần bên trong Navigation được an toàn hơn.
  • Có được sự cộng tác tốt với ViewModel.

Các Thành Phần Của Navigation

Mình biết, thật sớm khi nói đến các thành phần của Navigation trong khi chưa biết cách thức hoạt động và quản lý của nó ra sao. Lúc mới làm quen Navigation mình cũng đã rất đau đầu ở mục này, đọc đi đọc lại hoài mà không hiểu bao nhiêu. Nhưng mình thấy các tài liệu đều nói trước vậy đó, bạn cứ đọc trước đi, sau này khi biết Navigation là gì rồi thì bạn hãy xem lại xem có hiểu nhiều hơn không nhé.

Navigation được chia làm 3 phần.

Navigation Graph

Đây là một resource XML mới mà Android đã định nghĩa ra, nó giúp Android Studio vẽ lên một mô hình các màn hình. Mỗi một màn hình này được gọi với cái tên khá rõ ràng là destination. Các destination này có thể được liên kết với nhau bởi các action, mỗi action như vậy có thể cho chúng ta thông tin về đường đi trực quan giữa các destination với nhau.

Bạn hãy xem một Navigation Graph của một ứng dụng có 6 destination và 5 action được mình lấy từ trang hướng dẫn của Google.

Một Navigation Graph mẫu
Một Navigation Graph mẫu

Bạn có thể thấy số 1 ở trên hình chính là một destination của Navigation Graph. Còn số 2 là action nối giữa hai destination rồi đúng không nào. Mục này mang tính chất giới thiệu cho bạn biết Navigation Graph là gì thôi nhé. Cách thức tạo resource dạng này, chi tiết và cách tương tác với nó như thế nào mình sẽ nói cụ thể hơn ở phần sau.

NavHost

Google viết sao mình viết y vậy, thực ra viết cho đầy đủ chữ trên là Navigation HostNavHost là một không gian trên màn hình để chứa từng destination bên trong Navigation Graph trên kia. Vậy bạn có thể hiểu, Navigation Graph giúp tạo ra một sơ đồ, hay kịch bản mà người dùng sẽ đi qua theo từng destination bởi action. Rồi từng destination đó phải cần một nơi để hiển thị lên trên màn hình, nơi đó chính là NavHost này đây.

Khi thiết kế ra thư viện Navigation này. Google muốn Activity sẽ là nơi chứa NavHost. Như vậy mỗi một Activity sẽ bao gồm một tập hợp nhiều màn hình con được biểu diễn trong Navigation Graph. Nếu bạn xây dựng một ứng dụng gồm nhiều Activity, bạn có thể có nhiều NavHost trong từng Activity đó.

Chúng ta cũng sẽ xem cách tạo một NavHost sau. Giờ đế thành phần cơ bản kế tiếp.

NavController

Một đối tượng được xây dựng ra để giúp chúng ta điều khiển việc qua lại giữa các destination bên trong NavHost. Bởi vì dù có Navigation Graph giúp diễn đạt các destination lẫn action, có NavHost để hiển thị destination nào lên màn hình rồi, nhưng không có các dòng lệnh giúp thực thi các thay đổi hiển thị này thì không được, và NavController cung cấp cho chúng ta các công cụ để làm chuyện đó.

Xây Dựng Ứng Dụng

Phần trên đây khá nặng về lý thuyết, chính vì vậy mình sẽ mang đến cho các bạn một ứng dụng Android với một chức năng cơ bản mà hầu hết các ứng dụng khác đều có, đó là chức năng đăng nhập. Dĩ nhiên đã gọi là đăng nhập thì cũng bao gồm đăng ký và đăng xuất luôn đấy nhé.

Dưới đây là mô phỏng việc sử dụng ứng dụng này, cho các bạn có cái nhìn ban đầu về nó.

Các màn hình đăng nhập được xây dựng bởi Navigation

Để xây dựng các màn hình trên, mình đã dựng nên một Navigation Graph như thế này.

Navigation Graph của ứng dụng trông như thế này
Navigation Graph của ứng dụng trông như thế này

Kịch bản là khi người dùng mở ứng dụng lên, homeFragment sẽ được hiển thị đầu tiên. Tuy nhiên nếu người dùng chưa đăng nhập, ứng dụng sẽ dẫn thẳng vào màn hình signInFragment, tại đây nếu người dùng chưa có account thì có thể vào signUpFragment để tạo rồi quay lại đăng nhập với signInFragment. Sau khi đăng nhập thành công, ở những lần mở ứng dụng sau đó, người dùng sẽ thấy được màn hình homeFragment, từ đây có thể vào được profileFragment. Nếu ở profileFragment mà người dùng nhấn nút Logout, ứng dụng sẽ đăng xuất và (một cách tự động) signInFragment sẽ lại được hiển thị.

Về kỹ thuật, mình không có dùng gì cao siêu cả. Ứng dụng sẽ không có Social login. ViewModel và LiveData được dùng ở mức đơn giản đủ để chạy logic. Dữ liệu người dùng được lưu trực tiếp trên thiết bị nhưng không lưu với SQLite Database hoành tráng mà chỉ lưu theo dạng key-value vào SharedPreferences mà thôi. Mình chỉ tập trung nhất có thể vào Navigation, nhằm giúp cho các bạn có một cái nhìn tốt nhất về thư viện này của Jetpack.

Và chúng ta sẽ bắt đầu xây dựng ứng dụng này ở các phần sau. Hẹn gặp lại các bạn.

Kết Luận

Chúng ta vừa đi qua phần 1 và cùng nhau tìm hiểu sơ về các khái niệm của thư viện Navigation của Jetpack này. Ở bài sau chúng ta sẽ cùng nhau xây dựng nên những thứ mà bài viết hôm nay nhắc đến. Bạn sẽ hiểu rõ hơn về chúng và cách sử dụng chúng như thế nào trong việc xây dựng một ứng dụng một cách dễ dàng hơn nhé.

Thông Thạo Jetpack – Phần 4 – Navigation (Tập 2)

Ở bài hôm trước chúng ta đã cùng nhau tìm hiểu xem Navigation là gì và được cấu thành từ các thành phần nào. Chúng ta còn làm quen với ứng dụng mẫu được xây dựng lên từ thư viện này nữa đấy.

Phần tiếp theo hôm nay chúng ta sẽ cùng nhau xây dựng ứng dụng ở các bước ban đầu, song song với việc làm rõ nghĩa dần các khái niệm đã được đưa ra từ phần trước, giúp bạn hiểu rõ hơn về thư viện Navigation này.

Trước tiên chúng ta hãy cùng xem lại kịch bản của ứng dụng mà chúng ta sẽ xây dựng.

Kịch bản của ứng dụng chúng ta sắp xây dựng

Tạo Mới Project

Giờ thì bạn hãy tạo mới một project, rồi đặt tên NavigationSample, hoặc bất cứ tên nào mà bạn thích nhé. Project này sẽ được viết bằng ngôn ngữ Kotlin.

Tạo mới NavigationSample project
Tạo mới NavigationSample project

Khai Báo Thư Viện

Đầu tiên nhất, trước khi làm việc gì, chúng ta phải khai báo sử dụng thư viện Navigation vào build.gradle của module. Việc làm này sẽ giúp cho hệ thống down về các thư viện cần thiết cho chúng ta có thể đến các bước tiếp theo.

//...
 
dependencies {
 
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
 
// Navigation
implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.3.5'
 
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}

Xây Dựng Navigation Graph

Bạn đã được làm quen với Navigation Graph ở phần trước rồi, phần này mình không nói lại khái niệm nữa mà tiến hành xây dựng thể loại resource mới này.

Tạo Mới Một Resource Navigation Graph

Trong cửa sổ Project của Android Studio, bạn hãy click chuột phải vào thư mục res/ của project rồi chọn New > Android Resource File.

Tạo mới Android Resource File
Tạo mới Android Resource File

Sau đó, một cửa sổ khai báo hiện ra. Bạn hãy định nghĩa một cái tên cho resource Navigation Graph này. Mình đặt là login_nav_graph (vì Navigation Graph này định nghĩa luồng Login, bạn có thể sẽ phải cần nhiều Navigation Graph khác cho từng chức năng khác bên trong ứng dụng của bạn sau này, nên chúng ta sẽ tập đặt tên có phân biệt chức năng ngay bây giờ luôn, bằng cách thêm tiền tố login_ vào resource). Ở mục Resource type trong cửa sổ này, bạn hãy tìm và chọn Navigation, đây là kiểu resource chúng ta muốn tạo. Sau đó hãy để mọi thứ khác như mặc định rồi nhấn OK.

Khai báo cho resource Navigation Graph
Khai báo cho resource Navigation Graph

Khi này bạn sẽ thấy một thư mục mới, và một file XML mới được thêm vào project của chúng ta.

Resource Navigation mới được thêm vào project
Resource Navigation mới được thêm vào project

Làm Quen Với Resource Navigation Graph

Sau khi hoàn thành bước tạo mới resource Navigation Graph trên đây, bạn cũng sẽ thấy Android Studio mở resource này lên editor. Nếu không thấy bạn có thể kích đúp lên file login_nav_graph.xml để mở nó. Hiện tại editor đang hiển thị nội dung file này của chúng ta với một giao diện trống trơn. Mình sẽ mượn hình ảnh của Google để giới thiệu về việc thao tác trên editor đối với resource mới này như sau.

Cái này gọi là Navigation Editor
Cái này gọi là Navigation Editor

Editor dùng để hiển thị resource Navigation Graph chúng ta gọi với cái tên ngắn gọn hơn là Navigation EditorNavigation Editor trên đây có đánh số, chia nó làm 3 phần riêng biệt giúp chúng ta dễ dàng thao tác hơn.

  1. Destination panel: là một thanh chứ đựng các destinationDestination là gì thì mình có nói sơ qua ở phần trước rồi. Các destination bên trong Destination panel được chia ra thành hai nhóm, HOST và GRAPH. Giao diện của mỗi nhóm này sẽ hơi khác với Android Studio của chúng ta một xíu, nhưng nó vẫn là một. Còn ý nghĩa của chúng là gì thì lát nữa trong quá trình tạo các destination chúng ta sẽ cùng tìm hiểu nhé. Nhưng dù cho các destination được chia nhóm như thế nào đi chăng nữa thì chúng cũng sẽ được vẽ vào bên trong vùng thứ 2 được nói đến tiếp theo đây.
  2. Graph Editor: đây có thể nói là tinh túy của Navigation Graph, một nơi giúp vẽ ra sự tương quan giữa các thành phần bên trong Navigation Graph này. Nên nhớ là editor dành cho resource này cũng có 3 tab giúp chúng ta dễ dàng chuyển đổi giữa việc xem cấu trúc của file theo dạng Code, dạng Design hay dạng hỗn hợp Split nhé https://i0.wp.com/yellowcodebooks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-07-at-08.34.13.png?resize=300%2C51&ssl=1 300w" data-lazy-loaded="1" sizes="(max-width: 390px) 100vw, 390px" loading="eager" style="box-sizing: border-box; height: auto; max-width: 100%; border-style: none; margin: 0px; width: 150px;">.
  3. Attributes: nơi chứa đựng các tham số định nghĩa cho từng thành phần được chọn bên trong Graph Editor. Cụ thể các tham số này sẽ được mình nói rõ hơn khi tạo các thành phần tương ứng.

Thêm NavHost Vào Activity

Cho đến giờ phút này thì Navigation Editor của chúng ta chưa có thể hiện thông tin gì cả. Bởi vì login_nav_graph cũng chỉ mới là một resource mới được tạo ra, chưa có nơi nào “nhận chứa chấp” em nó, và em nó cũng chưa nhận hiển thị gì lên cả.

Thêm NavHostFragment Vào MainActivity

Bước tiếp theo này chúng ta sẽ tìm “nơi chứa chấp” em resource login_nav_graph. Nơi chứa chấp này đã được mình giới thiệu ở phần trước với cái tên NavHost. Không nói nhiều vì phần trước nói hết rồi, tóm lại thì MainActivity chính là nơi chứa chấp em login_nav_graph. Vậy chúng ta hãy mở activity_main.xml lên, đảm bảo editor đang hiện ở tab Design. Bạn hãy xóa TextView có sẵn khi chúng ta tạo mới project này đi. Đảm bảo giao diện của MainActivity khi này đang chỉ có một ConstraintLayout thôi nhé.

Sau đó hãy tìm trên thanh Palette, vào nhóm Containers, bạn sẽ thấy một thành phần UI có tên là NavHostFragmentNavHostFragment chính là “nơi chứa chấp” Navigation Graph mà chúng ta muốn tìm.

Tất cả những thao tác nãy giờ được biểu diễn bằng một hình ảnh như dưới đây.

Đi tìm NavHostFragment chuẩn bị đưa vào MainActivity
Đi tìm NavHostFragment chuẩn bị đưa vào MainActivity

Khi tìm thấy NavHostFragment rồi thì bạn hãy tiến hành kéo thả thành phần này vào trong màn hình thiết kế (nếu có lỡ ngu ngơ chỗ này quá thì bạn có thể xem thêm bên phần xây dựng giao diện cho ConstraintLayout nhé).

Ngay khi vừa kéo thả thành phần này vào giao diện thiết kế, bạn sẽ thấy một cửa sổ lập tức hiện ra, hỏi bạn rằng NavHost này sẽ chứa Navigation Graph nào. Dĩ nhiên là login_nav_graph mà chúng ta đã tạo trên kia rồi. Hãy chọn nó và nhấn OK nhé.

Cửa sổ hỏi chúng ta Navigation Graph nào sẽ được thêm vào NavHost này
Cửa sổ hỏi chúng ta Navigation Graph nào sẽ được thêm vào NavHost này

Canh Chỉnh NavHostFragment

Sau đó, bởi vì chúng ta cần MainActivity hiển thị từng Fragment con của nó toàn màn hình, nên chúng ta cũng sẽ điều chỉnh NavHostFragment sao cho chúng cũng sẽ chiếm hết không gian dài rộng của MainActivity (điều này cũng gợi ra cho các bạn một ý tưởng, rằng NavHostFragment thực ra cũng giống như các layout khác mà thôi, bạn có thể thoải mái xếp đặt chúng ở bất cứ vị trí nào và bất cứ kích thước nào trên màn hình tùy thích). Với bài học của chúng ta hôm nay thì NavHostFragment được biểu diễn như sau.

Canh chỉnh lại kích thước của NavHost
Canh chỉnh lại kích thước của NavHost

Hiểu Hơn Về NavHostFragment

Nếu bạn tò mò muốn biết nhiều hơn về NavHostFragment. Bạn có thể xem các thuộc tính được liệt kê ở editor trên đây. Nhưng mình lại muốn bạn nắm nhiều hơn thông qua code XML của MainActivity. Bạn hãy chuyển sang tab Code của activity_main nhé.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
 
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainerView"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="0dp"
android:layout_height="0dp"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:navGraph="@navigation/login_nav_graph" />
</androidx.constraintlayout.widget.ConstraintLayout>

Cũng không có gì cao siêu, nhưng mình cũng sẽ nói rõ ra cho các bạn nắm cách mà MainActivity chứa đựng một NavHostFragment như thế nào.

  • androidx.fragment.app.FragmentContainerView: đây là một layout mới, được thiết kế ra để chứa đựng các Fragment.
  • android:name: định nghĩa rằng layout trên đây đang chứa thành phần NavHostFragment. Nó giống như khi bạn khai báo fragment tĩnh vào trong một layout như bên bài viết về Fragment vậy.
  • android:navGraph: thuộc tính giúp hệ thống biết NavHostFragment này chứa đựng login_nav_graph.
  • app:defaultNavHost: thuộc tính này giúp hệ thống chặn nút system back cho chúng ta. System back là nút back của thiết bị. Bởi vì NavHostFragment của chúng ta sẽ chứa đựng rất nhiều destination trong đó, cũng giống như một trang Web chứa nhiều page con vậy, một khi người dùng đã đi qua các page, hay các destination, rồi nhấn nút back của thiết bị, chúng ta mong muốn các destination trước đó sẽ được hiển thị trở lại. Khi đó cờ của thuộc tính này là true sẽ giúp ứng dụng của chúng ta phản ứng theo kịch bản như vậy. Còn nếu bạn khai báo là false, khi nhấn back của thiết bị ở bất cứ destination con nào, cả một NavHost cũng sẽ trở về trạng thái trước đó (có thể khiến ứng dụng bị đóng luôn).

Thêm Destination Vào Navigation Graph

Lúc bấy giờ nếu quay lại tab Design của Navigation Editor đang hiển thị login_nav_graph. Bạn sẽ thấy thông tin trong Destination panel thể hiện rằng activity_main chính là Host (là nơi chứa chấp) login_nav_graph này.

Navigation Graph đã có một chút thông tin
Navigation Graph đã có một chút thông tin

Bước tiếp theo đây chúng ta sẽ xây dựng một kịch bản các destination ngay trên Navigation Graph này. Nếu như hướng dẫn trên editor, bạn hãy tìm và click lên icon có hình . Bạn sẽ thấy một popup sau xuất hiện.

Chức năng thêm vào một destinationhttps://i0.wp.com/yellowcodebooks.com/wp-content/uploads/2021/06/Screen-Shot-2021-06-08-at-20.01.45.png?resize=226%2C300&ssl=1 226w" data-lazy-loaded="1" sizes="(max-width: 698px) 100vw, 698px" loading="eager" style="box-sizing: border-box; height: auto; max-width: 100%; border-style: none; margin: 0px; vertical-align: bottom;">
Chức năng thêm vào một destination

Đây là nơi mà chúng ta lựa chọn các destination nào sẽ được thêm vào. Và vì do chúng ta không có bất kỳ một Fragment hay destination nào sẵn có trong project hết nên popup này mới trống trơn như vậy. Bạn có thể chọn Create new destination để được dẫn đến các chọn lựa tạo một destination mong muốn. Hoặc bạn có thể chọn placeholder để tạo trước một destination giữ chỗ, rồi sau đó sẽ khai báo destination cụ thể cho placeholder này sau. Nhưng với mình thì mình nghĩ bạn nên đóng popup này lại, chúng ta chỉ làm quen với nó thôi. Chúng ta sẽ lần lượt tạo các Fragment cần thiết, rồi sẽ quay lại popup này, khi đó mọi chuyện sẽ dễ dàng hơn rất nhiều nhé.

Tạo Các Fragment

Như đã nói, bước này chúng ta sẽ tạo các Fragment. Theo kịch bản chúng ta sẽ cần đến HomeFragmentProfileFragmentSignInFragmentSignUpFragment.

Để đỡ dài dòng, bạn hãy tham khảo link này cho việc tạo mới một Fragment, dựa vào đó bạn hãy tạo một Blank Fragment, tức là một Fragment gọn nhất có thể nhé. Đây là màn hình khi mình tạo một HomeFragment.

Tạo mới HomeFragment
Tạo mới HomeFragment

Dù cho source code của HomeFragment có rườm rà khi được tạo mới như thế nào, chúng ta cũng hãy làm cho nó thật gọn như sau.

class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
}

Giao diện cho HomeFragment thì cũng nên xây dựng ở mức vừa đủ với 1 Button như sau. Lưu ý là các bạn có thể thoải mái xây dựng giao diện theo cách các bạn biết, không nhất thiết phải là ConstraintLayout như mình. Chúng ta đang nói về Navigation chứ không nói sâu về việc tạo giao diện nhé.

Giao diện cho HomeFragment
Giao diện cho HomeFragment

Tương tự, bạn hãy tạo ProfileFragment và giao diện của nó. Với một TextView chào hỏi và một Button logout.

Giao diện cho ProfileFrgament
Giao diện cho ProfileFrgament

SignInFragment và giao diện của nó. Các EditText dùng cho việc nhập liệu username, password. Một Button sign in và một Button sign up.

Giao diện cho SignInFragment
Giao diện cho SignInFragment

Và cuối cùng là SignUpFragment và giao diện của nó. EditText dùng để khai báo username và password, và Button để tạo tài khoản.

Giao diện cho SignUpFragment
Giao diện cho SignUpFragment

Hi vọng các bạn đừng nản chí. Vì dù sao chúng ta đang xây dựng cả một ứng dụng hoàn chỉnh cơ mà. Đây có thể được xem là prototype, một bản dựng ban đầu của ứng dụng.

Nhưng vì sao mình muốn các bạn xây dựng trước giao diện cho các Fragment này. Đơn giản vì Navigation Editor rất tuyệt, nó không những có thể giúp hiển thị một bản đồ các destination, mà còn hiển thị trước giao diện thiết kế của destination đó nữa. Một lát bạn sẽ thấy sự hiệu quả từ bước này của chúng ta.

Nếu bạn có gặp khó khăn gì trong việc xây dựng Fragment và giao diện của chúng ở bước này. Bạn hoàn toàn có thể xem link đến source code trên Github mình để ở cuối bài đấy nhé.

Thêm Các Fragment Vào Navigation Graph

Đến bây giờ thì chúng ta hoàn toàn có thể thêm các Fragment này vào Navigation Graph, chúng sẽ là các destination. Bạn hãy mở lại login_nav_graph.

Giờ thì bạn hãy quay lại nhấn vào icon . Bạn đã thấy danh sách các Fragment nay đã được liệt kê lên chưa nào, có cả tên file giao diện, kèm với hình thiết kế giao diện của Fragment đó nữa. Thật tuyệt.

Thêm vào một destination nay đã có đầy đủ các Fragment
Thêm vào một destination nay đã có đầy đủ các Fragment

Ngoại trừ placeholder ra, bạn cứ lần lượt chọn từng Fragment trong danh sách trên, nó sẽ thêm vào trong Navigation Graph cho bạn. Bạn hãy thoải mái sắp xếp lại vị trí bằng cách kéo chuột sao cho chúng nhìn khá là đẹp mắt như sau.

Navigation Graph đã có đầy đủ destination
Navigation Graph đã có đầy đủ destination

Tuy chúng ta có bao nhiêu đây Fragment thôi, khá dễ quản lý, nhưng nếu tương lai ứng dụng của chúng ta có nhiều Fragment và vô tình chúng ta thêm lộn một Fragment nào đó vào Navigation Graph này, thì không sao, bạn cứ click chuột phải vào bất kỳ destination nào bên trong Navigation Graph và chọn Delete là xong. Điều này sẽ giúp chúng ta gỡ destination ra khỏi Navigation Graph thôi, không xóa luôn file giao diện của Fragment đâu bạn nhé.

Thêm một điều nữa mình muốn các bạn để ý trong Navigation Graph này. Đó là luôn có một destination sẽ được đánh dấu với biểu tượng hình ngôi nhà, chỗ tiêu đề của nó. Mặc định destination được thêm vào đầu tiên sẽ được đánh dấu với biểu tượng này. Trong trường hợp này chính là homeFragment.

homeFragment được đánh dấu có hình ngôi nhà
homeFragment được đánh dấu có hình ngôi nhà

Bạn hoàn toàn có thể chỉ định destination khác sẽ là ngôi nhà, bằng cách chọn click một destination trong Navigation Editor rồi nhấn vào icon  ở phía trên editor này. Việc đánh dấu với icon này trên một destination, là nhằm chỉ định rằng đây chính là start destination. Một start destination sẽ là destination đầu tiên được nhìn thấy khi ứng dụng của chúng ta được mở lên, và cũng sẽ là destination cuối cùng mà người dùng sẽ nhìn thấy trước khi ứng dụng kết thúc (khi người dùng nhấn lần lượt các nút back).

Tìm Hiểu Thuộc Tính Của Destination Bên Trong Navigation Graph

Cũng với login_nav_graph đang mở, và tab Design của editor này đang chọn. Bạn hãy nhấn vào bất kỳ một destination nào, bạn sẽ thấy một số thuộc tính của nó bên khung Attributes.

Thuộc tính của homeFragment
Thuộc tính của homeFragment

Hiện tại ở bước này bạn chỉ mới quan tâm vài thuộc tính sau của từng destination.

  • id: hiển nhiên là id của destination rồi. Khi đến bước di chuyển qua lại giữa các destination, bạn sẽ thấy id này được sử dụng như thế nào nhé.
  • label: là tên của destination. Tên này được dùng như với trường hợp hiển thị tên của màn hình lên Toolbar chẳng hạn. Chúng ta sẽ tìm hiểu vấn đề này ở bài sau, còn bây giờ bạn cứ để như mặc định nhé.
  • name: chính là Fragment mà destination này đang đại diện cho.

Gờ thì bạn hãy chuyển sang tab Code của editor. Bạn cũng sẽ thấy các destination khi này thực chất chính là các Fragment được khai báo bên trong thành phần cha navigation.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/login_nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/profileFragment"
android:name="com.yellowcode.navigationsample.ProfileFragment"
android:label="fragment_profile"
tools:layout="@layout/fragment_profile" />
<fragment
android:id="@+id/homeFragment"
android:name="com.yellowcode.navigationsample.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/signInFragment"
android:name="com.yellowcode.navigationsample.SignInFragment"
android:label="fragment_sign_in"
tools:layout="@layout/fragment_sign_in" />
<fragment
android:id="@+id/signUpFragment"
android:name="com.yellowcode.navigationsample.SignUpFragment"
android:label="fragment_sign_up"
tools:layout="@layout/fragment_sign_up" />
</navigation>
  • app:startDestination: thuộc tính này nằm ở navigation, giúp hệ thống biết được destination nào chính là start destination. Chính là destination có icon hình ngôi nhà mà bạn đã thấy bên tab Design trên kia.
  • tools:layout: sẽ chỉ định layout của Fragment được dùng hiển thị lên ở dạng xem trước bên tab Design. Nếu bạn thay đổi layout nào khác cho bất kỳ destination nào ở bên đây, bạn sẽ thấy sự khác biệt bên tab Design. Tuy nhiên mình không khuyến khích bạn sửa nhé, chỉ vọc chơi nếu thích rồi trả lại như cũ thôi.
  • Các thuộc tính còn lại như idnamelabel thì mình dã nói ở trên rồi.

Ứng dụng đã dần thành hình. Và bạn cũng đã hiểu hơn nhiều về việc sử dụng Navigation này rồi. Nếu bây giờ bạn thực thi ứng dụng, thì chỉ màn hình nào được đánh dấu là start destination mới được nhìn thấy, và hoàn toàn chưa có sự tương tác gì cho ứng dụng cả. Chúng ta sẽ cùng xây dựng tiếp ứng dụng này ở phần tiếp theo nhé.

Thông Thạo Jetpack – Phần 5 – Navigation (Tập 3)

Mình xin phép điểm lại các phần nhỏ liên quan đến Navigation trong chủ đề Jetpack để bạn dễ tham khảo.

  • Kiến thức sơ bộ về Navigation được nói đến ở Tập 1.
  • Xây dựng Navigation Graph và NavHost ở Tập 2.

Sang tập tiếp theo này chúng ta sẽ dùng thành phần quan trọng còn lại của Navigation, đó chính là NavController để tạo sự di chuyển giữa các destination bên trong NavHost mà chúng ta đã xây dựng. Ngoài ra chúng ta còn xem xét việc xây dựng hiệu ứng chuyển đổi các màn hình sao cho ứng dụng sinh động và mượt mà hơn. Ở cuối cùng của bài viết là các cách mà chúng ta có thể truyền dữ liệu qua lại giữa các destination.

Nào bài viết sẽ dài, chúng ta bắt đầu thôi.

Sử Dụng NavController

NavController là gì và tại sao phải dùng chúng? Câu trả lời cho câu hỏi là gì thì mình cũng đã nói ở Tập 1 của Navigation rồi. Nhưng mình đoán có nhiều bạn cũng đang thắc mắc công dụng của thành phần này.

Bạn nhớ lại xem, nếu bạn xây dựng một ứng dụng với nhiều Activity, một khi bạn muốn từ một Activity này chuyển sang hiển thị Activity khác, thì từ Activity gốc bạn gọi startActivity() đúng không nào. Hoặc trong một Activity của bạn có hiển thị Fragment một cách thủ công (không phải bằng Navigation), bạn muốn chuyển đổi hiển thị từ một Fragment này sang một Fragment khác bạn phải “triệu hồi” ra một FragmentManager thông qua lời gọi getSupportFragmentManager() ở Activity hay getChildFragmentManager() ở Fragment, để rồi từ FragmentManager này chúng ta mới thay thế hay thêm mới một Fragment vào được.

Và bây giờ, chúng ta bước sang sử dụng Navigation, bạn có một cách mới gọn gàng và đa năng hơn, đó là NavController. Công dụng của NavController cũng bao gồm cả hai công dụng chính mình nêu ra trên đây, di chuyển từ một destination này đến một destination khác (là một Activity hay một Fragment khác).

Gọi Ra Một NavController

Mình dùng “triệu hồi” nhé, cho nó vui và dễ nhớ, vì NavController có sẵn bên trong NavHostFragment. Các câu lệnh triệu hồi như sau.

  • Fragment.findNavController()
  • View.findNavController()
  • Activity.findNavController(viewId: Int)

Trên đây là 3 cách triệu hồi ở 3 ngữ cảnh khác nhau, lần lượt ở Fragment, View hay Activity. Google cũng có cảnh báo chúng ta rằng nên gọi findNavController() cho đúng chỗ, vì NavController luôn đi kèm với NavHosFragment, nên đảm bảo nơi gọi đến hàm này phải là một nơi nằm trong hay có liên quan đến một NavController.

Dùng NavController Để Di Chuyển Đến Destination

Lúc nãy mình có nói cách dùng với NavController là đa năng, vâng đa năng chỗ này đây, có nhiều cách để bạn di chuyển từ một destination này đến destination khác bên trong NavHost sử dụng NavController.

Chúng ta hãy bắt đầu xây dựng việc click vào button View Profile ở HomeFragment sẽ mở ra ProfileFragment qua từng cách dưới đây.

À trước hết chúng ta nên xây dựng sẵn sự kiện click trên button View Profile đã nhé.

class HomeFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
 
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
 
view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
// Will use NavController here
}
}
}

Cách 1 – Di Chuyển Trực Tiếp Trên Navigation Graph

NavController có một phương thức navigate() cho phép chúng ta truyền vào một id của destination để di chuyển. Dùng như sau.

view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
findNavController().navigate(R.id.profileFragment)
}

Với việc thêm 1 dòng trên vào sự kiện click của button thôi là bạn đã có thể mở ProfileFragment từ HomeFragment rồi, bạn thử thực thi ứng dụng rồi trải nghiệm nhé.

Cách 2 – Di Chuyển Bằng Action

Để làm được cách này, chúng ta hãy xây dựng action. Ở Tập 1 các bạn đã được giới thiệu action giúp cho biết trực quan đường đi từ một destination này đến destination khác. Thì giờ đây công dụng của action đã được làm rõ hơn thông qua việc thực thi việc di chuyển.

Để tạo một action, trước hết bạn hãy mở lại login_nav_graph lên, đảm bảo đang xem ở tab Design. Click chọn vào HomeFragment, bạn sẽ thấy một khung xanh bao quanh destination này kèm với một hình tròn ở bên phải khung xanh này.

Chọn destination homeFragment
Chọn destination homeFragment

Nhấn giữ chuột và kéo từ chấm tròn trên homeFragment này vào profileFragment. Khi thả chuột ra bạn sẽ tạo được một action hình mũi tên như thế này.

Một action vừa được tạo ra
Một action vừa được tạo ra

Nếu vẫn nhấn chọn vào action vừa mới tạo (action sẽ chuyển sang màu xanh khi được chọn). Nhìn vào khung Attributes bạn sẽ thấy một số thuộc tính quan trọng của nó.

Thuộc tính của action
Thuộc tính của action
  • id: chính là id của action. Bạn có thể thấy là nó khá dài, bạn hoàn toàn có thể sửa id này cho ngắn lại, nhưng mình thấy để như vậy cũng không sao, vì nó khá là rõ nghĩa.
  • destination: chính là id của destination mà action này sẽ dẫn đến.

Giờ chúng ta hãy chuyển sang tab Code để xem diễn biến bên này ra sao nhé.

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/login_nav_graph"
app:startDestination="@id/homeFragment">
<fragment
android:id="@+id/profileFragment"
android:name="com.yellowcode.navigationsample.ProfileFragment"
android:label="fragment_profile"
tools:layout="@layout/fragment_profile" />
<fragment
android:id="@+id/homeFragment"
android:name="com.yellowcode.navigationsample.HomeFragment"
android:label="fragment_home"
tools:layout="@layout/fragment_home" >
<action
android:id="@+id/action_homeFragment_to_profileFragment"
app:destination="@id/profileFragment" />
</fragment>
<fragment
... />
</navigation>

Bạn xem, nếu như ở bài trước sau khi thêm các destination vào file giao diện này, chúng ta chỉ mới có các tag fragment. Nay trong fragment mà id là homeFragment có thêm một tag con action với 2 thuộc tính id và destination mà chúng ta vừa mới làm quen.

Trên đây là cách tổ chức về phía giao diện và code XML của file login_nav_graph.

Quay trở lại với cách dùng NavController thứ 2. Bạn hãy thay thế id của destination ở cách 1 bằng id của action, và chạy lại chương trình để xem kết quả nhé.

view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_profileFragment)
}

Do action tự nó biết đích đến là ở đâu, nên việc gọi navigate() và truyền vào một id của action sẽ giúp cho hệ thống biết destination nào cần mở ra lúc này.

Bạn đã nắm rõ hai cách di chuyển chưa nào. Chúng ta cùng đến phần tiếp theo để thấy với hai cách trên, cách nào sẽ hay hơn nhé.

Tạo Hiệu Ứng Di Chuyển Qua Lại Giữa Các Destination

Bạn có thấy dù dùng cách nào để di chuyển qua lại giữa các destination trên đây, thì việc chuyển màn hình xảy ra rất đột ngột đúng không. Triết lý Material Design không thích điều này. Một giao diện đẹp là một giao diện có tính chuyển động uyển chuyển. Vậy chúng ta hãy thêm vào chút animation cho nó.

Trước hết bạn hãy tạo một resource /anim mới trong project bằng cách click chuội phải vào thư mục /res rồi chọn New > Android Resource Directory, chọn Resource type là anim ở cửa sổ sau.

Tạo mới thư mục anim
Tạo mới thư mục anim

Sau đó bạn hãy vào link này để xem và xây dựng lại từng anim vào trong thư mục /anim vừa mới tạo nhé. Tên file resource đã nói lên loại chuyển động hoặc hiệu ứng anim rồi nên mình không nói gì thêm.

Tạo các resource anim
Tạo các resource anim

Đến đây thì tùy xem bạn đã áp dụng việc di chuyển destination theo cách nào trên đây mà sẽ có các cách áp dụng animation khác nhau.

Nếu bạn dùng cách Di Chuyển Trực Tiếp Trên Navigation Graph, thì bạn phải xây dựng một NavOptions. Chúng ta sẽ tận dụng kiến trúc Builder của lớp này để override lại một số anim mà chúng ta cần thay đổi. Và cũng cảm ơn Kotlin KTX dành cho Navigation đã giúp chúng ta khai báo NavOptions đẹp đẽ hơn, rồi truyền NavOptions này vào trong navigate() như sau.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
 
val options = navOptions {
anim {
enter = R.anim.slide_in_right
exit = R.anim.slide_out_left
popEnter = R.anim.slide_in_left
popExit = R.anim.slide_out_right
}
}
view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
findNavController().navigate(R.id.profileFragment, null, options)
}
}

Còn nếu dùng cách Di Chuyển Bằng Action, thì còn tuyệt vời hơn, chúng ta không cần phải viết code Kotlin dài dòng, chỉ cần bạn mở login_nav_graph lên, click chọn vào action (mũi tên bạn vừa tạo khi nãy), rồi gõ tên anim vào khung Attributes ở các cột tương ứng. Như sau.

Chỉ cần khai báo anim bằng thuộc tính cho action
Chỉ cần khai báo anim bằng thuộc tính cho action

Bạn cũng có thể qua tab Code để xem cách mà hệ thống khai báo các anim này vào action ở XML như thế nào nhé.

Lưu ý là code ở HomeFragment, sự kiện click lên nút View Profile cũng vẫn như trên, chỉ có vỏn vẹn thế này thôi.

view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
findNavController().navigate(R.id.action_homeFragment_to_profileFragment)
}

Dù code bằng cách nào thì giờ đây bạn đã có thể trải nghiệm bằng cách thực thi ứng dụng và thử nhấn vào nút View Profile được rồi.

Truyền Data Qua Lại Giữa Các Destination

Giờ thì chúng ta sẽ nói đến cách truyền dữ liệu qua destination. Tuy nhiên mình cũng nói trước rằng dữ liệu bạn cần phải truyền lúc này thường là các dữ liệu nhỏ, như id của user, hay user name, hay một tham số Boolean gì đó để làm cờ bật tắt cho tính năng nào đó,… Tóm lại là các kiểu dữ liệu nhỏ xíu thôi, thì dùng cách dưới đây. Còn nếu bạn muốn truyền cả một object hay các dữ liệu lớn hơn, thì dùng ViewModel cho nó khỏe bạn nhé.

Để bắt đầu việc truyền dữ liệu “nhỏ”. Cụ thể ở ví dụ ứng dụng của chúng ta thì khi bạn đi từ màn hình HomeFragment sang ProfileFragment, bạn sẽ thấy một câu chào hỏi rất là lạ.

Câu welcome chưa đẹp
Câu welcome chưa đẹp

Sở dĩ “Welcome” chưa biết ai để chào hỏi là vì chúng ta chưa truyền gì từ HomeFragment qua đây. Kịch bản mong muốn là sau khi login thành công, HomeFragment sẽ biết được tên người đăng nhập và truyền tên này qua cho ProfileFragment hiển thị. Mặc dù chúng ta chưa xây dựng thành công luồng đăng nhập, nhưng có thể tạm xây dựng đầy đủ việc truyền một cái tên qua đây để kiểm thử.

Đến đây thì cũng có 2 cách để chúng ta truyền dữ liệu này.

Cách 1 – Truyền Dữ Liệu Theo Bundle

Đây có thể nói là cách “truyền thống”, khi mà nếu bạn xây dựng Activity hay Fragment theo cách cũ đều đã có dùng từ rất lâu. Nay thì phương thức navigation() cũng hỗ trợ chúng ta điều này.

Và cũng cảm ơn Android KTX đã cho chúng ta một đoạn code khá dễ hiểu sau khi tạo một Bundle như sau. Do chúng ta muốn truyền giá trị tên qua destination, nên trường hợp này chúng ta đặt tên cho key là “name”. Sau khi tạo xong Bundle chúng ta sẽ truyền nó thông qua phương thức navigation().

view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
val bundle = bundleOf(
"name" to "My Name",
)
findNavController().navigate(R.id.action_homeFragment_to_profileFragment, bundle)
}

Ở ProfileFragment, chúng ta chỉ cần gọi getArguments() để lấy ra giá trị bên trong Bundle này.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
 
val name = arguments?.getString("name")
view.findViewById<TextView>(R.id.tvWelcome)?.text = getString(R.string.welcome, "$name")
}

Xong bước này bạn có thể chạy lại ứng dụng và xem ProfileFragment đã hiển thị đúng tên được truyền qua từ HomeFragment chưa nhé.

Câu welcome đã đẹp
Câu welcome đã đẹp

Cách 2 – Sử Dụng Safe Args

Safe Args là một cách thức truyền dữ liệu mới dùng trong Navigaton này. Sở dĩ gọi là Safe Args là vì đây là một kiểu truyền dữ liệu được đảm bảo type-safety, tức là an toàn về kiểu dữ liệu.

Nếu như kiểu truyền thống Bundle trên kia buộc chúng ta phải nhớ sao cho khớp giữa việc đưa dữ liệu vào Bundle và lấy dữ liệu ra khỏi Bundle. Như ví dụ với key là “name” trên kia, thì chúng ta phải nhớ nò là kiểu String. Trong trường hợp chúng ta có ít giá trị truyền qua thì không sao, nhưng nếu một ứng dụng có quá nhiều giá trị, việc vô tình tạo ra sự không đồng nhất về giá trị giữa truyền và nhận Bundle là chuyện có thể xảy ra. Vậy hãy xem Safe Args giúp chúng ta thế nào nhé.

Định Nghĩa Argument

Safe Args là viết tắt của từ Safe ArgumentsArguments sẽ thay thế Bundle (về mặt tên gọi thôi chứ thực ra chúng là một). Việc thay thế như thế nào thì trước hết chúng ta hãy cùng tạo một argument cho giá trị name nào.

Trước hết bạn phải đảm bảo login_nav_graph đang được mở với tab Design. Nhấn chọn vào profileFragment. Nhìn sang khung Attributes, tìm đến phần Arguments, xổ thành phần này ra, sau đó nhấn vào dấu + để bắt đầu thêm một argument.

Thêm một argument vào profileFragment
Thêm một argument vào profileFragment

Một popup tiếp theo sẽ xuất hiện cho chúng ta khai báo thông tin của argument này. Vì chúng ta muốn chuyển qua profileFragment một giá trị tên, nên chúng ta đặt là nameArg, kiểu String. Như sau.

Khai báo argument
Khai báo argument

Lần này chúng ta đặt tên là nameArg để cho khác với name của Bundle trên kia, để dễ phân biệt và so sánh.

Nếu bạn muốn hiểu rõ hơn về argument này, như các kiểu dữ liệu mà argument hỗ trợ thì đọc thêm ở đây, hay cách override lại một argument cụ thể với từng action thì đọc thêm ở đây.

Bạn nên nhớ là dữ liệu cần truyền vào cho destination nào thì định nghĩa argument ở destination đó nhé. Không cần biết destination đó nhận dữ liệu từ đâu, hễ destination cần dữ liệu nào, argument sẽ khai báo ở destination đó.

Khai Báo Thư Viện

Cơ mà, để dùng Safe Args thì việc cần thiết tiếp theo đó là chúng ta cần khai báo các thư viện. Bạn hãy thêm classpath sau vào file build.gradle của project.

buildscript {
ext.kotlin_version = "1.5.10"
ext.nav_version = "2.3.5"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
}
}

Trong file build.gradle của module thì chúng ta phải khai báo thêm plugin như mình đã tô sáng như sau.

plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
}

Sau khi khai báo các bước trên đây, hệ thống sẽ căn cứ vào project của chúng ta mà sẽ tạo ra một số lớp tương ứng. Mình xin giới thiệu trước các lớp này, một lát sau chúng ta sẽ dùng tới nó.

  • Cứ mỗi một destination mà có một action “dính” tới nó. Tức là một mũi tên từ nó trỏ ra. Sẽ có thêm một lớp mới tạo ra, với tên lớp là sự kết hợp giữa tên lớp của destination cộng với từ “Directions”. Như vậy trong project của chúng ta giờ đây sẽ có thêm một lớp: HomeFragmentDirections, vì chúng ta chỉ mới khai báo một action đi ra từ destination homeFragment thôi. Lớp này sẽ chứa đựng các phương thức chính là các action được định nghĩa bên trong destination này (xem giải nghĩa phương thức này ngay dưới đây).
  • Mỗi một phương thức action trên đây có tham số đầu vào chính là các argument cần truyền qua destination khác. Tên của phương thức này cũng chính là tên action. Như vậy với việc khai báo một action với tên (id) là action_homeFragment_to_profileFragment, chúng ta sẽ có phương thức kèm theo với tên actionHomeFragmentToProfileFragment(), tham số truyền vào chính là một kiểu String có tên nameArg.
  • Cứ mỗi một destination mà có một argument “dính” tới nó. Tức là đây là destination nhận dữ liệu. Sẽ có thêm một lớp mới tạo ra, với tên lớp là sự kết hợp giữa tên lớp của destination cộng với từ “Args”. Như vậy trong project của chúng ta giờ đây sẽ có thêm một lớp: ProfileFragmentArgs.

Với “bộ ba” thông tin mình liệt kê trên đây bạn đã phần nào mường tượng ta Safe Args sử dụng như thế nào chưa. Bạn hãy nhìn vào code khi truyền tham số từ homeFragment như dưới đây sẽ hiểu.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
 
view.findViewById<Button>(R.id.btnViewProfile)?.setOnClickListener {
val action = HomeFragmentDirections.actionHomeFragmentToProfileFragment(nameArg = "My Name")
findNavController().navigate(action)
}
}

Còn ở ProfileFragment, chúng ta dùng như sau (có sự hỗ trợ Android KTX ở dòng khai báo args đầu tiên).

val args: ProfileFragmentArgs by navArgs()
 
// ...
 
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
 
val name = args.nameArg
view.findViewById<TextView>(R.id.tvWelcome)?.text = getString(R.string.welcome, "$name")
}

Uhm tuy sử dụng Safe Args có phần “rườm rà”. Nhưng bù lại nó khá an toàn và trơn tru, vì khi này chúng ta truyền hay nhận argument thông qua việc sử dụng biến, chứ không gọi ra từ key trong Bundle nữa, an toàn là chỗ này.

Bạn hãy thử chạy lại ứng dụng để xem kết quả có mỹ mãn không nhé.

Kết Luận

Tuy bạn thấy cách chúng ta dùng Navigation để dựng nên một ứng dụng cho tới bài hôm nay khá là rối rắm và nhiều bước. Nhưng bạn cũng nên biết, chúng ta đang xây dựng một ứng dụng hoàn chỉnh với khá nhiều màn hình, và Navigation đang giúp chúng ta quản lý tốt từ các màn hình cho đến đường đi giữa chúng, và cả animation lẫn truyền dữ liệu nữa. Nếu không phải là Navigation, chắc chắn chúng ta sẽ đau đầu hơn trong việc giải quyết từng chuyện một một cách thủ công với những thể loại ứng dụng có nhiều màn hình như ví dụ này đây. Tuy nhiên ứng dụng vẫn chưa xong, chúng ta cần một bài viết nữa để có thể sử dụng Navigation ở mức thuần thục hơn.

Cám ơn bạn đã đọc tài liệu của chúng tôi

Công ty cổ phần thương mại Vạn Tín Việt

0936.006.058
0936.006.058