在應(yīng)用中導(dǎo)航時(shí)使用 SafeArgs | MAD Skills

image

這是一個(gè)新的系列文章,我們稱之為 "Modern Android Development 技巧"势篡,簡稱為 "MAD Skills"。本系列文章致力于幫助開發(fā)者們打造更好的現(xiàn)代 Android 開發(fā)體驗(yàn)祟蚀,敬請關(guān)注可霎。

今天為大家發(fā)布本系列文章中的第三篇: 在應(yīng)用中導(dǎo)航時(shí)使用 SafeArgs。如果您想回顧過去發(fā)布的內(nèi)容密强,請參考下面鏈接查看:

這篇文章主要介紹 SafeArgs茅郎,它屬于導(dǎo)航組件,并且可以在應(yīng)用不同的目的地 (界面) 之間提供更加便捷的數(shù)據(jù)傳遞功能或渤。

簡介

當(dāng)您在應(yīng)用中導(dǎo)航到不同目的地的時(shí)候系冗,可能會(huì)需要傳遞數(shù)據(jù)。為了避免使用全局對象引用薪鹦,通過數(shù)據(jù)傳遞可以實(shí)現(xiàn)更好的代碼封裝結(jié)構(gòu)掌敬,這樣不同的 fragment 或者 activity 僅需要分享它們所需的數(shù)據(jù)即可。

導(dǎo)航組件可以通過 Bundles 傳遞數(shù)據(jù)池磁,這個(gè)機(jī)制也可用于 Android 中跨 activity 傳遞數(shù)據(jù)奔害。

這里我們也可以使用同樣的方式,為要傳遞的數(shù)據(jù)創(chuàng)建一個(gè) Bundle地熄,然后在接收側(cè)將數(shù)據(jù)提取出來华临。

不過導(dǎo)航組件有更好的方法: SafeArgs

SafeArgs 是一個(gè) gradle 插件端考,它可以幫助您在 導(dǎo)航圖 中輸入需要傳遞的數(shù)據(jù)信息雅潭。然后它會(huì)生成代碼幫您解決創(chuàng)建 Bundle 時(shí)所需完成的冗長的過程,并且在接收側(cè)提取數(shù)據(jù)却特。

您也可以直接使用 Bundle扶供,但是我們建議使用 SafeArgs。不僅僅是為了代碼更簡潔核偿,更多的是它為數(shù)據(jù)增加了類型安全的保障诚欠,使得代碼具備更好的健壯性。

為了向大家展示 SafeArgs 的效果漾岳,我將繼續(xù)使用之前在 Dialog Destinations 演示過的 Donut Tracker (甜甜圈追蹤) 應(yīng)用轰绵。如果您希望隨著文章的講解進(jìn)行同步操作,請下載 應(yīng)用源碼尼荆,并在 Android Studio 中打開左腔。

制作甜甜圈的時(shí)候到了

我們的 donut tracking 應(yīng)用又來了:

Donut Track: 就是這個(gè) App,它又來了

Donut Tracker 會(huì)顯示甜甜圈的列表捅儒,每個(gè)列表項(xiàng)含有名稱液样、描述和評分信息振亮,這些內(nèi)容有些是我添加的,有些是通過在點(diǎn)擊 懸浮操作按鈕 (FAB) 彈出的對話框中填寫的鞭莽。

點(diǎn)擊懸浮操作按鈕會(huì)彈出對話框填寫新的甜甜圈信息

僅僅可以添加新的甜甜圈的信息是不夠的坊秸,我還希望可以修改已有甜甜圈的信息。沒準(zhǔn)我拿到一張?zhí)鹛鹑Φ恼掌炫蛘呶蚁M嵘暗脑u分褒搔。

比較自然的實(shí)現(xiàn)方法是點(diǎn)擊列表項(xiàng),然后打開之前添加甜甜圈時(shí)的對話框喷面,然后我可以在這里修改甜甜圈的信息星瘾。但是應(yīng)用如何知道對話框里顯示哪個(gè)甜甜圈的信息呢?代碼里需要傳遞所點(diǎn)擊的列表項(xiàng)的信息惧辈。在這里琳状,它需要將對應(yīng)表項(xiàng)的 id 從列表所在的 fragment 傳遞到對話框所在的 fragment,然后對話框可以根據(jù) id 從數(shù)據(jù)庫里找到對應(yīng)甜甜圈的信息盒齿,并且填充到表單里念逞。

