首頁>技術>

這是一個新的系列文章,我們稱之為 "Modern Android Development 技巧",簡稱為 "MAD Skills"。本系列文章致力於幫助開發者們打造更好的現代 Android 開發體驗,敬請關注。

今天為大家釋出本系列文章中的第三篇: 在應用中導航時使用 SafeArgs。如果您想回顧過去釋出的內容,請參考下面連結檢視:

導航元件概覽導航到對話方塊

這篇文章主要介紹 SafeArgs,它屬於導航元件,並且可以在應用不同的目的地 (介面) 之間提供更加便捷的資料傳遞功能。

簡介

當您在應用中導航到不同目的地的時候,可能會需要傳遞資料。為了避免使用全域性物件引用,透過資料傳遞可以實現更好的程式碼封裝結構,這樣不同的 fragment 或者 activity 僅需要分享它們所需的資料即可。

導航元件可以透過 Bundles 傳遞資料,這個機制也可用於 Android 中跨 activity 傳遞資料。

這裡我們也可以使用同樣的方式,為要傳遞的資料建立一個 Bundle,然後在接收側將資料提取出來。

不過導航元件有更好的方法: SafeArgs

SafeArgs 是一個 gradle 外掛,它可以幫助您在 導航圖 中輸入需要傳遞的資料資訊。然後它會生成程式碼幫您解決建立 Bundle 時所需完成的冗長的過程,並且在接收側提取資料。

您也可以直接使用 Bundle,但是我們建議使用 SafeArgs。不僅僅是為了程式碼更簡潔,更多的是它為資料增加了型別安全的保障,使得程式碼具備更好的健壯性。

為了向大家展示 SafeArgs 的效果,我將繼續使用之前在 Dialog Destinations 演示過的 Donut Tracker (甜甜圈追蹤) 應用。如果您希望隨著文章的講解進行同步操作,請下載 應用原始碼,並在 Android Studio 中開啟。

製作甜甜圈的時候到了

我們的 donut tracking 應用又來了:

Donut Track: 就是這個 App,它又來了

僅僅可以新增新的甜甜圈的資訊是不夠的,我還希望可以修改已有甜甜圈的資訊。沒準我拿到一張甜甜圈的照片,或者我希望提升之前的評分。

比較自然的實現方法是點選列表項,然後開啟之前新增甜甜圈時的對話方塊,然後我可以在這裡修改甜甜圈的資訊。但是應用如何知道對話方塊裡顯示哪個甜甜圈的資訊呢?程式碼裡需要傳遞所點選的列表項的資訊。在這裡,它需要將對應表項的 id 從列表所在的 fragment 傳遞到對話方塊所在的 fragment,然後對話方塊可以根據 id 從資料庫裡找到對應甜甜圈的資訊,並且填充到表單裡。

要傳遞 id,這裡我們使用 SafeArgs 來實現。

首先,我需要新增一些依賴庫。

SafeArgs 和導航元件的其它模組不太一樣,它本身並不是一個 API,而是一個可以生成程式碼的 gradle 外掛。所以需要將它設定為 gradle 依賴,並且在構建時使其能夠正確執行來生成所需的程式碼。

首先我在專案級的 build.gradle 檔案的依賴部分中添加了下面的內容:

def nav_version = "2.3.0"// 獲取最新的版本號 https://developer.android.google.cn/jetpack/androidx/releases/navigationclasspath “androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version”

這裡用到了 2.3.0 版本。如果您看到這篇文章的時候較晚,那麼應該會有一個更新的版本供您使用。只要和您所使用的導航元件 API 的其它模組的版本一致就可以了。

然後我添加了下面的內容到 app 模組的 build.gradle 檔案中。它使得在呼叫 SafeArgs 的時候可以生成所需的程式碼。

這是一個您不應該忽略的提示

接下來,在導航圖中建立並傳遞所需的資料。

新增資料的時候會顯示這個對話方塊,這裡可以輸入資料型別、預設值和其它所需的資訊

需要注意的是當我定義資料型別為 Long 的時候,Nullable 的位置會變成灰色。這是因為 Java 程式語言中,基礎資料型別 (Integer、Boolean、Float、Long) 是基於原始資料型別 (int、bool、float、long) 進行封裝的,而原始資料型別不可為空,所以我們在使用基礎資料型別的時候需要保證資料非空。

另外需要注意的是,應用現在使用該對話方塊新增新的元素 (我在上一篇文章 使用導航元件: 對話方塊目的地 | MAD Skills 中已經介紹),同時也使用該對話方塊編輯已有元素。所以並不一定會傳遞元素 id,當用戶建立新元素的時候,程式碼應該能夠判斷當前並無元素資訊需要顯示。所以我在對話方塊中 Default Value (預設值) 的位置輸入了 -1,因為 -1 並不是一個有效的索引值。當代碼導航至該介面並且沒有資料傳遞的時候,-1 就會作為預設值傳遞,接收端的程式碼需要使用該值判斷使用者現在需要建立一個新的甜甜圈。

到這裡,我們執行 build 操作,gradle 就會針對所輸入的資料生成相應的程式碼。這一點很重要,因為不是這樣的話,Android Studio 就無法知道想要呼叫的函式在自動生成程式碼中的位置。

您可以在專案結構樹的 "java(generated)" 分支下找到上面過程中生成的程式碼的執行結果。在子目錄中,可以看到有新檔案生成,它們負責傳遞和獲取資料。

在 DonutListDirections 中,您可以找到 companion 物件,它是用於導航至對話方塊的 API。

companion object {    fun actionDonutListToDonutEntryDialogFragment(        itemId: Long = -1L): NavDirections =        ActionDonutListToDonutEntryDialogFragment(itemId)}

這裡 navigate() 並沒有使用最初的 Action,而是使用了 NavDirections 物件。它既封裝了 action (我們可以透過 action 導航至對話方塊),同時還封裝了早期建立的變數。

需要注意的是上面的 actionDonutListToDonutEntryDialogFragment() 函式需要一個 Long 型別的引數,我們之前建立了相關變數,並且給它賦值為 -1。所以如果我們在呼叫該函式的時候不加引數,該方法會返回一個 NavDirections 物件,並且它的 itemId 為 -1。

在另一個生成的檔案 DonutEntryDialogFragmentArgs 中,您可以看到 fromBundle() 函式包含從目標對話方塊獲取資料的程式碼:

fun fromBundle(bundle: Bundle): DonutEntryDialogFragmentArgs {    // ...    return DonutEntryDialogFragmentArgs(__itemId)}

現在我可以利用生成的程式碼成功傳遞和獲取資料了。首先,我在 DonutEntryDialogFragment 類中編寫程式碼來獲取 itemId 資料,並且確定使用者的意圖是新增一個新的甜甜圈還是編輯一個已有的甜甜圈:

val args: DonutEntryDialogFragmentArgs by navArgs()val editingState =    if (args.itemId > 0) EditingState.EXISTING_DONUT    else EditingState.NEW_DONUT

第一行程式碼用到了一個屬性委託,它由 Navigation 元件庫提供,這樣寫可以簡化從 bundle 獲取資料的過程。透過它可以在 args 變數中直接找到資料所對應的名稱。

如果使用者正在編輯一個已有的甜甜圈資訊,那麼這裡的程式碼會獲取該元素的資訊,並且使用獲取到的資訊填充 UI:

if (editingState == EditingState.EXISTING_DONUT) {    donutEntryViewModel.get(args.itemId).observe(        viewLifecycleOwner,        Observer { donutItem ->            binding.name.setText(donutItem.name)            binding.description.setText(donutItem.description)            binding.ratingBar.rating = donutItem.rating.toFloat()            donut = donutItem        }    )}

需要注意的是這裡的程式碼是從資料庫請求資訊,並且我們希望整個請求過程能夠在 UI 執行緒之外進行。所以程式碼裡會監聽 ViewModel 所提供的 LiveData 物件,並且非同步處理請求,當資料返回時填充檢視。

binding.doneButton.setOnClickListener {    donutEntryViewModel.addData(        donut?.id ?: 0,        binding.name.text.toString(),        binding.description.text.toString(),        binding.ratingBar.rating.toInt()    )    dismiss()}

上面的這些程式碼主要側重於在目的介面裡處理資料,現在我們來看一下如何將資料傳送到目標介面。

binding.fab.setOnClickListener { fabView ->    fabView.findNavController().navigate(DonutListDirections        .actionDonutListToDonutEntryDialogFragment())}

需要注意的是,這段程式碼裡在建立 NavDirections 物件的時候呼叫了無引數的建構函式,所以變數會被預設賦值為 -1 (以表明這是一個新的甜甜圈),這也是我們希望透過點選懸浮操作按鈕所要實現的效果。

另一個途徑是當用戶點選列表中已有元素的時候,會開啟對話方塊。可以透過下面的 lambda 表示式實現,它將在 DonutListAdapter 的構建過程中傳入 (即 onEdit 引數),然後會在每個表項的 onClick 被觸發的時候被呼叫:

donut ->    findNavController().navigate(DonutListDirections        .actionDonutListToDonutEntryDialogFragment(donut.id))

這裡的程式碼和使用者點選懸浮操作按鈕的程式碼相似,只不過這裡將表項的 id 傳了進去,告訴對話方塊它要編輯一個已有的元素。而且和我們之前的程式碼看到的一樣,它會用已有元素的資訊填充對話方塊,並且對該表項所做的修改也會相應更新資料庫裡的對應項。

總結

這就是 SafeArgs 的全部內容。使用起來非常簡單 (比起 Bundle 要簡單很多),因為依賴庫會幫您生成程式碼來簡化資料傳遞,並且保障了資料型別安全。透過這樣的方式,您可以更好地利用資料封裝,在目的地之間僅僅傳遞所需的資料而無需在更大的範圍內暴露資料。

更多資訊

更多關於導航元件的詳情,請檢視 導航元件使用入門文件

DonutTracker 應用的完整程式碼,請檢視 Github 示例:

更多現代 Android 開發技巧 (MAD Skills) 系列內容,請檢視 Android Developers 頻道

10
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • leetcode955_go_刪列造序II