MVVM架构下Activity和Fragment之间数据传递和点
2024-07-19 本文已影响0人
爱学习的猫叔
背景
传统的activity和fragment之间、fragment和fragment之间的数据传递有以下方式
1、直接使用类似eventbus、livedatabus这类事件总线的方式
2、使用接口回调,找到实现实现该接口的fragment或activity进行强制类型转换再进行方法调用和参数传递
第一种方式如果项目大了,没法调试,代码可读性差,管理维护麻烦
第二种方式显示的持有具有生命周期的fragment或activity理论上都需要套一层WeakReference防止内存泄露,麻烦。
今天我们使用livedata+viewmodel的方式解决数据传递和点击事件回调,总之两个字:优雅!
实现方式
第一步:依赖引入
app的build.gradle下引入livedata和viewmodel依赖
android {
//...
compileOptions {
sourceCompatibility = 1.8
targetCompatibility = 1.8
}
kotlinOptions {
jvmTarget = "1.8"
}
}
dependencies {
///...
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel- ktx:$lifecycle_version"
// LiveData
implementation "androidx.lifecycle:lifecycle-livedata- ktx:$lifecycle_version"
// For using KTX Delegates extensions for fragments
implementation "androidx.fragment:fragment-ktx:$fragment_version"
}
第二步,viewmodel里声明事件
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MainViewModel(val data: String) : ViewModel() {
val showSharedVMToast = MutableLiveData<Unit>()
// Shows a Toast using SharedViewModel implementation
fun onShowVMToastClick() {
showSharedVMToast.value = Unit
}
}
第三步,activity里响应事件
import android.os.Bundle
import android.widget.Toast
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
// Adding the interface that communicates with the fragment
class MainActivity : AppCompatActivity() {
// Initializing the viewModel on call using KTX-Fragments extension.
private val viewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// Observing viewModel's LiveData
viewModel.showSharedVMToast.observe(this, Observer {
Toast.makeText(this, "Hello from sharedVM", Toast.LENGTH_SHORT).show()
})
// Adding the DemoFragment to a container FrameLayout
supportFragmentManager.beginTransaction().add(R.id.demoFragmentContainer, DemoFragment()).commit()
}
}
第四步,fragment里调用
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
class DemoFragment : Fragment() {
// We create an instance of MainViewModel using specific KTX-Fragments extension to
// tell the program that we are willing to use the instance of the Activity this fragment is attached to.
private val sharedVM: MainViewModel by activityViewModels()
// We don't need Constructor to pass any extra to this fragment because we have access to the data from SharedVM
// already.
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_demo, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//We can tap directly to the viewModel without using Interface callbacks!
sharedVMButton.setOnClickListener {
sharedVM.onShowVMToastClick()
}
}
}
这样就完成了fragment里点击调用activity事件或数据传递(反之亦如此),核心就是这两个东西
val showSharedVMToast = MutableLiveData<Unit>()
private val sharedVM: MainViewModel by activityViewModels()
如果要传递普通的内容,如String,Unit换成String即可
扩展
上述点击事件和数据传递使用的是liveData,liveData有个特性就是存储的内容会回显,简单点说就是fragment被销毁后,如果对应的viewmodel没有被销毁,所持有的livedata对象在fragment/activity被重新创建好后会主动回调一次,如果业务逻辑是只想消费一次,如点击事件,不希望数据回显或数据回流,这个时候需要改造liveData成SingleEvent类型,核心思路就是内部给一个标记位或Event Wrapper,代码如下:
方式一:内部标记位
class SingleLiveEvent<T> : MutableLiveData<T>() {
private val mPending = AtomicBoolean(false)
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
if (hasActiveObservers()) {
Log.w("SingleLiveEvent", "Multiple observers registered but only one will be notified of changes.")
}
// Observe the internal MutableLiveData
super.observe(owner) { t ->
if (mPending.compareAndSet(true, false)) {
observer.onChanged(t)
}
}
}
@MainThread
override fun setValue(t: T?) {
mPending.set(true)
super.setValue(t)
}
}
使用方式
//MutableLiveData换成SingleLiveEvent即可
val mediatorLiveData = SingleLiveEvent<String>()
方式二:Event Wrappter
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
使用方式
class ListViewModel : ViewModel {
private val _navigateToDetails = MutableLiveData<Event<String>>()
val navigateToDetails : LiveData<Event<String>>
get() = _navigateToDetails
fun userClicksOnButton(itemId: String) {
_navigateToDetails.value = Event(itemId) // Trigger the event by setting a new Event as a new value
}
}
观察的地方
myViewModel.navigateToDetails.observe(this, Observer {
it.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
startActivity(DetailsActivity...)
}
})