kotlin 流_Kotlin多平台世界中的单向数据流mvi

kotlin 流

MVI stands for Model-View-Intent.

MVI代表Model-View-Intent

Recently this has been a hot topic in our developer community and there has been lot of libraries that solve this problem, but it becomes too overwhelming to understand the actual inner working behind it.

最近,这已成为我们开发人员社区中的热门话题,并且有很多库可以解决此问题,但是对于了解其背后的实际内部工作而言,它变得势不可挡。

If you are new to MVI I’d suggest you to read a thorough series of post http://hannesdorfmann.com/android/mosby3-mvi-1

如果您不熟悉MVI,建议您阅读一系列详尽的文章http://hannesdorfmann.com/android/mosby3-mvi-1

In this post we would go through how we can setup our own uni-directional data flow architecture using coroutines & flow in kotlin multi-platform.

在这篇文章中,我们将介绍如何在Kotlin多平台中使用协程和流来设置自己的单向数据流体系结构。

To build this MVI flow we would be looking at following interface

要构建此MVI流,我们将查看以下界面

Action : It is mapped as user input in the screen like click or swipe.

行动 :它被映射为用户点击在屏幕上的输入,如点击或滑动。

Result: The actions or the user input are passed to viewmodel. It filters the actions by it type and process it. Every action generates a Result. A new State is created by copying the current State and changing the necessary bits according to the Result.

结果 :操作或用户输入被传递到视图模型。 它按类型过滤并处理操作。 每个动作都会生成一个Result 。 通过复制当前状态并根据结果更改必要的位来创建新状态。

State: It is the View state of the screen. Whatever the View shows on screen is represented by a state.

状态:这是屏幕的查看状态 视图在屏幕上显示的任何内容均由状态表示。

This interface are than subclass by sealed classes, except for state that is a data class. So using sealed class this makes sure every action is mapped to a result.

该接口是密封类的子类,但状态是数据类。 因此,使用密封类可确保将每个操作映射到结果。

This modeling incorporates every actions or input the user can trigger and what will be the resultant change in the screen.

这种建模方法结合了用户可以触发的每项动作或输入,以及屏幕上将发生的最终变化。

data class NewsListState(
    val loading: Boolean = false,
    val newsList: NewsList? = null,
    val error: String =  "",
    val showError: Boolean = false
) : State


sealed class NewsListAction : Action {
    object LoadNews : NewsListAction()
}


sealed class NewsListResult : Result {
    object Loading : NewsListResult()
    data class Result(val newsList: NewsList) : NewsListResult()
    data class Error(val error: String) : NewsListResult()
}

As, we see the architecture really forces us to model it in a particular structure. Every actions should be listened by Viewmodel.

正如我们看到的那样,该架构确实迫使我们在特定结构中对其进行建模。 Viewmodel应该监听每个动作。

For actions we would be using Channels.

对于动作,我们将使用Channels

It is is how we can transfer streams of values in Coroutines. It is similar to RxJava subject or MutableLiveData.

这就是我们可以在协程中传递值流的方式。 它类似于RxJava主题或MutableLiveData

private val actions: Channel<A> = Channel(Channel.UNLIMITED)

We would be using offer(element: E) method to send items in channel.

我们将使用offer(element: E) 在频道中发送项目的方法。

This action is listened by viewmodel, actions are processed to generate Result.

这个动作被viewmodel监听,动作被处理以生成Result

To follow the reactive approach the actionToResults method should return a stream. Flow is similar like Observables in RxJava or LiveData.

要遵循React式方法,actionToResults方法应返回流。 流类似于RxJava或LiveData中的 Observables。

abstract suspend fun actionToResults(action: A): Flow<R>

This method is responsible to filter the actions and generate the result and pass it in the stream

此方法负责过滤操作并生成结果并将其传递到流中

suspend fun actionToResults(action: NewsListAction): Flow<NewsListResult> {
        return when (action) {
            is NewsListAction.LoadNews ->
                newsRepository.getNewsSource().map {
                    when (it) {
                        is Lce.Loading -> NewsListResult.Loading


                        is Lce.Content -> NewsListResult.Result(it.packet)


                        else -> NewsListResult.Error("Something went wrong")
                    }
                }.flowOn(ioCoroutineDispatcher)
        }
    }

Suspending functions aren’t compiled to ObjC so we can’t use those on iOS but thanks to CFlow from KotlinConf app, we are able to do so. See this for the source code they have solved this use case in their app. This is one of the way of solving this.

暂挂功能未编译到ObjC,因此我们无法在iOS上使用这些功能,但多亏KotlinConf应用程序CFlow了CFlow,我们才能够这样做。 请参阅以获取他们已在其应用程序中解决此用例的源代码。 这是解决此问题的方法之一。

import io.ktor.utils.io.core.*
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*


fun <T> ConflatedBroadcastChannel<T>.wrap(): CFlow<T> = CFlow(asFlow())


fun <T> Flow<T>.wrap(): CFlow<T> = CFlow(this)


class CFlow<T>(private val origin: Flow<T>) : Flow<T> by origin {
    fun watch(block: (T) -> Unit): Closeable {
        val job = Job(/*ConferenceService.coroutineContext[Job]*/)


        onEach {
            block(it)
        }.launchIn(CoroutineScope(dispatcher() + job))


        return object : Closeable {
            override fun close() {
                job.cancel()
            }
        }
    }
}

Now we have actions and results. Let us discuss about State now.

现在我们有了行动和结果。 让我们现在讨论状态

ViewModel should have a way to communicate to view when there is change in state. So to solve this we would be using, Channels again but ConflatedBroadcastChannel.

当状态发生变化时,ViewModel应该可以进行通信以进行查看。 因此,要解决此问题,我们将再次使用Channels,但使用ConflatedBroadcastChannel

ConflatedBroadcastChannel : Whenever Back-to-back states elements are send— only the the most recently sent value is received, while previously sent elements are lost.

ConflatedBroadcastChannel:每当发送背对背状态元素时-仅接收到最近发送的值,而先前发送的元素会丢失

For coroutines in multiplatform world to work in Main thread, we would be using expect/actual

为了使多平台世界中的协程在线程中工作,我们将使用期望/实际

The expect keyword declares that the common code can expect different actual implementations for each platform. We can use this for things which are platform specific.

expect关键字声明公用代码可以期望每个平台的不同actual实现。 我们可以将其用于特定于平台的事物。

import kotlinx.coroutines.CoroutineDispatcher


expect fun uiDispatcher(): CoroutineDispatcher


expect fun ioDispatcher(): CoroutineDispatcher

Android actual implementation

Android实际实现

actual fun uiDispatcher() : CoroutineDispatcher = Dispatchers.Main


 actual fun ioDispatcher() : CoroutineDispatcher = Dispatchers.IO

iOS actual implementation

iOS实际实现

import kotlinx.coroutines.*
import platform.darwin.*
import kotlin.coroutines.*


actual fun uiDispatcher(): CoroutineDispatcher = UI


 actual fun ioDispatcher(): CoroutineDispatcher = UI


@UseExperimental(InternalCoroutinesApi::class)
private object UI : CoroutineDispatcher(), Delay {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        val queue = dispatch_get_main_queue()
        dispatch_async(queue) {
            block.run()
        }
    }


    override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) {
        val queue = dispatch_get_main_queue()


        val time = dispatch_time(DISPATCH_TIME_NOW, (timeMillis * NSEC_PER_MSEC.toLong()))
        dispatch_after(time, queue) {
            with(continuation) {
                resumeUndispatched(Unit)
            }
        }
    }
}

Below is the implementation of ViewModel. We can see here we pass initial state and CoroutineDispatcher that we discussed above as constructor parameter.

下面是ViewModel的实现。 我们可以在这里看到我们将上面讨论的初始状态和CoroutineDispatcher传递为构造函数参数。

abstract class ViewModel<A : Action, S : State, R : Result>
    (initialState: S, mainCoroutineDispatcher: CoroutineDispatcher) {
    
  /** Any coroutine launched in this scope is automatically 
      canceled if the ViewModel is cleared.*/     
    val viewModelScope = CoroutineScope(SupervisorJob() + mainCoroutineDispatcher)
    private val actions: Channel<A> = Channel(Channel.UNLIMITED)


    // Listening to actions and converting to results    
    @FlowPreview
    private val results: Flow<R> = actions.consumeAsFlow().flatMapLatest { actionToResults(it) }


    
    private val stateChannel: ConflatedBroadcastChannel<S> = ConflatedBroadcastChannel(initialState)




    fun sendEvent(action: A) = actions.offer(action)


    protected abstract suspend fun actionToResults(action: A): Flow<R>


    protected abstract suspend fun resultToState(result: R, state: S): S
  
    @ExperimentalCoroutinesApi
    val stateJob = results.scan(initialState) { state, result -> resultToState(result, state) }
        .distinctUntilChanged()// Only emit when the current state value is different than the last.
        .onEach { stateChannel.offer(it) }
        .launchIn(viewModelScope)
    
    // Use this method to observe the state
    fun observeState(): CFlow<S> = CFlow(stateChannel.asFlow()) 


    // Need to call this method when view is destroyed to cancel coroutines
     fun onCleared() {
         viewModelScope.cancel()
    }
}

In above code we are using something called scan. Scan operator takes the current state and result to combine it into a new state.

在上面的代码中,我们使用的是所谓的扫描。 扫描运算符获取当前状态和结果以将其组合为新状态。

We need to call onCleared() function from the view, whenever the view is getting destroyed.

每当视图被破坏时,我们都需要从视图中调用onCleared()函数。

If you want to check the sample example. You can check here:

如果要检查示例示例。 您可以在这里查看:

Conclusion

结论

Hopefully, you would have a better understanding of implementation of MVI or uni-directional data flow and how to get started. There can be multiple ways to approach this choose whatever works for your project.

希望您对MVI或单向数据流的实现以及入门有更好的了解。 可以采用多种方法来选择适合您项目的方法。

Please feel free to ask any questions or views about modularization, do reach out at LinkedIn or Twitter.

请随时询问有关模块化的任何问题或看法,请访问LinkedInTwitter

If you like the article, remember to share it with the android community. Happy Coding!

如果您喜欢这篇文章,请记住与android社区分享。 编码愉快!

资源资源 (Resources)

翻译自: https://medium.com/@rohitsingh14101992/uni-directional-data-flow-mvi-in-kotlin-multi-platform-world-9f11edb71373

kotlin 流

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
【为什么学习Kotlin?】 Google正式宣布Kotlin为Android开发的首选语言,这使得其成为除官方平台支持的Java之外的唯一语言。自2016年发布1.0版以来,Kotlin一直在稳定增长。随着被Netflix、Uber、Pinterest和Trello等公司的积极应用和推荐,可以预期Kotlin未来的市场将保持稳定增长。Kotlin被设计为一种工业级的面向对象的语言,并且是比Java更好的语言,但却可以与Java代码完全互操作,这一特点也能够帮助开发者可以更自然的从Java迁移到Kotlin。不可否认,Kotlin是近一段时间以来最令人激动的新语言之一。其实用性和互操作性让Kotlin成为许多Java开发人员更容易过渡学习的语言之一。但是,更多想要了解和学习Kotlin的学习者可能并没有Java开发背景。为此在本课程,我们将从头开始研究Kotlin,涵盖想要扎实了解Kotlin所必需的各个主题知识点。课程,我们将介绍语言的基础知识,并深入探讨一些棘手的学习问题。诸如构建和测试,泛型和扩展功能之类的主题。我们甚至研究一下Kotlin是如何帮助您编写自己的DSL。我们为此还建立了一套工具,可帮助您成为最有生产力同时最快乐的Kotlin开发人员。 【课程亮点】 1、课程涵盖基础框架、基础要点、高阶特性、灵活应用全部知识体系2、Kotlin是比Java更好的语言,但却可以与Java代码完全互操作3、实用性和互操作性让Kotlin成为许多Java开发人员更容易过渡学习的语言之一【讲师介绍】  Justin Lee(贾斯汀·李)—— Red Hat红帽首席软件工程师Justin Lee(贾斯汀·李)是Java Champion和Kotlin圈子的最受欢迎的专家之一。从1996年开始,他就一直使用Java进行编程,几乎在应用程序堆栈的每个级别上都有过开发经历。从数据库驱动程序一直到应用程序服务器和前端接口。他一直都是Java和Kotlin的拥护者,曾多次在美国和欧洲的技术大会及用户组发表过精彩演讲。同时,他更是一位活跃的开源社区成员,在任何时候都乐于贡献自己的力量。目前他就职于Red Hat,是Red Hat原生Java微服务框架QuarkusIO的首席软件工程师。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值