要傳遞 id,這里我們使用 SafeArgs 來實(shí)現(xiàn)边翁。

使用 SafeArgs

這里我需要說明一下肮柜,我已經(jīng)完成了全部的代碼,大家可以在 GitHub 的 示例 中找到完整的代碼倒彰。所以接下來我會(huì)給大家講解每個(gè)步驟审洞,并且讓大家看到示例代碼的效果,而不是簡簡單單帶著大家完成代碼待讳。

首先芒澜,我需要添加一些依賴庫。

SafeArgs 和導(dǎo)航組件的其它模塊不太一樣创淡,它本身并不是一個(gè) API痴晦,而是一個(gè)可以生成代碼的 gradle 插件。所以需要將它設(shè)置為 gradle 依賴琳彩,并且在構(gòu)建時(shí)使其能夠正確運(yùn)行來生成所需的代碼誊酌。

首先我在項(xiàng)目級的 build.gradle 文件的依賴部分中添加了下面的內(nèi)容:

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

這里用到了 2.3.0 版本。如果您看到這篇文章的時(shí)候較晚露乏,那么應(yīng)該會(huì)有一個(gè)更新的版本供您使用碧浊。只要和您所使用的導(dǎo)航組件 API 的其它模塊的版本一致就可以了。

然后我添加了下面的內(nèi)容到 app 模塊的 build.gradle 文件中瘟仿。它使得在調(diào)用 SafeArgs 的時(shí)候可以生成所需的代碼箱锐。

apply plugin: "androidx.navigation.safeargs.kotlin"

這里,gradle 提示需要同步劳较,所以我點(diǎn)擊一下 "Sync Now"驹止。

這是一個(gè)您不應(yīng)該忽略的提示

接下來浩聋,在導(dǎo)航圖中創(chuàng)建并傳遞所需的數(shù)據(jù)。

image

需要數(shù)據(jù)的目標(biāo)界面是對話框 donutEntryDialogFragment臊恋,它需要知道所需顯示的對象的信息衣洁。點(diǎn)擊目標(biāo)界面會(huì)在右側(cè)顯示相關(guān)屬性。

點(diǎn)擊目標(biāo)界面會(huì)顯示該界面的屬性列表抖仅,您可以在這里輸入需要傳遞的數(shù)據(jù)

Arguments 窗格點(diǎn)擊 + 可以添加數(shù)據(jù)闸与,會(huì)彈出下面所示的對話框。這里我希望傳遞的是所需顯示的甜甜圈信息岸售,所以數(shù)據(jù)類型設(shè)置為 Long,和數(shù)據(jù)庫里的 id 的數(shù)據(jù)類型一致厂画。

添加數(shù)據(jù)的時(shí)候會(huì)顯示這個(gè)對話框凸丸,這里可以輸入數(shù)據(jù)類型、默認(rèn)值和其它所需的信息

需要注意的是當(dāng)我定義數(shù)據(jù)類型為 Long 的時(shí)候袱院,Nullable 的位置會(huì)變成灰色屎慢。這是因?yàn)?Java 編程語言中,基礎(chǔ)數(shù)據(jù)類型 (Integer忽洛、Boolean腻惠、FloatLong) 是基于原始數(shù)據(jù)類型 (int欲虚、bool集灌、floatlong) 進(jìn)行封裝的复哆,而原始數(shù)據(jù)類型不可為空欣喧,所以我們在使用基礎(chǔ)數(shù)據(jù)類型的時(shí)候需要保證數(shù)據(jù)非空。

另外需要注意的是梯找,應(yīng)用現(xiàn)在使用該對話框添加新的元素 (我在上一篇文章 使用導(dǎo)航組件: 對話框目的地 | MAD Skills 中已經(jīng)介紹)唆阿,同時(shí)也使用該對話框編輯已有元素。所以并不一定會(huì)傳遞元素 id锈锤,當(dāng)用戶創(chuàng)建新元素的時(shí)候驯鳖,代碼應(yīng)該能夠判斷當(dāng)前并無元素信息需要顯示。所以我在對話框中 Default Value (默認(rèn)值) 的位置輸入了 -1久免,因?yàn)?-1 并不是一個(gè)有效的索引值浅辙。當(dāng)代碼導(dǎo)航至該界面并且沒有數(shù)據(jù)傳遞的時(shí)候,-1 就會(huì)作為默認(rèn)值傳遞阎姥,接收端的代碼需要使用該值判斷用戶現(xiàn)在需要?jiǎng)?chuàng)建一個(gè)新的甜甜圈摔握。

