【Android Jetpack】ViewModel——在Fragment之间共享数据

1. 前言

在上篇【Android Jetpack】ViewModel——配置更改保留状态数据中简单使用了ViewModel,结合databinding可以将数据很轻松的展示在UI控件上,而不需要过多的配置、数据状态保存和恢复。而ViewModel能做到的不仅是在设备配置发生改变的时候状态保存,还可以用作Fragment之间的数据共享。可以查阅:官方文档

2. 实现方式

Activity 中的两个或更多 Fragment 需要相互通信是一种很常见的现象。这里在案例中配置两个Fragment,一个Fragment中输入内容,另一个中显示。比如:
在这里插入图片描述
实现也比较简单,也就是创建Fragment然后在Activityxml布局文件中引入:

<fragment
	android:id="@+id/left_fragment"
	android:name="com.weizu.jetpackdemo.LeftFragment"
	android:layout_width="200dp"
	android:layout_height="wrap_content"
	/>

2.1 方式一:借助Activity

2.1.1 实现案例

为了达到预期的效果,那么就需要借助Activity这个中间商,在Activity中监听到LeftFragment的输入事件,然后将结果反馈到RrightFragment中。但由于EditText或者TextView均在Fragment页面中,而不在Activity中。故而就需要借助接口来实现回调。比如下面改造一下LeftFragment的代码:

class LeftFragment : Fragment() {

    private var listener: EditTextInputListener? = null

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val root = inflater.inflate(R.layout.fragment_left, container, false)
        val editText = root.findViewById<EditText>(R.id.left_fragment_edit_text)
        editText.addTextChangedListener(object : TextWatcher{
            override fun beforeTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
            override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
            override fun afterTextChanged(s: Editable?) {
                listener?.onTextChanged(s)
            }
        })
        return root
    }

    fun setOnTextChangeListener(l: EditTextInputListener){
        this.listener = l;
    }

    interface EditTextInputListener{
        fun onTextChanged(s: Editable?)
    }
}

然后在Activity中为LeftFragment设置监听:

val leftFragment = supportFragmentManager.findFragmentById(R.id.left_fragment) as LeftFragment
val rightFragment = supportFragmentManager.findFragmentById(R.id.right_fragment) as RightFragment

leftFragment.setOnTextChangeListener(object : LeftFragment.EditTextInputListener {
    override fun onTextChanged(s: Editable?) {
        // callback
        rightFragment.setText(s)
    }
})

类似的,在RightFragment暴露设置的方法即可,然后就可以做到两个Fragment之间的通讯。
在这里插入图片描述

2.1.2 缺点

这种方式具有明显的缺点,即当两个Fragment均需要对方的数据的时候,那么就需要这两个Fragment均定义对应的接口,然后再在Activity中进行设置监听接口的示例,以达到数据的共享。

  • 咋一看没啥问题,但是随着Fragment的增多这就无疑不合适了,因为需要在Activity中写对应的多个接口实现以及定义接口;
  • 当所需要传递(共享)的数据增多的时候,通过参数传递的方式不太合适;

2.2 方式二:使用本地广播

Fragment比较多的时候,就可以使用本地广播进行传递数据。需要注意的是:

  • 本地广播需要使用动态注册;

LeftFragment中发送本地广播:

class LeftFragment : Fragment() {

    private val localBroadcastManager by lazy {
        this.context?.let {
            LocalBroadcastManager.getInstance(
                it
            )
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val root = inflater.inflate(R.layout.fragment_left, container, false)
        val editText = root.findViewById<EditText>(R.id.left_fragment_edit_text)
        editText.addTextChangedListener(object : TextWatcher {
            override fun beforeTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
            override fun onTextChanged(s: CharSequence?, p1: Int, p2: Int, p3: Int) {}
            override fun afterTextChanged(s: Editable?) {
                sendBroadcast(s)
            }
        })
        return root
    }

    fun sendBroadcast(s: Editable?) {
        context?.apply {
            localBroadcastManager?.sendBroadcast(Intent().apply {
                this.action = "leftfragment"
                this.putExtra("msg", s.toString())
            })
        }
    }
}

RightFragment中接收广播,注册一个自定义接收器,然后处理:

class RightFragment : Fragment() {

    private var textView: TextView? = null

    private val localBroadcastManager by lazy {
        this.context?.let {
            LocalBroadcastManager.getInstance(
                it
            )
        }
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val root = inflater.inflate(R.layout.fragment_right, container, false)
        textView = root.findViewById(R.id.right_fragment_text_view)

        localBroadcastManager?.registerReceiver(MyReceiver(), IntentFilter().apply {
            this.addAction("leftfragment")
        })
        return root
    }

    inner class MyReceiver: BroadcastReceiver(){
        override fun onReceive(p0: Context?, intent: Intent?) {
            textView?.text = intent?.extras?.get("msg").toString()
        }
    }
}

效果和第一种类似。

2.3 方式三:使用ViewModel

此时在Activity中不需要什么配置,同样的,还是在xml中引入两个Fragment

<fragment
    android:id="@+id/left_fragment"
    android:name="com.weizu.jetpackdemo.LeftFragment"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toStartOf="@+id/guideline2"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent" />

<fragment
    android:id="@+id/right_fragment"
    android:name="com.weizu.jetpackdemo.RightFragment"
    android:layout_width="200dp"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="@+id/guideline2"
    app:layout_constraintTop_toTopOf="parent" />

然后在对应的LeftFragment以及RightFragment中设置ViewModel以共享数据。首先是LeftFragment的布局文件:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    >

    <data>
        <variable
            name="data"
            type="com.weizu.jetpackdemo.MyViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".LeftFragment">

        <EditText
            app:addTextChangedListener="@{ data.textWatcher }"
            android:id="@+id/left_fragment_edit_text"
            android:hint="输入文本"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

LeftFragment中设置databinding,然后返回视图对象:

class LeftFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // databinding到Fragment
        val binding =
            DataBindingUtil.inflate<FragmentLeftBinding>(
                inflater,
                R.layout.fragment_left,
                container,
                false
            )

        val myViewModel = ViewModelProvider(
            requireActivity(),
            ViewModelProvider.NewInstanceFactory()
        ).get(MyViewModel::class.java)

        binding.data = myViewModel
        binding.lifecycleOwner = requireActivity()

        return binding.root
    }
}

RightFragment中同理:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">
    
    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".RightFragment">

        <TextView
            android:id="@+id/right_fragment_text_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="Right Fragment" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

然后在RgihtFragment中配置观察:

class RightFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding =
            DataBindingUtil.inflate<FragmentRightBinding>(
                inflater,
                R.layout.fragment_right,
                container,
                false
            )

        val myViewModel = ViewModelProvider(
            requireActivity(),
            ViewModelProvider.NewInstanceFactory()
        ).get(MyViewModel::class.java)

        // 绑定观察
        myViewModel.getInputValue().observe(requireActivity()) {
            binding.rightFragmentTextView.text = myViewModel.getInputValue().value.toString()
        }
        return binding.root
    }
}

运行即可看见和方式一、二同样的效果:
在这里插入图片描述
因为我们虽然在两个Fragment中均获得了ViewModel实例,即:

val myViewModel = ViewModelProvider(
    requireActivity(),
    ViewModelProvider.NewInstanceFactory()
).get(MyViewModel::class.java)

但是,打印其地址可以知道,这两个对象为同一个实例。也就是当这两个Fragment各自获取 ViewModel时,它们会收到相同的 ViewModel实例。而它们均依托于宿主Activity,故而可以完成对LiveData数据的监听和设置。

2.4 其他

当然,还有很多其余的方式可以实现,比如基于文件类的。可以使用SPDataStoreFileSQLite等,甚至可以借助Socket等方式。这里只是为了引出ViewModel这种方式,就不再继续展开。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
Android Jetpack是Google提供的一套用于加速Android应用开发的工具包,其中包括了许多架构组件,其中之一就是ViewModelViewModel是一种设计模式,用于保存和管理与UI相关的数据。在传统的Android开发中,当屏幕旋转或者因为其他原因导致Activity或Fragment重建时,之前保存的临时数据就会丢失。而ViewModel的出现解决了这个问题。 ViewModel的主要作用是将数据与UI组件分离。它的工作方式是创建一个ViewModel类,并在其中保存需要与UI组件交互的数据。这样,当屏幕旋转或重建时,ViewModel实例不会销毁,数据也会得到保留。然后,在Activity或Fragment中,通过获取ViewModel实例,可以轻松地访问这些数据。 使用ViewModel的好处有很多。首先,它可以避免内存泄漏,因为ViewModel的生命周期与Activity或Fragment无关。其次,它可以节省资源,因为当Activity或Fragment销毁时,ViewModel实例可以被系统缓存起来,下次再创建时可以直接返回该实例。另外,由于ViewModel保存了与UI相关的数据,可以减少因为屏幕旋转导致的数据重复加载的问题。 在使用ViewModel时,你可以选择使用Android Jetpack中的其他架构组件来进一步提高开发效率,比如通过LiveData实现数据的观察和通知,或者通过DataBinding来实现UI与数据的自动绑定。 总之,ViewModelAndroid Jetpack中非常重要的一个架构组件,它的出现实现了数据与UI的解耦,提高了开发效率,并且解决了数据丢失的问题。希望通过这篇文档的详解,你对ViewModel有了更深入的理解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦否

文章对你有用?不妨打赏一毛两毛

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值