Android fragments 安卓数据绑定&x2B;Mediator实时数据-处理生命周期事件

Android fragments 安卓数据绑定&x2B;Mediator实时数据-处理生命周期事件,android-fragments,mvvm,android-lifecycle,android-databinding,android-livedata,Android Fragments,Mvvm,Android Lifecycle,Android Databinding,Android Livedata,在我的Android应用程序中,我有一个片段,用户可以同时查看和编辑某些对象的属性 我正在使用一个带有数据绑定的MVVM体系结构和一个保存正在编辑的关系对象的中介实时数据。下面是它的工作原理: 片段膨胀并绑定视图(布局xml) 在此过程中,片段生成了一个ViewModel ViewModel将从数据库中获取关系对象(及其属性),并将其放在LiveData中 由于数据绑定和绑定适配器,editText字段自动设置为对象的属性 然后,用户可以编辑这些编辑文本字段并保存 保存后,ViewModel将从

在我的Android应用程序中,我有一个片段,用户可以同时查看和编辑某些对象的属性

我正在使用一个带有数据绑定的MVVM体系结构和一个保存正在编辑的关系对象的中介实时数据。下面是它的工作原理:

  • 片段膨胀并绑定视图(布局xml)
  • 在此过程中,片段生成了一个ViewModel
  • ViewModel将从数据库中获取关系对象(及其属性),并将其放在LiveData中
  • 由于数据绑定和绑定适配器,editText字段自动设置为对象的属性
  • 然后,用户可以编辑这些编辑文本字段并保存
  • 保存后,ViewModel将从EditText中获取文本,并使用它们更新本地数据库中的关系对象
  • 问题是:旋转屏幕时,碎片会被破坏并重新创建。但是我没有办法恢复editText的内容。绑定只会重置editText内容(因为我们实际上还没有更新关系对象属性,我们只在用户按下“保存”时才这样做)

    我不能使用Bundle/savedInstanceState,因为绑定只会覆盖它。使用MediatorLiveData保存编辑的内容也不会起作用,因为ViewModel在旋转时会被破坏,所以我们会丢失这些数据

    片段布局的一部分。注意relationNameEditText中的数据变量(viewmodel)和数据绑定:

    <?xml version="1.0" encoding="utf-8"?>
    <layout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:tools="http://schemas.android.com/tools"
        tools:context=".presentation.relationdetail.RelationDetailFragment">
    
        <data>
            <variable
                name="relationDetailViewModel"
                type="be.pjvandamme.farfiled.presentation.relationdetail.RelationDetailViewModel" />
        </data>
    
            <ScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent">
    
                <androidx.constraintlayout.widget.ConstraintLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_margin="@dimen/relationDetailLayoutMargin">
    
                    <TextView
                        android:id="@+id/nameLabelTextView"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:layout_marginStart="24dp"
                        android:layout_marginBottom="8dp"
                        android:text="@string/nameLabel"
                        app:layout_constraintBottom_toTopOf="@+id/relationNameEditText"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintHorizontal_bias="0.0"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toTopOf="parent" />
    
                    <EditText
                        android:id="@+id/relationNameEditText"
                        android:layout_width="@dimen/relationNameEditWidth"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="8dp"
                        android:layout_marginBottom="16dp"
                        android:ems="10"
                        android:inputType="textPersonName"
                        app:layout_constraintBottom_toTopOf="@+id/editTextChips"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintHorizontal_bias="0.0"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toBottomOf="@+id/nameLabelTextView"
                        app:relationName="@{relationDetailViewModel.relation}" />
    
    viewmodel:

    class RelationDetailViewModel (
        private val relationKey: Long?,
        val relationDatabase: RelationDao,
        val relationLifeAreaDatabase: RelationLifeAreaDao,
        application: Application
    ): AndroidViewModel(application) {
    
        private var viewModelJob = Job()
        private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    
        private val relation = MediatorLiveData<Relation?>()
        fun getRelation() = relation
    
        private val relationLifeAreas = MediatorLiveData<List<RelationLifeArea?>>()
        fun getRelationLifeAreas() = relationLifeAreas
    
        // other LiveData's with backing properties, to trigger UI events
    
        init {
            initializeRelation()
        }
    
        private fun initializeRelation(){
            if(relationKey == null || relationKey == -1L) {
                initializeNewRelation()
                getAdorableAvatarFacialFeatures()
            }
            else {
                retrieveAvatarUrl()
                relation.addSource(
                    relationDatabase.getRelationWithId(relationKey),
                    relation::setValue)
                relationLifeAreas.addSource(
                    relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(relationKey),
                    relationLifeAreas::setValue)
            }
        }
    
        private fun initializeNewRelation(){
            uiScope.launch{
                var relationId = insert(Relation(0L,"","",null,false))
                initializeLifeAreasForRelation(relationId)
                relation.addSource(
                    relationDatabase.getRelationWithId(
                            relationId!!),
                    relation::setValue)
                relationLifeAreas.addSource(
                    relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(
                        relationId!!),
                    relationLifeAreas::setValue)
            }
        }
    
        private fun initializeLifeAreasForRelation(relationId: Long?){
            if(relationId != null){
                enumValues<LifeArea>().forEach {
                    uiScope.launch{
                        var relationLifeArea = RelationLifeArea(0L,relationId,it,"")
                        insert(relationLifeArea)
                    }
                }
            }
        }
    
        private fun retrieveAvatarUrl(){
            uiScope.launch{
                var rel = get(relationKey!!)
                var avatarUrl = rel?.avatarUrl
                if (avatarUrl.isNullOrEmpty()){
                    getAdorableAvatarFacialFeatures()
                    _enableSaveButton.value = true
                }
                else
                    _adorableAvatarString.value = rel?.avatarUrl
            }
        }
    
        private fun getAdorableAvatarFacialFeatures(){
            uiScope.launch{
                var getFeaturesDeferred = AdorableAvatarApi.retrofitService.getFacialFeatures()
                try{
                    var result = getFeaturesDeferred.await()
                    _adorableAvatarString.value = "https://api.adorable.io/avatars/face/" +
                            result.features.eyes.shuffled().take(1)[0] + "/" +
                            result.features.nose.shuffled().take(1)[0] + "/" +
                            result.features.mouth.shuffled().take(1)[0] + "/" +
                            result.features.COLOR_PALETTE.shuffled().take(1)[0]
                    relation.value?.avatarUrl = _adorableAvatarString.value
                } catch(t:Throwable){
                    // ToDo: what if this fails?? -> Try again later!!
                    _adorableAvatarString.value = "Failure: " + t.message
                }
            }
        }
    
        fun onEditRelation(
            relationNameText: String,
            relationSynopsisText: String,
            lifeAreaNowText: String,
            // etc.
        ){
            _enableSaveButton.value = !compareRelationAttributes(
                relationNameText,
                relationSynopsisText,
                lifeAreaNow.Text,
                // etc
            )
        }
    
        private fun compareRelationAttributes(
            relationNameText: String,
            relationSynopsisText: String,
            lifeAreaNowText: String,
            // etc.
        ): Boolean {
            // checks if any of the attributes of the Relation object were changed
            // i.e. at least 1 of the editText fields has a text content that does
            // does not equal the corresponding attribute of the Relation object
        }
    
        fun onSave(
            name: String,
            synopsis: String,
            nowLA: String,
            // etc.
        ){
            if(!name.isNullOrEmpty()) {
                uiScope.launch {
                    // update the DB
                }
                // TODO: this one should go away, need some sort of up button instead
                _navigateToRelationsList.value = true
            }
            else
                _showNameEmptySnackbar.value = true
        }
    
        // database suspend funs omitted
    
        // ui event handling functions
    
        override fun onCleared(){ /* cancel the viewModelJob */ }
    
    }
    
    类关系详细视图模型(
    private val relationKey:长?,
    val relationDatabase:RelationDao,
    val RelationLifeArea数据库:RelationLifeAreaDao,
    应用程序:应用程序
    ):AndroidViewModel(应用程序){
    私有变量viewModelJob=Job()
    private val uiScope=CoroutineScope(Dispatchers.Main+viewModelJob)
    private val relation=MediatorLiveData()
    fun getRelation()=关系
    private val relationLifeAreas=MediatorLiveData()
    乐趣getRelationLifeAreas()=relationLifeAreas
    //具有支持属性的其他LiveData,用于触发UI事件
    初始化{
    初始化()
    }
    私人娱乐初始化(){
    如果(relationKey==null | | relationKey==1L){
    初始化
    getAdorableAvatarFacialFeatures()
    }
    否则{
    RetrieveAvataUrl()
    relationship.addSource(
    relationDatabase.getRelationWithId(relationKey),
    关系::设置值)
    relationLifeAreas.addSource(
    RelationLifeArea数据库。GetAllRelationLifeAreas关联(relationKey),
    relationLifeAreas::setValue)
    }
    }
    私人娱乐初始值化(){
    发射{
    var relationId=insert(关系(0L,“,”,null,false))
    初始化关系(relationId)
    relationship.addSource(
    relationDatabase.getRelationWithId(
    relationId!!),
    关系::设置值)
    relationLifeAreas.addSource(
    relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(
    relationId!!),
    relationLifeAreas::setValue)
    }
    }
    private fun初始值EliFeareasForRelation(关系ID:Long?){
    if(relationId!=null){
    enumValues().forEach{
    发射{
    var relationLifeArea=relationLifeArea(0L,relationId,it,“”)
    插入(relationLifeArea)
    }
    }
    }
    }
    私人娱乐检索{
    发射{
    var rel=get(relationKey!!)
    变量avatarUrl=rel?.avatarUrl
    if(avatarUrl.isNullOrEmpty()){
    getAdorableAvatarFacialFeatures()
    _enableSaveButton.value=true
    }
    其他的
    _可爱的化身字符串.value=rel?.avatarUrl
    }
    }
    私人娱乐getAdorableAvatarFacialFeatures(){
    发射{
    var getFeaturesFerred=AdorableAvatarApi.RefughtService.getFacialFeatures()
    试一试{
    var result=getFeaturesDeferred.await()
    _adorableAvatarString.value=”https://api.adorable.io/avatars/face/" +
    result.features.eyes.shuffled().take(1)[0]+“/”+
    result.features.nose.shuffled().take(1)[0]+“/”+
    result.features.mouth.shuffled().take(1)[0]+“/”+
    result.features.COLOR_palete.shuffled().take(1)[0]
    relation.value?.avatarUrl=\u adorableAvatarString.value
    }捕获(t:可丢弃){
    //ToDo:如果失败怎么办??->请稍后再试!!
    _adorableAvatarString.value=“失败:”+t.message
    }
    }
    }
    有趣的关系(
    relationNameText:String,
    relationSynopsisText:String,
    lifeAreaNowText:String,
    //等等。
    ){
    _enableSaveButton.value=!compareRelationAttributes(
    关系名称文本,
    relationSynopsisText,
    lifeAreaNow.Text,
    //等
    )
    }
    私人乐趣比较属性(
    relationNameText:String,
    relationSynopsisText:String,
    lifeAreaNowText:String,
    //等等。
    ):布尔值{
    //检查关系对象的任何属性是否已更改
    //也就是说,至少有一个editText字段的文本内容
    //不等于关系对象的相应属性
    }
    快乐在储蓄(
    名称:String,
    剧情简介:弦,
    诺拉:弦,
    //等等。
    ){
    如果(!name.isNullOrEmpty()){
    发射{
    //更新数据库
    
    class RelationDetailViewModel (
        private val relationKey: Long?,
        val relationDatabase: RelationDao,
        val relationLifeAreaDatabase: RelationLifeAreaDao,
        application: Application
    ): AndroidViewModel(application) {
    
        private var viewModelJob = Job()
        private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
    
        private val relation = MediatorLiveData<Relation?>()
        fun getRelation() = relation
    
        private val relationLifeAreas = MediatorLiveData<List<RelationLifeArea?>>()
        fun getRelationLifeAreas() = relationLifeAreas
    
        // other LiveData's with backing properties, to trigger UI events
    
        init {
            initializeRelation()
        }
    
        private fun initializeRelation(){
            if(relationKey == null || relationKey == -1L) {
                initializeNewRelation()
                getAdorableAvatarFacialFeatures()
            }
            else {
                retrieveAvatarUrl()
                relation.addSource(
                    relationDatabase.getRelationWithId(relationKey),
                    relation::setValue)
                relationLifeAreas.addSource(
                    relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(relationKey),
                    relationLifeAreas::setValue)
            }
        }
    
        private fun initializeNewRelation(){
            uiScope.launch{
                var relationId = insert(Relation(0L,"","",null,false))
                initializeLifeAreasForRelation(relationId)
                relation.addSource(
                    relationDatabase.getRelationWithId(
                            relationId!!),
                    relation::setValue)
                relationLifeAreas.addSource(
                    relationLifeAreaDatabase.getAllRelationLifeAreasForRelation(
                        relationId!!),
                    relationLifeAreas::setValue)
            }
        }
    
        private fun initializeLifeAreasForRelation(relationId: Long?){
            if(relationId != null){
                enumValues<LifeArea>().forEach {
                    uiScope.launch{
                        var relationLifeArea = RelationLifeArea(0L,relationId,it,"")
                        insert(relationLifeArea)
                    }
                }
            }
        }
    
        private fun retrieveAvatarUrl(){
            uiScope.launch{
                var rel = get(relationKey!!)
                var avatarUrl = rel?.avatarUrl
                if (avatarUrl.isNullOrEmpty()){
                    getAdorableAvatarFacialFeatures()
                    _enableSaveButton.value = true
                }
                else
                    _adorableAvatarString.value = rel?.avatarUrl
            }
        }
    
        private fun getAdorableAvatarFacialFeatures(){
            uiScope.launch{
                var getFeaturesDeferred = AdorableAvatarApi.retrofitService.getFacialFeatures()
                try{
                    var result = getFeaturesDeferred.await()
                    _adorableAvatarString.value = "https://api.adorable.io/avatars/face/" +
                            result.features.eyes.shuffled().take(1)[0] + "/" +
                            result.features.nose.shuffled().take(1)[0] + "/" +
                            result.features.mouth.shuffled().take(1)[0] + "/" +
                            result.features.COLOR_PALETTE.shuffled().take(1)[0]
                    relation.value?.avatarUrl = _adorableAvatarString.value
                } catch(t:Throwable){
                    // ToDo: what if this fails?? -> Try again later!!
                    _adorableAvatarString.value = "Failure: " + t.message
                }
            }
        }
    
        fun onEditRelation(
            relationNameText: String,
            relationSynopsisText: String,
            lifeAreaNowText: String,
            // etc.
        ){
            _enableSaveButton.value = !compareRelationAttributes(
                relationNameText,
                relationSynopsisText,
                lifeAreaNow.Text,
                // etc
            )
        }
    
        private fun compareRelationAttributes(
            relationNameText: String,
            relationSynopsisText: String,
            lifeAreaNowText: String,
            // etc.
        ): Boolean {
            // checks if any of the attributes of the Relation object were changed
            // i.e. at least 1 of the editText fields has a text content that does
            // does not equal the corresponding attribute of the Relation object
        }
    
        fun onSave(
            name: String,
            synopsis: String,
            nowLA: String,
            // etc.
        ){
            if(!name.isNullOrEmpty()) {
                uiScope.launch {
                    // update the DB
                }
                // TODO: this one should go away, need some sort of up button instead
                _navigateToRelationsList.value = true
            }
            else
                _showNameEmptySnackbar.value = true
        }
    
        // database suspend funs omitted
    
        // ui event handling functions
    
        override fun onCleared(){ /* cancel the viewModelJob */ }
    
    }
    
    @BindingAdapter("relationName")
    fun TextView.setRelationName(item: Relation?){
        item?.let{
            text = item.name
        }
    }
    
    @BindingAdapter("relationSynopsis")
    fun TextView.setRelationSynopsis(item: Relation?){
        item?.let{
            text = item.synopsis
        }
    }
    
    @BindingAdapter("relationLifeAreaNow")
    fun TextView.setLifeAreaNowText(item: List<RelationLifeArea?>?){
        item?.let{
            item.forEach{
                if(it?.lifeArea == LifeArea.EPHEMERA){
                    text = it.content
                }
            }
        }
    }
    <!-- etc. -->