在我的Android应用中,我有一个片段,用户可以在其中同时查看和编辑某些对象的属性。
我正在使用具有数据绑定和保存正在编辑的Relation对象的介体实时数据的MVVM体系结构。它是这样工作的:
这里是问题:旋转屏幕时,碎片会被销毁并重新创建。但是我没有办法恢复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)将其存储在数据库中。
但是我不认为这在结构上是合理的。我也不知道是否行得通。
在这种情况下,您可以尝试Two Way Data Binding