如何在Fragment之間進行資料交換

如何在Fragment之間進行資料交換

我們很常遇到一個需求就是在不同的 Fragment 當中交換資料,通常解決的辦法有很多種,今天來試著紀錄到底有哪些方法可以實作?

這邊參考的來源是官方網站,其實官方網站給的資訊相當豐富,透過這些方法大致上已經涵蓋了所有能實作的方向。

透過 Fragment Result 的方式


一開始我們先宣告一個 Fragment,就叫它 MainFragment。

class MainFragment : Fragment() {  
  
    private lateinit var binding: FragmentMainBinding  
  
    override fun onCreateView(inflater: LayoutInflater,	container: ViewGroup?, savedInstanceState: Bundle?  
    ): View {    
		binding = FragmentMainBinding.inflate(layoutInflater)  
		return binding.root  
	}  
  
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
        super.onViewCreated(view, savedInstanceState)  
        binding.goNext.setOnClickListener {  
			parentFragmentManager.beginTransaction()  
				.replace(R.id.container, NextFragment.newInstance())  
				.addToBackStack("")  
				.commit()  
        }  
		parentFragmentManager.setFragmentResultListener(REQUEST_KEY, viewLifecycleOwner) { _, bundle ->  
			val result = bundle.getString(BUNDLE_KEY, "")  
			Log.d("MainFragment", "result:$result")  
        }  
	}  
  
    companion object {  
		const val REQUEST_KEY = "requestKey"  
		const val BUNDLE_KEY = "bundleKey"  
		fun newInstance() = MainFragment()  
    }  
}

這邊是拿來接收從另外一個 Fragment 所帶出來的結果。
因此我們這邊也需要設計另外一個 Fragment 來示範怎麼帶結果回來,就叫它 NextFragment。

class NextFragment : Fragment() {  
  
    private lateinit var binding: FragmentNextBinding  
  
	override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View {  
		binding = FragmentNextBinding.inflate(layoutInflater)  
		return binding.root  
	}  
  
	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
		super.onViewCreated(view, savedInstanceState)  
		binding.goBack.setOnClickListener {  
			val result = "test"  
			setFragmentResult(MainFragment.REQUEST_KEY, bundleOf(MainFragment.BUNDLE_KEY to result))  
			parentFragmentManager.popBackStack()  
		}  
	}  
  
    companion object {  
        fun newInstance() = NextFragment()  
    }  
}

如此一來就可以看到我們的 MainFragment 會收到結果。

透過共用 ViewModel 的方式

https://insert-koin.io/docs/reference/koin-android/viewmodel/#shared-viewmodel
Koin 是我用來操作 injection 的工具,透過這套 injection 的工具,可以輕鬆宣告出共通的 ViewModel 來協助我們使用通知 Fragment 頁面更新。
首先宣告一個 ViewModel 的 class。

class CommonViewModel : ViewModel() {  
    val isDataChanged = MutableLiveData<Boolean>()  
}

接著在 AppModule 內加入一行 ViewModel 的宣告。

val vmModule = module {  
  viewModel { CommonViewModel() }

接著我們在 MainFragment 上面進行宣告。

class MainFragment : Fragment() {
	private val commonViewModel by sharedViewModel<CommonViewModel>()

	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
	    super.onViewCreated(view, savedInstanceState)
		commonViewModel.isDataChanged.observe(viewLifecycleOwner, { isDataChanged ->
			//do something
		}
	}
}

接著我們到 NextFragment 進行相關傳送資料。

class NextFragment : Fragment() {
	private val commonViewModel by sharedViewModel<CommonViewModel>()

	override fun onViewCreated(view: View, savedInstanceState: Bundle?) {  
	    super.onViewCreated(view, savedInstanceState)
		parentViewModel.isDataChanged.value = true
	}
}

如此一來我們就可以在 MainFragment 收到資料變動了。

除了這樣還可以透過 EventBus、Singleton Pattern 製造一個 class 來進行傳送,不過這些方式可能耦合性會比較重,因此在使用時,必須小心斟酌設計。

參考網址
https://developer.android.com/guide/fragments/communicate