到這里,我們執(zhí)行 build 操作丁寄,gradle 就會(huì)針對所輸入的數(shù)據(jù)生成相應(yīng)的代碼氨淌。這一點(diǎn)很重要泊愧,因?yàn)椴皇沁@樣的話,Android Studio 就無法知道想要調(diào)用的函數(shù)在自動(dòng)生成代碼中的位置盛正。

您可以在項(xiàng)目結(jié)構(gòu)樹的 "java(generated)" 分支下找到上面過程中生成的代碼的執(zhí)行結(jié)果删咱。在子目錄中,可以看到有新文件生成豪筝,它們負(fù)責(zé)傳遞和獲取數(shù)據(jù)痰滋。

DonutListDirections 中,您可以找到 companion 對象续崖,它是用于導(dǎo)航至對話框的 API敲街。

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

這里 navigate() 并沒有使用最初的 Action,而是使用了 NavDirections 對象严望。它既封裝了 action (我們可以通過 action 導(dǎo)航至對話框)多艇,同時(shí)還封裝了早期創(chuàng)建的變量。

需要注意的是上面的 actionDonutListToDonutEntryDialogFragment() 函數(shù)需要一個(gè) Long 類型的參數(shù)像吻,我們之前創(chuàng)建了相關(guān)變量峻黍,并且給它賦值為 -1。所以如果我們在調(diào)用該函數(shù)的時(shí)候不加參數(shù)拨匆,該方法會(huì)返回一個(gè) NavDirections 對象姆涩,并且它的 itemId 為 -1。

在另一個(gè)生成的文件 DonutEntryDialogFragmentArgs 中惭每,您可以看到 fromBundle() 函數(shù)包含從目標(biāo)對話框獲取數(shù)據(jù)的代碼:

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

現(xiàn)在我可以利用生成的代碼成功傳遞和獲取數(shù)據(jù)了骨饿。首先,我在 DonutEntryDialogFragment 類中編寫代碼來獲取 itemId 數(shù)據(jù)台腥,并且確定用戶的意圖是添加一個(gè)新的甜甜圈還是編輯一個(gè)已有的甜甜圈:

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

第一行代碼用到了一個(gè)屬性委托样刷,它由 Navigation 組件庫提供,這樣寫可以簡化從 bundle 獲取數(shù)據(jù)的過程览爵。通過它可以在 args 變量中直接找到數(shù)據(jù)所對應(yīng)的名稱置鼻。

如果用戶正在編輯一個(gè)已有的甜甜圈信息,那么這里的代碼會(huì)獲取該元素的信息蜓竹,并且使用獲取到的信息填充 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
        }
    )
}

需要注意的是這里的代碼是從數(shù)據(jù)庫請求信息箕母,并且我們希望整個(gè)請求過程能夠在 UI 線程之外進(jìn)行。所以代碼里會(huì)監(jiān)聽 ViewModel 所提供的 LiveData 對象俱济,并且異步處理請求嘶是,當(dāng)數(shù)據(jù)返回時(shí)填充視圖。

當(dāng)用戶點(diǎn)擊對話框里的 Done 按鈕時(shí)蛛碌,就需要存儲(chǔ)用戶所輸入的信息了聂喇。下面這段代碼會(huì)更新數(shù)據(jù)庫里相應(yīng)的數(shù)據(jù),并且關(guān)閉對話框:

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

上面的這些代碼主要側(cè)重于在目的界面里處理數(shù)據(jù),現(xiàn)在我們來看一下如何將數(shù)據(jù)發(fā)送到目標(biāo)界面希太。

DonutList 中一共有兩種途徑可以轉(zhuǎn)向?qū)υ捒蚩巳摹F渲幸环N是當(dāng)用戶點(diǎn)擊 懸浮操作按鈕 (FAB) 的時(shí)候:

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

