Android Fragment通訊

1. 簡要

Fragment庫提供兩個通訊的選項,共享的View Model和Fragment Result API

如需共享持久性數據,應使用View Model

放置Bundle中一次性結果,應使用Fragment Result API

2. 使用View Model共享數據

以下為Android官網注意事項:

注意ViewModel 会一直在内存中,直到其范围限定到的 ViewModelStoreOwner 永久消失。在一个 Activity 架构中,如果 ViewModel 的范围限定为 Activity,那么它本质上是单例。首次实例化 ViewModel 之后,使用 Activity 范围检索 ViewModel 的后续调用始终返回相同的现有 ViewModel 以及现有数据,直到 Activity 的生命周期永久结束。

注意:務必將適當的範圍與 ViewModelProvider 一起使用。在前面的示例中,MainActivity 用作 MainActivity 和 ListFragment 中的範圍,因此為它們提供了相同的 ViewModel。如果 ListFragment 將自身用作範圍,會為其提供與 MainActivity 不同的 ViewModel。

在多個Fragment之間或與Activity之間共享數據時,View Model是理想的選擇

第一種:在Activity監聽觀察變化

//新增一個Item
class Item(
    var id: Int = 0, 
    var name: String = "", 
    var data: Int = 0) {
}

//建立一個ViewModel
class ItemViewModel: ViewModel() {
    private val mutableSelectItem = MutableLiveData()
    val selectedItem get() = mutableSelectItem
    fun selectItem(item: Item) {
        mutableSelectItem.value = item
    }
}


//在Activity中新增
private val viewModel: ItemViewModel by viewModels()
val fragment = TestFragment()
//在onCreate中
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment
    )
}
viewModel.selectedItem.observe(this, { item ->
    println("item id = ${item.id}, " +
            "name = ${item.name}," +
            "data = ${item.data}"
    )
})


//在TestFragment中新增,此處按下按鈕後
//Activity就會打印出
//item id = 0, name = test, data = 2021
private val viewModel: ItemViewModel by activityViewModels()
fun onItemClicked(item: Item) {
    viewModel.selectItem(item)
}
binding.itemImage.setOnClickListener {
    onItemClicked(Item().also {
        it.id = 0
        it.name = "test"
        it.data = 2021
    })
}

第二種:在兩個Fragment中監聽觀察變化

兩個Fragment都在Activity中

當資料變動可以刷新另一個Fragment

在FragmentA按下按鈕,由FragmentB接收並打印

viewModel item id = 0, name = test,data = 2021

在FragmentB按下按鈕,由FragmentA接收並打印

viewModel flag true

//新增一個ViewModel
class ItemViewModel2: ViewModel() {
    private val mutableSelectItem = MutableLiveData()
    val selectedItem get() = mutableSelectItem
    val updateFlag = MutableLiveData()
    fun selectItem(item: Item) {
        mutableSelectItem.value = item
    }
    fun updateFlag(flag: Boolean) {
        updateFlag.value = flag
    }
}


//在Activity新增
val fragment = TestFragment()
val fragment2 = TestFragment2()
//onCreate中新增
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment
    )
}
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view2,
        fragment2
    )
}


//在TestFragment新增
private val viewModel: 
            ItemViewModel2 by activityViewModels()
fun onItemClicked(item: Item) {
    viewModel.selectItem(item)
}
//在onViewCreated內新增
viewModel.updateFlag.observe(viewLifecycleOwner, 
        { flag ->
    println("viewModel flag $flag")
})
binding.itemImage.setOnClickListener {
    onItemClicked(Item().also {
        it.id = 0
        it.name = "test"
        it.data = 2021
    })
}


//在TestFragment2新增
private val viewModel: 
            ItemViewModel2 by activityViewModels()
private var flag = false
fun onItemClicked(flag: Boolean) {
    viewModel.updateFlag(flag)
}
//在onViewCreated中增新
viewModel.selectedItem.observe(viewLifecycleOwner, 
        { item ->
    println("viewModel item id = ${item.id}, " +
            "name = ${item.name}," +
            "data = ${item.data}"
    )
})
binding.itemImage.setOnClickListener {
    flag = !flag
    onItemClicked(flag)
}

第三種:父與子之間共享資料

一個Fragment在Activity中(父),另一個在Fragment中(子)

當資料變動可以刷新另一個Fragment

在FragmentA按下按鈕,由FragmentB接收並打印

viewModel item id = 0, name = test,data = 2021

在FragmentB按下按鈕,由FragmentA接收並打印

viewModel flag true

//新增一個ViewModel
class ItemViewModel2: ViewModel() {
    private val mutableSelectItem = MutableLiveData()
    val selectedItem get() = mutableSelectItem
    val updateFlag = MutableLiveData()
    fun selectItem(item: Item) {
        mutableSelectItem.value = item
    }
    fun updateFlag(flag: Boolean) {
        updateFlag.value = flag
    }
}


//在Activity中新增
val fragment = TestFragment()
//onCreate中新增
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment
    )
}


