如何使状态流< model> Androd中的多态性?

发布于 2025-02-13 12:16:46 字数 2558 浏览 3 评论 0原文

我正在舞台上,想将我的stateflow<()曝光到公共baseviewModel()类中以实现对其进行公共操作,而无需在其他实现中重复它们ViewModels。我最终提出了一个想法,但是在建造POC时面临着几个局限性。我的想法及其局限性在下面,您的原点问题解决方案受到欢迎。

我将模型分为接口和一个具体实现。

sealed interface Model {
    var isLoading: Boolean
    var errors: List<String>
    /**
     * @param obj Should has the same concrete type as concrete type of object which copy() it invokes
     * */
    fun copy(obj: Model): Model
}

data class DeputiesModel(
    override var isLoading: Boolean = false,
    override var errors: List<String> = emptyList<String>(),
    var deputies: List<Deputy> = emptyList()
) : Model {
    override fun copy(obj: Model): DeputiesModel {
        if (obj !is DeputiesModel)
            throw IllegalArgumentException("Passed object implements ${Model::javaClass.name}" +
                    " interface, but should be concrete ${DeputiesModel::javaClass::name} implementation.")
        return this.copy(deputies = obj.deputies, isLoading = obj.isLoading, errors = obj.errors)
    }
}

我需要接口中的copy()方法data class copy()已关闭以进行超载和覆盖。

我的stateflow实现已在baseViewModel()中移动,

abstract class BaseViewModel<T : Model> : ViewModel() {

    protected lateinit var state: MutableStateFlow<T>
    lateinit var uiState: StateFlow<T>

}

我在此处添加了generics,以避免施放model键入其一个具体实现,例如> deputiesModel ,在继承baseViewModel的类中,否则此额外代码将使整体意图揭示常见方法冗余。

这是第一个常见方法:

fun removeShownError() {
    state.update { state ->
        state.errors = state.errors.filter { str -> !str.equals(state.errors.first()) }
        state.copy(state) as T
    }
}

该设计的限制是state.copy.copy(state)不触发uistate.collectlatest {} nourm parameperized <代码> state.copy(isloading = false)做到了。我还没有找到它的根本原因。

    val viewModel: DeputiesViewModel by viewModels { viewModelFactory }
    lifecycleScope.launch {
        viewModel.uiState.collectLatest { it ->
            if (it.errors.isNotEmpty()) {
                showError(
                    view = requireActivity().findViewById(R.id.nav_view),
                    text = it.errors.first(),
                    onDismiss = { viewModel.removeShownError() }
                )
            }
            (binding.list.adapter as DeputiesAdapter).update(it.deputies)
        }
    }

就这样。您的想法得到赞赏。

I am at stage when I would like to expose my StateFlow<Model>() into the common BaseViewModel() class to implement common operations on it without repeating them in others implementations of ViewModels. I ended up with one idea, but faced with several limitations when has built PoC. My idea and its limitations are below, your solutions for origin question are welcomed.

I split my model into interface and one concrete implementation.

sealed interface Model {
    var isLoading: Boolean
    var errors: List<String>
    /**
     * @param obj Should has the same concrete type as concrete type of object which copy() it invokes
     * */
    fun copy(obj: Model): Model
}

data class DeputiesModel(
    override var isLoading: Boolean = false,
    override var errors: List<String> = emptyList<String>(),
    var deputies: List<Deputy> = emptyList()
) : Model {
    override fun copy(obj: Model): DeputiesModel {
        if (obj !is DeputiesModel)
            throw IllegalArgumentException("Passed object implements ${Model::javaClass.name}" +
                    " interface, but should be concrete ${DeputiesModel::javaClass::name} implementation.")
        return this.copy(deputies = obj.deputies, isLoading = obj.isLoading, errors = obj.errors)
    }
}

I need a copy() method in interface to data class copy() is closed for overloading and overriding.

My StateFlow implementation has been moved in BaseViewModel()

abstract class BaseViewModel<T : Model> : ViewModel() {

    protected lateinit var state: MutableStateFlow<T>
    lateinit var uiState: StateFlow<T>

}

I added generics here to avoid casting Model type to one of its concrete implementation, e.g. DeputiesModel, in classes which inherits BaseViewModel, otherwise this extra code would made a whole intent to expose common methods redundant.

And here is a first common method:

fun removeShownError() {
    state.update { state ->
        state.errors = state.errors.filter { str -> !str.equals(state.errors.first()) }
        state.copy(state) as T
    }
}

The limitation of this design&implementation is state.copy(state) doesn't trigger uiState.collectLatest{} call when origin parameterized state.copy(isLoading = false) does it. I haven't find yet the root cause of it.

    val viewModel: DeputiesViewModel by viewModels { viewModelFactory }
    lifecycleScope.launch {
        viewModel.uiState.collectLatest { it ->
            if (it.errors.isNotEmpty()) {
                showError(
                    view = requireActivity().findViewById(R.id.nav_view),
                    text = it.errors.first(),
                    onDismiss = { viewModel.removeShownError() }
                )
            }
            (binding.list.adapter as DeputiesAdapter).update(it.deputies)
        }
    }

That's all. Your ideas are appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

薯片软お妹 2025-02-20 12:16:46

以下是您的常见函数示例removeshownError()可以通过扩展功能求解,因此您不需要baseviewModel。

没有突变性的模型类:

sealed interface Model {
    val isLoading: Boolean
    val errors: List<String>
}

data class DeputiesModel(
    override val isLoading: Boolean = false,
    override val errors: List<String> = emptyList<String>(),
    val deputies: List<Deputy> = emptyList()
) : Model 

共享功能的扩展功能,在最高级别定义的某个地方定义:

// Since this is a sealed interface, you can create a single extension function to
// implement clearing errors for each of the implementations. This will be cleaner
// than having to deal with wrong input type problem in your current copy function
// implementation.
@Suppress("UNCHECKED_CAST")
fun <T: Model> T.withNewErrors(newErrors: List<String>): T = when (this) {
    is DeputiesModel -> copy(errors = newErrors)
    //... other types
} as T

fun <T: Model> MutableStateFlow<T>.removeShownError() {
    update { state ->
        state.withNewErrors(
            state.errors.filter { str -> !str.equals(state.errors.first()) }
        )
    }
}

现在不需要baseviewModel。您只需添加任何具有相关流的视图模型:

fun removeShownError() = state.removeShownError()

顺便说一句,在ViewModel中使用LateInit并没有多大意义。这是诸如活动之类的课程所需的黑客攻击您自己的任何属性都将用于任何事物。在ViewModel中,您可能需要使用的所有内容都可以通过构造函数进行类初始化,因此没有理由将属性的初始化推迟。

即使您确实必须使用lateinit为此,也无需将其用于合作伙伴的不可用的公共财产,因为该属性只能使用自定义Getter,因此它没有备份变量首先初始化:

val uiState: StateFlow<T> get() = state

Here's how your example of the common function removeShownError() could be solved with an extension function so you don't need a BaseViewModel.

Model classes without mutability:

sealed interface Model {
    val isLoading: Boolean
    val errors: List<String>
}

data class DeputiesModel(
    override val isLoading: Boolean = false,
    override val errors: List<String> = emptyList<String>(),
    val deputies: List<Deputy> = emptyList()
) : Model 

Extension functions for shared functionality, defined at the top level somewhere:

// Since this is a sealed interface, you can create a single extension function to
// implement clearing errors for each of the implementations. This will be cleaner
// than having to deal with wrong input type problem in your current copy function
// implementation.
@Suppress("UNCHECKED_CAST")
fun <T: Model> T.withNewErrors(newErrors: List<String>): T = when (this) {
    is DeputiesModel -> copy(errors = newErrors)
    //... other types
} as T

fun <T: Model> MutableStateFlow<T>.removeShownError() {
    update { state ->
        state.withNewErrors(
            state.errors.filter { str -> !str.equals(state.errors.first()) }
        )
    }
}

Now there's no need for a BaseViewModel. You can just add to any ViewModel that has a relevant flow:

fun removeShownError() = state.removeShownError()

By the way, it doesn't make much sense to use lateinit in a ViewModel. That's a hack needed for classes like Activity where most of what you need to do to initialize variables requires Context, but the context isn't ready at class instantiation time and create() is the earliest point at which any of your own properties will be used for anything. In a ViewModel, everything you could possibly need to use is already available at class initialization via the constructor, so there's no reason to postpone initialization of a property.

Even if you did have to use lateinit for it, it wouldn't be necessary to use it for it's partner non-mutable public property, because that property could just use a custom getter, so it has no backing variable to initialize in the first place:

val uiState: StateFlow<T> get() = state
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文