[Android数据绑定+中介器实时数据-处理生命周期事件

问题描述 投票:1回答:1

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

我正在使用具有数据绑定和保存正在编辑的Relation对象的介体实时数据的MVVM体系结构。它是这样工作的:

  1. 片段使视图膨胀并绑定视图(布局xml)。
  2. 在此过程中,片段已创建了ViewModel。
  3. ViewModel将从数据库中获取Relation对象(及其属性),并将其放入MediatorLiveData中。
  4. 由于数据绑定和绑定适配器,editText字段自动设置为对象的属性。
  5. 然后用户可以编辑这些editText字段并保存。
  6. 保存后,ViewModel将从editTexts中获取文本,并使用它们来更新本地数据库中的Relation对象

这里是问题:旋转屏幕时,碎片会被销毁并重新创建。但是我没有办法恢复editText的内容。绑定只会重置editText的内容(因为我们实际上还没有更新Relation对象的属性,因此只有在用户按下“保存”时才这样做)。

我不能使用Bundle / savedInstanceState,因为绑定只会覆盖它。使用MediatorLiveData来保存编辑的内容也不起作用,因为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}" />

片段本身:

class RelationDetailFragment : Fragment() {


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

        val application = requireNotNull(this.activity).application

        val arguments = RelationDetailFragmentArgs.fromBundle(arguments!!)

        val relationDataSource = FarFiledDatabase.getInstance(application).relationDao

        val relationLifeAreaDataSource = FarFiledDatabase.getInstance(application).relationLifeAreaDao

        val viewModelFactory =
            RelationDetailViewModelFactory(
                arguments.relationId,
                relationDataSource,
                relationLifeAreaDataSource,
                application
            )

        val relationDetailViewModel =
            ViewModelProviders.of(
                this, viewModelFactory).get(RelationDetailViewModel::class.java)

        binding.relationDetailViewModel = relationDetailViewModel

        binding.setLifecycleOwner(this)

        // stuff about chips

        val textWatcher = object: TextWatcher{ /* */ }

        binding.saveButton.isEnabled = false

        binding.relationNameEditText.addTextChangedListener(textWatcher)
        binding.relationSynopsisEditText.addTextChangedListener(textWatcher)
        binding.lifeAreaNowEditText.addTextChangedListener(textWatcher)
        // etc.

        relationDetailViewModel.enableSaveButton.observe(this, Observer{ /* */})

        relationDetailViewModel.showNameEmptySnackbar.observe(this, Observer{ /* */})

        relationDetailViewModel.navigateToRelationsList.observe(this, Observer{ /* */})

        return binding.root
    }
}

视图模型:

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. -->

所以我的问题是:我该如何处理?我认为唯一的解决方案是:1)保留一个具有EDITED属性的Relation对象,每当用户编辑editText时更新该对象,2)将其存储在数据库中。

但是我不认为这在结构上是合理的。我也不知道是否行得通。

android-fragments mvvm android-lifecycle android-databinding android-livedata
1个回答
0
投票

在这种情况下,您可以尝试Two Way Data Binding

© www.soinside.com 2019 - 2024. All rights reserved.