//在TestFragment中新增
private val viewModel: ItemViewModel2 by viewModels()
val fragment2 = TestFragment2()
fun onItemClicked(item: Item) {
    viewModel.selectItem(item)
}
//在onViewCreated中增新
childFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment2
    )
}
viewModel.updateFlag.observe(viewLifecycleOwner, 
         { flag ->
    println("viewModel flag $flag")
})
binding.itemImage.setOnClickListener {
    onItemClicked(Item().also {
        it.id = 0
        it.name = "test"
        it.data = 2021
    })
}


//在TestFragment2中新增
private val viewModel: ItemViewModel2 
            by viewModels({requireParentFragment()})
private var flag = false
fun onItemClicked(flag: Boolean) {
    viewModel.updateFlag(flag)
}
//在onViewCreated中增新
viewModel.selectedItem.observe(viewLifecycleOwner, 
         { item ->
    println("viewModel item id = ${item.id}, " +
            "name = ${item.name}," +
            "data = ${item.data}"
    )
})
binding.itemImage.setOnClickListener {
    flag = !flag
    onItemClicked(flag)
}

3. 使用 Fragment Result API獲取結果

從Fragment 1.3.0-alpha04版本開始

每個FragmentManager 都會實現 FragmentResultOwner

由FragmentB傳至FragmentA時,先在FragmentB設置監聽setFragmentResultListener()

以下為Android官網注意事項:

監聽器收到結果並觸發 onFragmentResult() 回調後,結果會被清除。這種行為有兩個主要影響:

a. 返回堆棧上的 Fragment 只有在被彈出且處於 STARTED 狀態之後才會收到結果。
b. 如果在設置結果時監聽結果的 Fragment 處於 STARTED 狀態,則會立即觸發監聽器的回調。

注意:由於 Fragment 結果存儲在 FragmentManager 級別,因此 Fragment 必須隨父 FragmentManager 一起附加到對 setFragmentResultListener() 或 setFragmentResult() 的調用。

主要是看setFragmentResult在哪個FragmentManager,調用時要特別注意使用級別

第一種:在Fragment之間傳遞結果

以下程式碼,由TestFragment2傳送資料至TestFragment

//在Activity中新增
val fragment = TestFragment()
val fragment2 = TestFragment2()
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment
    )
}
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view2,
        fragment2
    )
}


//在TestFragment新增
setFragmentResultListener("requestKey")
{ requestKey, bundle ->
    val result = bundle.getString("bundleKey")
    println("result $result")
}


//在TestFragment2新增
binding.itemImage.setOnClickListener {
    val result = "result"
    setFragmentResult(
        "requestKey", 
        bundleOf("bundleKey" to result)
    )
}

第二種:在父 Fragment 與子 Fragment 之間傳遞結果

以下程式碼,由TestFragment2(子)傳送資料至TestFragment(父)

因為子是childFragmentManager

所以接收的時候也需要使用childFragmentManager

//在Activity中新增
val fragment = TestFragment()
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment
    )
}


//在TestFragment中新增
val fragment2 = TestFragment2()
childFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment2
    )
}
childFragmentManager
    .setFragmentResultListener(
        "requestKey",
        viewLifecycleOwner
    ) { key, bundle ->
        val result = bundle.getString("bundleKey")
        println("result key $key, result $result")
}


//在TestFragment2中新增
binding.itemImage.setOnClickListener {
    val result = "result"
    setFragmentResult(
        "requestKey",
        bundleOf("bundleKey" to result)
    )
}

第三種:在Activity中接收結果

因為最終希望在Activity中接收資料

所以傳送資料需使用supportFragmentManager.setFragmentResult

//在Activity中新增
val fragment = TestFragment()
supportFragmentManager.commit {
    setReorderingAllowed(true)
    add(
        R.id.fragment_container_view,
        fragment
    )
}
supportFragmentManager
    .setFragmentResultListener(
        "requestKey", 
        this)
    { requestKey, bundle ->
        val result = bundle.getString("bundleKey")
        println("result $result")
    }


//在TestFragment中新增
binding.itemImage.setOnClickListener {
    val result = "result"
    activity?.supportFragmentManager?.setFragmentResult(
        "requestKey",
        bundleOf("bundleKey" to result)
    )
}

以上內容參考Android 官網


相關文章

Android Fragment建立、更換、尋找、Back StackAndroid Fragment add與replace分析
1. 使用XML在Activity與Fragment連接
2. 使用程式方式在Activity與Fragment連接
3. 將已新增的Fragment更換
4. 尋找已建立的Fragment
5. Fragment對應的FragmentManager
6. Back Stack使用
Android Fragment 自定義constructorAndroid Fragment show、hide、attach、detach用法
1. 重寫FragmentFactory
2. 建立Fragment
3. 在Activity中使用
1. 範例程式
2. Fragment show與hide是什麼呢?
3. Fragment attach與detach是什麼呢?
Android Fragment Transitions動畫效果Android Fragment shared element transitions動畫效果
1. 範例程式
2. 功能介紹
1. 範例程式
2. 功能介紹
Android Fragment lifecycle
1. 簡略
2. 測試搭配setMaxLifecycle的生命週期

訂閱Codeilin的旅程,若有最新消息會通知。

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com 標誌

您的留言將使用 WordPress.com 帳號。 登出 /  變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 /  變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 /  變更 )

連結到 %s

透過 WordPress.com 建置的網站.

向上 ↑

%d 位部落客按了讚: