liangjingkanji / BRV

[使用文档] Android 快速构建 RecyclerView, 比 BRVAH 更简单强大
http://liangjingkanji.github.io/BRV/
MIT License
2.59k stars 327 forks source link

树形列表全部展开,用ViewModol处理数据恢复时,子列表重复出现 #171

Closed wwy863399246 closed 2 years ago

wwy863399246 commented 2 years ago

问题描述

mViewModel.catalogueDataState.observe(viewLifecycleOwner) {
    it.successData?.let { list ->
        mDatabind.apply {
            if (!isOnPause) {
                pmRv.bindingAdapter.models = list.onEach { book ->
                    book.itemExpand = true
                }
            } else {
                pmRv.setDifferModels(list)
                (pmRv.models as List<BookDataModel>).forEachIndexed { index, bookDataModel ->
                    if (bookDataModel.isChecked) {
                        pmRv.bindingAdapter.setChecked(index, true)
                    }
                }
            }
            pmStateLayout.showContent()
        }
    }
    it.showError?.let {
        mDatabind.pmStateLayout.showError()
    }
}

返回页面,用pmRv.setDifferModels(list) 恢复树形列表数据,不会出现这个问题

期望行为

如何复现

建议步骤:

截图

异常堆栈信息或者手机截图/视频

版本

liangjingkanji commented 2 years ago

无法看出问题所在, 你可以fork仓库在sample中复现问题吗?

wwy863399246 commented 2 years ago

当前页面树形目录页,用navigation跳转到下一界面返回时,重新设置数据时,展开的子列表会重复出现,折叠的不会

liangjingkanji commented 2 years ago

你是不是给itemSublist重复赋值

wwy863399246 commented 2 years ago

没有使用itemSublist,只在解析时用到,设置数据只用到 pmRv.bindingAdapter.models = list.onEach { book -> book.itemExpand = true }

@Serializable data class BookDataModel( @SerialName("chapterName") val chapterName: String, @SerialName("chapterCode") val chapterCode: String, @SerialName("level") val level: Int, @SerialName("isChecked") var isChecked: Boolean = false, @SerialName("belongBook") val belongBook: Int, @SerialName("bookName") var bookName: String = "", @SerialName("hasChild") val hasChild: Boolean, @SerialName("report_record") val reportRecord: Int = 0, @SerialName("mistake_count") val mistakeCount: Int = 0, @SerialName("upload_status") val uploadStatus: Int = 0, @SerialName("mistake_list") val mistakeList: List = emptyList(), @SerialName("children") var children: List? ) : ItemExpand, ItemHover, ItemPosition, BaseObservable() { override var itemGroupPosition: Int = 0 override var itemExpand: Boolean = false set(value) { field = value notifyChange() }

// 这种代理方式是为了避免Gson等框架解析Kotlin会覆盖默认值问题: https://liangjingkanji.github.io/BRV/group.html#_2
override var itemSublist: List<Any?>?
    get() = children
    set(value) {
        children = value as List<BookDataModel>
    }

override var itemHover: Boolean = true
override var itemPosition: Int = 0

}

liangjingkanji commented 2 years ago

那你早点fork仓库复现问题吧, 这样解决问题速度快的多

而且sample也正是使用的navigation实现的

liangjingkanji commented 2 years ago

另外你不觉得你的网络请求稍显复杂和冗余吗? 而且你这完全无法支持并发请求吧, 推荐你使用Net

wwy863399246 commented 2 years ago

我就是用的net,但是得做数据恢复,所以请求写在viewmodel里面,仓库没拉下来,拉下来后试试能不能复现吧,我现在用 pmRv.setDifferModels(list)可以解决这个问题,只是得加个判断

liangjingkanji commented 2 years ago

快复现, 我不允许有问题遗留到第二天

liangjingkanji commented 2 years ago

数据恢复你把请求回来的数据赋值给ViewModel对象里面的某个字段不就好了吗? 然后判断下对象是否为空, 空则重新请求

为什么还得写liveData观察者?

wwy863399246 commented 2 years ago

用LiveData是因为我创建了网络请求状态类,用来设置界面状态,我现在遇到的问题是因为保存的数据变了,我这边是无线层级,第一次请求用上面的模型解析后,拿到的是所有的父条目,假如是5条。列表设置全部展开后,跳转页面回来后我保存的数据条目变成了父条目加上子条目的数量。

wwy863399246 commented 2 years ago

无限层级解析,是因为我解析类写的有问题吗?我用你的demo里面的假数据做数据恢复没有问题

liangjingkanji commented 2 years ago

启用分组后models会变成父条目+子条目的, 这是无可避免的

另外我极度不推荐使用LiveData去接收网络请求结果, 你这是本末倒置, 依然使用的是接口回调去处理异步任务, 页面关闭应当真正取消网络请求而不是仅仅不监听请求结果

而且网络错误应当抛出异常交由全局异常处理器去处理(比如收集到服务器)而不是设置一个错误状态, 你整个应用的网络请求都有问题

wwy863399246 commented 2 years ago

@HiltViewModel class ReportMistakePublishViewModel @Inject constructor() : BaseViewModel() {

private val _catalogueDataState =
    MutableLiveData<StateModel<List<BookDataModel>>>()
val catalogueDataState: LiveData<StateModel<List<BookDataModel>>>
    get() = _catalogueDataState

fun getBookCatalogue(classId: Int, bookId: Int) = scopeNetLife {
    _catalogueDataState.value = StateModel(showLoading = true)
    appToken?.let {
        val data = Get<Book>("teacher/class/wrong/question/catalogue/info") {
            param("token", it)
            param("class_id", classId)
            param("book_id", bookId)
        }.await()
        _catalogueDataState.value = StateModel(successData = data.mistakeCatalogue)
    }
}.catch {
    _catalogueDataState.value = StateModel(showError = it.message ?: "")
}

val mistake = ObservableInt(0)
val section = ObservableInt(0)

} 这是我的viewmodel类 我需要设置界面状态,应该怎样处理才好。

wwy863399246 commented 2 years ago

我现在用的是单Activity多fragment,Navigation导航,需要处理界面状态恢复,所以得使用viewmodel。为了不重复请求网络并且设置正确设置界面状态,所以写在viewmodel里面

liangjingkanji commented 2 years ago

单Activity.... 你挺会折腾的, 你不会还是什么MVI架构吧

写代码已经够难了, 你还强行给自己加料增加难度

liangjingkanji commented 2 years ago

Net支持在ViewModel里面网络请求并且监听ViewModel生命周期, 我建议你将Post等方法写到ViewModel但是scope**还是写到Activity中, 他们可以监听生命周期

wwy863399246 commented 2 years ago

好的,我现在写的MVVM接近MVI吧,单向数据流动。目前用Net和BRV没啥问题,就是Net的Catch作用域里面不能用flow发送值,所以用livedata发送状态,单Activity很难受,复杂点的fragement页面要保存很多状态。

liangjingkanji commented 2 years ago

所以我一直极度不推荐使用SingleActivity的写法, 遇到的坑非常多, 很多框架你甚至不支持, 而且你需要考虑的东西太多了

而你收获到什么优势? 页面速度快? 你感受到了多少?

liangjingkanji commented 2 years ago
  1. 只推荐简单的MVVM设计模式, 数据和UI的双向绑定的确优势明显, 代码量少数据安全. 可能有人觉得MVVM太简单了不够卷所以发明了MVI

  2. 无论是插件化/组件化/MVI亦或是滥用ViewModel/LiveData我都建议看清楚技术本质, 而不是人云亦云或者套模板

  3. 如果页面不需要数据恢复完全不需要使用ViewModel

  4. 网络请求属于单次数据流完全不适用Flow, 如果需求是对数据进行数据流处理完全可以使用自带的集合stream扩展函数

  5. 不是把代码挪个位置就是优化

你可以看下BRV使用MVVM实现一个聊天列表有多简单直接

wwy863399246 commented 2 years ago

确实,前期坑真的很多,都快被我踩完了,我现在简单的页面只要不做数据恢复,用BRV和Net还有databing开发挺爽的。

liangjingkanji commented 2 years ago

你解决数据问题了吗? 是models包含子条目的问题是吧