需要注意的是,這段代碼里在創(chuàng)建 NavDirections 對象的時(shí)候調(diào)用了無參數(shù)的構(gòu)造函數(shù)誊辉,所以變量會(huì)被默認(rèn)賦值為 -1 (以表明這是一個(gè)新的甜甜圈)矾湃,這也是我們希望通過點(diǎn)擊懸浮操作按鈕所要實(shí)現(xiàn)的效果。

另一個(gè)途徑是當(dāng)用戶點(diǎn)擊列表中已有元素的時(shí)候堕澄,會(huì)打開對話框邀跃。可以通過下面的 lambda 表達(dá)式實(shí)現(xiàn)蛙紫,它將在 DonutListAdapter 的構(gòu)建過程中傳入 (即 onEdit 參數(shù))拍屑,然后會(huì)在每個(gè)表項(xiàng)的 onClick 被觸發(fā)的時(shí)候被調(diào)用:

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

這里的代碼和用戶點(diǎn)擊懸浮操作按鈕的代碼相似,只不過這里將表項(xiàng)的 id 傳了進(jìn)去坑傅,告訴對話框它要編輯一個(gè)已有的元素僵驰。而且和我們之前的代碼看到的一樣,它會(huì)用已有元素的信息填充對話框裁蚁,并且對該表項(xiàng)所做的修改也會(huì)相應(yīng)更新數(shù)據(jù)庫里的對應(yīng)項(xiàng)。

總結(jié)

這就是 SafeArgs 的全部內(nèi)容继准。使用起來非常簡單 (比起 Bundle 要簡單很多)枉证,因?yàn)橐蕾噹鞎?huì)幫您生成代碼來簡化數(shù)據(jù)傳遞,并且保障了數(shù)據(jù)類型安全移必。通過這樣的方式室谚,您可以更好地利用數(shù)據(jù)封裝,在目的地之間僅僅傳遞所需的數(shù)據(jù)而無需在更大的范圍內(nèi)暴露數(shù)據(jù)崔泵。

請繼續(xù)關(guān)注我們后續(xù)的關(guān)于導(dǎo)航組件的內(nèi)容秒赤,接下來我們會(huì)介紹如何使用 Deep Link。

更多信息

更多關(guān)于導(dǎo)航組件的詳情憎瘸,請查看 導(dǎo)航組件使用入門文檔

DonutTracker 應(yīng)用的完整代碼入篮,請查看 Github 示例

更多現(xiàn)代 Android 開發(fā)技巧 (MAD Skills) 系列內(nèi)容,請查看 Android Developers 頻道

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末幌甘,一起剝皮案震驚了整個(gè)濱河市潮售,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锅风,老刑警劉巖酥诽,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異皱埠,居然都是意外死亡肮帐,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進(jìn)店門边器,熙熙樓的掌柜王于貴愁眉苦臉地迎上來训枢,“玉大人托修,你說我怎么就攤上這事“估” “怎么了诀黍?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長仗处。 經(jīng)常有香客問我眯勾,道長,這世上最難降的妖魔是什么婆誓? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任吃环,我火速辦了婚禮,結(jié)果婚禮上洋幻,老公的妹妹穿的比我還像新娘郁轻。我一直安慰自己,他們只是感情好文留,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布好唯。 她就那樣靜靜地躺著,像睡著了一般燥翅。 火紅的嫁衣襯著肌膚如雪骑篙。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天森书,我揣著相機(jī)與錄音靶端,去河邊找鬼。 笑死凛膏,一個(gè)胖子當(dāng)著我的面吹牛杨名,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播猖毫,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼台谍,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吁断?” 一聲冷哼從身側(cè)響起典唇,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎胯府,沒想到半個(gè)月后介衔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡骂因,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年炎咖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,127評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乘盼,死狀恐怖升熊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情绸栅,我是刑警寧澤级野,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站粹胯,受9級特大地震影響蓖柔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜风纠,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一况鸣、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧竹观,春花似錦镐捧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至誊抛,卻和暖如春列牺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芍锚。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工昔园, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蔓榄,地道東北人并炮。 一個(gè)月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像甥郑,于是被迫代替她去往敵國和親逃魄。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容