使用共享 ViewModel 在 Android 中进行片段到片段通信
如果一个活动中有两个或多个片段,它们需要在它们之间进行通信和共享数据。在两个片段之间共享数据的传统方式是使用一个接口来实现回调,这个接口很麻烦并且可能会抛出异常。但现代的做法是使用共享的 ViewModel。因此,在本文中,演示了如何使用共享的 ViewModel 在片段之间进行通信。查看下图以了解讨论的概况。
Note: This discussion is implemented in Kotlin programming language.
先决条件
- Android 中的片段生命周期
- Android 架构组件中的 ViewModel
实现fragment之间通信的步骤
第 1 步:创建一个空的活动项目
创建一个空的活动 Android Studio 项目,并选择 Kotlin 作为编程语言。参考安卓 |如何在 Android Studio 中创建/启动新项目?。
第 2 步:添加所需的依赖项
依赖项包括 ViewModel 和 LiveData。因此,在应用级 Gradle 文件中添加以下依赖项并同步项目。
// lifecycle_version and architecture versions may vary
def lifecycle_version = “2.3.1”
def arch_version = “2.1.0”
// ViewModel
implementation “androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version”
// LiveData
implementation “androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version”
// Lifecycles only (without ViewModel or LiveData)
implementation “androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version”
// architecture Lifecycle extensions
implementation “androidx.lifecycle:lifecycle-extensions:$arch_version”
第 3 步:使用 activity_main.xml 文件
应用程序的主布局包含两个 FrameLayouts,它们包含两个片段。要实现相同的调用,请在activity_main.xml文件中调用以下代码。
XML
Kotlin
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class SharedViewModel : ViewModel() {
// Mutable LiveData which observed by LiveData
// and updated to EditTexts when it is changed.
private val mutableLiveData: MutableLiveData = MutableLiveData()
// function to set the changed
// data from the EditTexts
fun setData(input: CharSequence) {
mutableLiveData.value = input
}
// function to get the changed data from the EditTexts
fun getData(): MutableLiveData = mutableLiveData
}
XML
XML
Kotlin
import android.os.Bundle
import android.text.Editable
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
class Fragment1 : Fragment() {
private var sharedViewModelInstance: SharedViewModel? = null
private var editTextFromFragment1: EditText? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_1, container, false)
val sendDataButton: Button = view.findViewById(R.id.send_button_fragment_1)
editTextFromFragment1 = view.findViewById(R.id.edit_text_from_fragment_1)
// as soon as the button is clicked
// send the data to ViewModel
// and the Live data will take care of
// updating the data inside another Fragment
sendDataButton.setOnClickListener {
sharedViewModelInstance?.setData(editTextFromFragment1!!.text)
}
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// create instances of the shared view model
// when the activity is created
sharedViewModelInstance = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
// observe the data inside the view model that
// is mutable live of type CharSequence and
// set the data for edit text
sharedViewModelInstance!!.getData().observe(viewLifecycleOwner, Observer {
editTextFromFragment1!!.text = it as Editable?
})
}
}
Kotlin
import android.os.Bundle
import android.text.Editable
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
class Fragment2 : Fragment() {
private var sharedViewModelInstance: SharedViewModel? = null
private var editTextFromFragment2: EditText? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_2, container, false)
val sendDataButton: Button = view.findViewById(R.id.send_button_fragment_2)
editTextFromFragment2 = view.findViewById(R.id.edit_text_from_fragment_2)
// as soon as the button is clicked
// send the data to ViewModel
// and the Live data will take care of
// updating the data inside another Fragment
sendDataButton.setOnClickListener {
sharedViewModelInstance?.setData(editTextFromFragment2!!.text)
}
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// create instances of the shared view model
// when the activity is created
sharedViewModelInstance = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
// observe the data inside the view model that is mutable
// live of type CharSequence and set the data for edit text
sharedViewModelInstance!!.getData().observe(viewLifecycleOwner, Observer {
editTextFromFragment2!!.text = it as Editable?
})
}
}
Kotlin
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentTransaction
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.apply {
add(R.id.fragment_1_holder, Fragment1())
add(R.id.fragment_2_holder, Fragment2())
commit()
}
}
}
第 4 步:实现共享 ViewModel
在SharedViewModel.kt文件中,有一个 CharSequence 的 MutableLiveData 用于设置 EditTexts 的数据。两个函数 setData 和 getData 用于在 EditTexts 中的数据发生更改时立即更新可变的实时数据。
要实现相同的调用以下代码。
科特林
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class SharedViewModel : ViewModel() {
// Mutable LiveData which observed by LiveData
// and updated to EditTexts when it is changed.
private val mutableLiveData: MutableLiveData = MutableLiveData()
// function to set the changed
// data from the EditTexts
fun setData(input: CharSequence) {
mutableLiveData.value = input
}
// function to get the changed data from the EditTexts
fun getData(): MutableLiveData = mutableLiveData
}
第 5 步:创建 2 个片段
- 创建两个具有自己布局的Fragment,命名为 Fragment1.kt 和 Fragment2.kt 。
- 对于每个片段的布局,它包含一个 EditText 来获取要为片段 2 发送的数据和一个按钮,单击时它将数据共享给另一个片段。
- 要实现 Fragment 1 的布局,请调用 fragment_1.xml 文件中的以下代码
XML
要实现 Fragment 2 的布局,请调用fragment_2.xml文件中的以下代码。
XML
第 6 步:在 Fragment.kt 文件中创建共享视图模型的实例
- 创建Activity的时候需要创建ShareViewModel类型的ViewModel的实例。
- 因此,需要覆盖每个 Fragment 中的 onActivityCreated() 方法。
- 这是因为当我们看到片段的生命周期时,视图是在调用onCreateView()之后创建和更新的,并且在执行 onCreateView() 之后调用 onAcrivityCreated()回调。因此 LiveData 可以跟踪 UI 元素更新了哪些元素。请查看以下图表,了解片段的活动生命周期。
要在两个片段中实现相同的内容,请调用Fragment1.kt 中的以下代码。
科特林
import android.os.Bundle
import android.text.Editable
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
class Fragment1 : Fragment() {
private var sharedViewModelInstance: SharedViewModel? = null
private var editTextFromFragment1: EditText? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_1, container, false)
val sendDataButton: Button = view.findViewById(R.id.send_button_fragment_1)
editTextFromFragment1 = view.findViewById(R.id.edit_text_from_fragment_1)
// as soon as the button is clicked
// send the data to ViewModel
// and the Live data will take care of
// updating the data inside another Fragment
sendDataButton.setOnClickListener {
sharedViewModelInstance?.setData(editTextFromFragment1!!.text)
}
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// create instances of the shared view model
// when the activity is created
sharedViewModelInstance = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
// observe the data inside the view model that
// is mutable live of type CharSequence and
// set the data for edit text
sharedViewModelInstance!!.getData().observe(viewLifecycleOwner, Observer {
editTextFromFragment1!!.text = it as Editable?
})
}
}
和Fragment2.kt文件一样,调用以下代码。
科特林
import android.os.Bundle
import android.text.Editable
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
class Fragment2 : Fragment() {
private var sharedViewModelInstance: SharedViewModel? = null
private var editTextFromFragment2: EditText? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.fragment_2, container, false)
val sendDataButton: Button = view.findViewById(R.id.send_button_fragment_2)
editTextFromFragment2 = view.findViewById(R.id.edit_text_from_fragment_2)
// as soon as the button is clicked
// send the data to ViewModel
// and the Live data will take care of
// updating the data inside another Fragment
sendDataButton.setOnClickListener {
sharedViewModelInstance?.setData(editTextFromFragment2!!.text)
}
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// create instances of the shared view model
// when the activity is created
sharedViewModelInstance = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
// observe the data inside the view model that is mutable
// live of type CharSequence and set the data for edit text
sharedViewModelInstance!!.getData().observe(viewLifecycleOwner, Observer {
editTextFromFragment2!!.text = it as Editable?
})
}
}
步骤 7:使用 MainActivity.kt 文件填充片段持有者
在MainActivity.kt文件中,需要用两个片段填充 activity_main.xml 中的片段持有者。要实现相同的调用,请在MainActivity.kt文件中调用以下代码。
科特林
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentTransaction
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()
fragmentTransaction.apply {
add(R.id.fragment_1_holder, Fragment1())
add(R.id.fragment_2_holder, Fragment2())
commit()
}
}
}
输出: