胖蔡说技术
随便扯扯

MVVM之LiveData的使用

引子

LiveDatas作为MVVM的一份子,很容易人健忘,不是很重要,但又不得不考虑,LiveData就个人而言其更类似一种简化的Rxjava的感觉,两者都是使用的观察者模式来实现的,相较于Rxjava而言,LiveData所能做的工作很有限,RxJava之与liveData类似与万精油,LiveData之于Rxjava又好像于一个界面数据处理专家,一个专精、一个大广(当然也不是说Rxjava不精,至少在线程切换、链式请求方面基本无出其右者),在StackOverflow里也曾有人对此产生过讨论,大家也可围观下,地址如下:RxJava-vs-LiveData。在这里,我们不做过多讨论,诚如,上面有人说的一样:

RxJava对LiveData来说既不好也不差,它是不同的。
Android架构组件旨在为Android开发人员提供模型架构模式。因此,如果您对LiveData感到满意,请使用它,或者如果您对RxJava感到满意,请使用它。
您可以使用两个库执行所有操作。虽然RxJava确实含有大量的语法糖,但使用Livedata也可以实现同样的效果。

所以一句话看需求需要,反正我是用来LiveData就把RxJava替换了,毕竟后面还有一篇LiveData和Retrofit集成的文章( ̄_, ̄ )。
8eb84d1a901a371

LiveData的使用

LiveData的封装很小,就几个类,我们比较常用的主要是: LiveDataMutableLiveData ,还有用的比较少的:
ComputableLiveDataMediatorLiveData
等,这里只是介绍MutableLiveData的使用,其他有时间后续或会进行分析介绍.
LiveData的使用很简单,这里介绍两种使用方式:一种是基础使用MutableLiveData进行数据绑定、另一种是自定义一个LiveData并实现监听

数据绑定

    <layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

        <data>

            <import type="android.view.View"/>

            <variable
                name="viewmodel"
                type="com.example.android.architecture.blueprints.todoapp.addedittask.AddEditTaskViewModel"/>
        </data>

        <com.example.android.architecture.blueprints.todoapp.ScrollChildSwipeRefreshLayout
            android:id="@+id/refresh_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:enabled="@{viewmodel.dataLoading}"
            app:refreshing="@{viewmodel.dataLoading}">

            <ScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical"
                    android:paddingBottom="@dimen/activity_vertical_margin"
                    android:paddingLeft="@dimen/activity_horizontal_margin"
                    android:paddingRight="@dimen/activity_horizontal_margin"
                    android:paddingTop="@dimen/activity_vertical_margin"
                    android:visibility="@{viewmodel.dataLoading ? View.GONE : View.VISIBLE}">

                    <EditText
                        android:id="@+id/add_task_title"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:hint="@string/title_hint"
                        android:maxLines="1"
                        android:imeOptions="flagNoExtractUi"
                        android:text="@={viewmodel.title}"
                        android:textAppearance="@style/TextAppearance.AppCompat.Title"/>

                    <EditText
                        android:id="@+id/add_task_description"
                        android:layout_width="match_parent"
                        android:layout_height="350dp"
                        android:imeOptions="flagNoExtractUi"
                        android:gravity="top"
                        android:hint="@string/description_hint"
                        android:text="@={viewmodel.description}"/>

                </LinearLayout>
            </ScrollView>
        </com.example.android.architecture.blueprints.todoapp.ScrollChildSwipeRefreshLayout>
    </layout>
    class AddEditTaskViewModel(
        private val tasksRepository: TasksRepository
    ) : ViewModel(), TasksDataSource.GetTaskCallback {

        // Two-way databinding, exposing MutableLiveData
        val title = MutableLiveData<String>()

        // Two-way databinding, exposing MutableLiveData
        val description = MutableLiveData<String>()

        private val _dataLoading = MutableLiveData<Boolean>()
        val dataLoading: LiveData<Boolean>
            get() =_dataLoading


        private val _snackbarText = MutableLiveData<Event<Int>>()
        val snackbarMessage: LiveData<Event<Int>>
            get() = _snackbarText

        private val _taskUpdated = MutableLiveData<Event<Unit>>()
        val taskUpdatedEvent: LiveData<Event<Unit>>
            get() = _taskUpdated

        private var taskId: String? = null

        private var isNewTask: Boolean = false

        private var isDataLoaded = false

        private var taskCompleted = false

        fun start(taskId: String?) {
            _dataLoading.value?.let { isLoading ->
                // Already loading, ignore.
                if (isLoading) return
            }
            this.taskId = taskId
            if (taskId == null) {
                // No need to populate, it's a new task
                isNewTask = true
                return
            }
            if (isDataLoaded) {
                // No need to populate, already have data.
                return
            }
            isNewTask = false
            _dataLoading.value = true

            tasksRepository.getTask(taskId, this)
        }

        override fun onTaskLoaded(task: Task) {
            title.value = task.title
            description.value = task.description
            taskCompleted = task.isCompleted
            _dataLoading.value = false
            isDataLoaded = true
        }

        override fun onDataNotAvailable() {
            _dataLoading.value = false
        }


        internal fun saveTask() {
            val currentTitle = title.value
            val currentDescription = description.value

            if (currentTitle == null || currentDescription == null) {
                _snackbarText.value =  Event(R.string.empty_task_message)
                return
            }
            if (Task(currentTitle, currentDescription ?: "").isEmpty) {
                _snackbarText.value =  Event(R.string.empty_task_message)
                return
            }

            val currentTaskId = taskId
            if (isNewTask || currentTaskId == null) {
                createTask(Task(currentTitle, currentDescription))
            } else {
                val task = Task(currentTitle, currentDescription, currentTaskId)
                    .apply { isCompleted = taskCompleted }
                updateTask(task)
            }
        }

        private fun createTask(newTask: Task) {
            tasksRepository.saveTask(newTask)
            _taskUpdated.value = Event(Unit)
        }

        private fun updateTask(task: Task) {
            if (isNewTask) {
                throw RuntimeException("updateTask() was called but task is new.")
            }
            tasksRepository.saveTask(task)
            _taskUpdated.value = Event(Unit)
        }
    }


    //在 Activity或者fragment中调用

    class AddEditTaskFragment : Fragment() {


      override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                 savedInstanceState: Bundle?): View? {
             val root = inflater.inflate(R.layout.addtask_frag, container, false)
             viewDataBinding = AddtaskFragBinding.bind(root).apply {
                 viewmodel = (activity as AddEditTaskActivity).obtainViewModel()

             }
             viewDataBinding.setLifecycleOwner(this.viewLifecycleOwner)

             viewDataBinding.viewmodel.taskUpdatedEvent.observe(this@AddEditTaskFragment.viewLifecycleOwner, Observer {
                  // 监听回调
               })
             setHasOptionsMenu(true)
             retainInstance = false
             return viewDataBinding.root
         }

    }

如上,解析步骤为:

  • viewmodel中创建LiveData对象

可以通过MutableLiveData来创建对象,初始情况下,LiveData包裹的value值为空

    // 双向绑定,开发title的访问权限
    val title = MutableLiveData<String>()

    // 双向绑定,开发description的访问权限
    val description = MutableLiveData<String>()

    private val _dataLoading = MutableLiveData<Boolean>()

    // 读取权限
    val dataLoading: LiveData<Boolean>
        get() =_dataLoading

    private val _taskUpdated = MutableLiveData<Event<Unit>>()
    val taskUpdatedEvent: LiveData<Event<Unit>>
            get() = _taskUpdated
  • 操作LiveData对象

可以手动操作修改LiveData数值,如数据库操作获取、网络请求等,也可以绑定xml通过手动输入同样达到修改的目的。

//同步操作  
_taskUpdated.setValue(Event(Unit))

//异步操作  
_taskUpdated.postValue(Event(Unit))
  • 监听LiveData对象

完成如上步骤的话就可以对LiveData里的value改变状态进行监听了,当然这有个前提在LifecycleOwner周期内,当然我们也可以通过 observeForever 进行长期监听,但这最好在不需要的时候通过人工使用** removeObserver 进行释放.

//周期内监听  
_taskUpdated.observe(this, Observer {  
// 监听回调  
})

//一直监听,不限周期  
_taskUpdated.observeForever(this, Observer {  
// 监听回调  
})

//释放observer  
_taskUpdated.removeObserver(observer)

自定义网络连接LiveData

我们可以通过自定义网络链接LiveData,然后通过监听该对象实现周期内的网络检测处理,如下:

    /**
     * @ClassName NetStateLiveData
     * @Description 网络状态监听,通过监听NetStateLiveData监听网络状态改变
     * @Author hfcai https://www.enjoytoday.cn
     * @Date 2019/4/4 9:56
     * @Version 1.0
     */
    class NetStateLiveData(val context: Context) :LiveData<Boolean>(){

        private val broadcastReceiver: BroadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(p0: Context?, p1: Intent?) {
                value = NetWorkUtils.isNetWorkAvailable(context)
            }
        }

        override fun onInactive() {
            super.onInactive()
            context.unregisterReceiver(broadcastReceiver)
        }

        override fun onActive() {
            super.onActive()
            val intentFilter = IntentFilter()
            intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
            context.registerReceiver(broadcastReceiver, intentFilter)
        }



        fun refresh(){


        }
    }


    class NetActivity:AppCompatActivity() {

        lateinit var netLiveData:NetStateLiveData


        override fun onCreate(savedInstanceState: Bundle?) {
           super.onCreate(savedInstanceState)
           netLiveData = NetStateLiveData(this)
           netLiveData..oserve(this, Observer {
                if (it) {
                    LogUtils.e("网络访问正常")
                }else{
                    LogUtils.e("网络访问异常")
                }
            })
       }


    }

LiveData在使用过程还有几个问题需要注意的,如下列出:

  • LiveData初始化不等于其内数据初始化 LiveData包裹的数据初始状态默认为null
  • LiveData的观察者LiveData 需要有观察者观察时才会调用onActive、onInactive
  • LiveData的onChange LiveData的value为一个对象时,当我们修改其中的属性时观察者并不能监听到修改,需要我们主动触发使用setValue或者postValue方法通知LiveData,如下可以发现其内通过version进行版本控制.
@MainThread  
protected void setValue(T value) {  
    assertMainThread(“setValue”);  
    mVersion++;  
    mData = value;  
    dispatchingValue(null);  
}
赞(3) 打赏
转载请附上原文出处链接:胖蔡说技术 » MVVM之LiveData的使用
分享到: 更多 (0)

请小编喝杯咖啡~

支付宝扫一扫打赏

微信扫一扫打赏