Giter Site home page Giter Site logo

badoo / mvikotlin Goto Github PK

View Code? Open in Web Editor NEW
824.0 19.0 70.0 4.9 MB

Extendable MVI framework for Kotlin Multiplatform with powerful debugging tools (logging and time travel), inspired by Badoo MVICore library

Home Page: https://arkivanov.github.io/MVIKotlin

License: Apache License 2.0

Kotlin 100.00%
kotlin-multiplatform mvi android jvm native ios multiplatform kotlin framework hacktoberfest

mvikotlin's Introduction

Maven Central License kotlinlang|MVIKotlin

Maintenance notice

This repository is not maintained. Arkadii Ivanov continued to work on the fork arkivanov/MVIKotlin.

Inspiration

This project is inspired by Badoo MVICore library.

Overview

Should you have any questions or ideas please welcome to the Slack channel: #mvikotlin

What is MVI

MVI stands for Model-View-Intent. It is an architectural pattern that utilizes unidirectional data flow. The data circulates between Model and View only in one direction - from Model to View and from View to Model.

MVI

What is MVIKotlin

MVIKotlin is a Kotlin Multiplatform framework that provides a way of (not only) writing shared code using MVI pattern. It also includes powerful debug tools like logging and time travel. The main functionality of the framework does not depend on any reactive nor coroutines library. Extensions for Reaktive and for Coroutines libraries are provided as separate modules.

MVIKotlin

Responsibility

MVIKotlin does not bring or enforce any particular architecture. Its responsibility can be described as follows:

  • To provide a single source of truth for State (the scope is not defined, it can be a whole app, a screen, a feature, or a part of a feature);
  • To provide an abstraction for UI with efficient updates (however this is not obligatory, you can use whatever you want);
  • To provide lifecycle aware connections (binding) between inputs and outputs (again this is not obligatory in any way).

Everything else is out of scope of the library, there are no definitions for "screens", "features", "modules", etc. Also, no particular reactive framework is enforced/exposed. This gives a lot of flexibility:

  • MVIKotlin can be introduced incrementally (e.g. you can start using it in a small feature and then expand gradually);
  • You can use/experiment with different architectures, approaches and/or libraries for navigation, UI, modularization, etc;
  • Use whatever reactive framework you like or don't use it at all.

You can find one of the architecture options in the samples. Again, this is just an example of one possible solution.

Setup

Recommended minimum Gradle version is 5.3. Please read first the documentation about metadata publishing mode.

There are a number of modules published to Maven Central:

  • mvikotlin - core interfaces and functionality (multiplatform)
  • mvikotlin-main - the main module with the default Store implementation (mutiplatform)
  • mvikotlin-logging - logging functionality (mutiplatform)
  • mvikotlin-timetravel - time travel feature (mutiplatform)
  • mvikotlin-extensions-reaktive - extensions set for Reaktive library (multiplatform)
  • mvikotlin-extensions-coroutines - extensions set for coroutines (multiplatform)
  • keepers - provides StateKeeper and InstanceKeeper API for state preservation and objects retaining (deprecated)
  • rx - a tiny module with abstractions over rx and coroutines (multiplatform)

Add required modules to your module`s build.gradle file:

implementation "com.arkivanov.mvikotlin:<module-name>:<version>"

IDEA Live Templates

To speed up the creation of new Stores, you can use the following IDEA Live Templates.

Features

  • Multiplatform: Android, JVM, JavaScript, iosX64, iosArm64, macosX64, linuxX64
  • Does not depend on any reactive library or coroutines
  • Extensions for Reaktive library
  • Extensions for Coroutines
  • Multithreading friendly (freezable in Kotlin Native if needed)
  • Lifecycle-aware connections (bindins) between inputs and outputs
  • Logging functionality with customizable logger and formatter
  • Time travel feature:
    • Multiplatform for all supported targets
    • Plug-and-play UI for Android
    • Plug-and-play UI for iOS (copy-paste from the sample app)
    • Export/import events for Android
    • IntelliJ IDEA and Android Studio plugin for Android apps
    • Desktop client application for Android, Java and native Apple (iOS, watchOS, tvOS, macOS) apps

Documentation

https://arkivanov.github.io/MVIKotlin

Sample project

The sample project is a todo list with details view.

  • Shared module using Reaktive is here
  • Shared module using coroutines is here
  • Sample Android application with both Reaktive and coroutines implementations, plus logging and time travel is here
  • Sample iOS application with Reaktive implementation only, plus logging and time travel is here
  • Sample JavaScript application with both Reaktive and coroutines implementations, plus logging and time travel is here

Author

Twitter: @arkann1985

If you like this project you can always Buy Me A Coffee ;-)

Watch video (time travel, logs, debug, etc.)

Debugging Android application with MVIKotlin

Debugging Android application with MVIKotlin

Debugging iOS application with MVIKotlin

Debugging iOS application with MVIKotlin

Debugging Android application with IntelliJ IDEA time travel plugin

Debugging Android application with IntelliJ IDEA time travel plugin

Debugging iOS application using MVIKotlin time travel client app

Debugging iOS application using MVIKotlin time travel client app

mvikotlin's People

Contributors

alexeykorshun avatar arkivanov avatar ema987 avatar insanedoggo avatar maxtox3 avatar plusmobileapps avatar sellmair avatar sgallese avatar shabinder avatar vkazanov avatar yektadev avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mvikotlin's Issues

Cannot find module MVIKotlin-mvikotlin

I'm trying to use MVIKoltin on Kotlin 1.4.0, but i get this error:

Uncaught Error: Cannot find module 'MVIKotlin-mvikotlin'

I have included all the mvikotlin dependencies...

Lost labels on init fragment view issue

Hi there.
Idk, it may be not a bug, but...

When we call store.accept(PermissionsStore.Intent.MapReady) like in a real use case with google map, when we wait until google map will be ready and OnMapReadyCallback are called.
Handle this intent in store, and try to send a label, which calls a fragment method with showing dialog, for example. But we lost the sent label.

Because in the DefaultStore method fun labels() calls after accept(). It looks like a lifecycle and binding issue.

You can reproduce this just run my project and see the log with the DEBDEB tag.
We won't see strings:
"DEBDEB consume AskLocationPermissions label"
"DEBDEB askLocationPermissions"

PermissionsFragment

PermissionsController

PermissionsStoreFactory

Whole project

class PermissionsFragment : Fragment() {
...
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        controller.onViewCreated(
            view = PermissionsViewImpl(view),
            lifecycle = lifecycle.asMviLifecycle(),
            viewLifecycle = viewLifecycleOwner.lifecycle.asMviLifecycle()
        )
        initUi()
    }
	
	private fun initUi() {
		//mapView?.getMapAsync(getOnMapReadyCallback())
        onOnMapReady()
    }

	//Call method, whick send intent
    private fun onOnMapReady() {
		//super.setupGoogleMap(googleMap)
        controller.onOnMapReady()
        Timber.d("DEBDEB onOnMapReady")
    }
...
}
class PermissionsController constructor(
    private val store: PermissionsStore,
    private val fragment: PermissionsFragment
) {
...
    fun onViewCreated(
        view: MviView<PermissionsView.ViewModel, PermissionsView.ViewEvent>,
        lifecycle: Lifecycle,
        viewLifecycle: Lifecycle
    ) {
        bind(viewLifecycle, BinderLifecycleMode.START_STOP) {
            store.states.mapNotNull(stateToViewModel) bindTo view
        }

        bind(viewLifecycle, BinderLifecycleMode.CREATE_DESTROY) {
            view.events.mapNotNull(viewEventToIntent) bindTo store
            store.labels.mapNotNull(labelToEvent) bindTo { consumeEvent(it) }
        }

        lifecycle.doOnDestroy(store::dispose)
    }

    fun onOnMapReady() {
        store.accept(PermissionsStore.Intent.MapReady)
    }

    private fun consumeEvent(event: PermissionsEvent) {
        when (event) {
            is PermissionsEvent.Back -> {
                //TOOD back
            }
            is PermissionsEvent.AskLocationPermissions -> {
                Timber.d("DEBDEB PermissionsEvent.AskLocationPermissions")
                fragment.askLocationPermissions()
            }
        }
    }
...
}
internal class DefaultStore<in Intent : Any, in Action : Any, in Result : Any, out State : Any, Label : Any> @MainThread constructor(
    initialState: State,
    private val bootstrapper: Bootstrapper<Action>?,
    executorFactory: () -> Executor<Intent, Action, State, Result, Label>,
    private val reducer: Reducer<State, Result>
) : Store<Intent, State, Label> {
...
	//Call call after
    override fun labels(observer: Observer<Label>): Disposable {
        assertOnMainThread()

        return labelSubject.subscribe(observer)
    }

	//Call first
    override fun accept(intent: Intent) {
        assertOnMainThread()

        intentSubject.onNext(intent)
    }
...
}

Issue importing MviKotlin for JS browser target (Kotlin 1.3.72)

Hi,

I'm encountering an error executing the jsBrowserProductionWebpack task for a js browser target after importing MviKotlin dependencies on a multiplatform project (using Kotlin 1.3.72):

74% optimizing
74% basic module optimization
75% module optimization
75% advanced module optimization
76% after module optimization
76% basic chunk optimization
76% basic chunk optimization EnsureChunkConditionsPlugin
76% basic chunk optimization RemoveParentModulesPlugin
76% basic chunk optimization RemoveEmptyChunksPlugin
76% basic chunk optimization MergeDuplicateChunksPlugin
77% chunk optimization
77% advanced chunk optimization
77% advanced chunk optimization SplitChunksPlugin
77% advanced chunk optimization RemoveEmptyChunksPlugin
77% after chunk optimization
78% module and chunk tree optimization
78% after module and chunk tree optimization
79% basic chunk modules optimization
80% chunk modules optimization
80% chunk modules optimization ModuleConcatenationPlugin
80% advanced chunk modules optimization
81% after chunk modules optimization
81% module reviving
81% module reviving RecordIdsPlugin
82% module order optimization
82% module order optimization OccurrenceOrderModuleIdsPlugin
82% advanced module order optimization
83% before module ids
83% module ids
84% module id optimization
84% module id optimization
85% chunk reviving
85% chunk reviving RecordIdsPlugin
85% chunk order optimization
85% chunk order optimization OccurrenceOrderChunkIdsPlugin
86% before chunk ids
86% chunk id optimization
86% chunk id optimization FlagIncludedChunksPlugin
87% after chunk id optimization
88% hashing
88% after hashing
89% module assets processing
90% chunk assets processing
90% additional chunk assets processing
92% additional asset processing
92% chunk asset optimization
92% chunk asset optimization TerserPlugin
93% after chunk asset optimization
93% after chunk asset optimization SourceMapDevToolPlugin
93% after chunk asset optimization SourceMapDevToolPlugin
93% after chunk asset optimization SourceMapDevToolPlugin content.js generate SourceMap
93% after chunk asset optimization SourceMapDevToolPlugin resolve sources
93% after chunk asset optimization SourceMapDevToolPlugin content.js attach SourceMap
93% after chunk asset optimization SourceMapDevToolPlugin
93% asset optimization
94% after asset optimization
94% after seal
100%
Hash: 05868d80530f6fdfdf4a
Version: webpack 4.41.2
Time: 4463ms
Built at: 2020-08-21 21:27:27
2 assets
Entrypoint main = content.js content.js.map
[0] ./kotlin-dce/kotlin.js 242 KiB {0} [built]
[2] ./kotlin-dce/Reaktive-utils.js 2.41 KiB {0} [built]
[3] ./kotlin-dce/kotlinx-coroutines-core.js 270 KiB {0} [built]
[4] multi ./kotlin-dce/untitled5-content.js 28 bytes {0} [built]
[5] ./kotlin-dce/untitled5-content.js 2.82 KiB {0} [built]
[6] ./kotlin-dce/MVIDroid-mvikotlin.js 3.16 KiB {0} [built]
[7] ./kotlin-dce/MVIKotlin-mvikotlin-extensions-coroutines.js 3.18 KiB {0} [built]
[8] ./kotlin-dce/MVIKotlin-rx.js 761 bytes {0} [built]
[9] ./kotlin-dce/MVIKotlin-utils-internal.js 1.63 KiB {0} [built]
+ 1 hidden module

ERROR in ./kotlin-dce/MVIDroid-mvikotlin.js
Module not found: Error: Can't resolve 'MVIDroid-rx-internal' in 'C:\Users\denis\IdeaProjects\untitled5\build\js\packages\untitled5-content\kotlin-dce'
@ ./kotlin-dce/MVIDroid-mvikotlin.js 3:4-111
@ ./kotlin-dce/untitled5-content.js
@ multi ./kotlin-dce/untitled5-content.js

ERROR in ./kotlin-dce/MVIDroid-mvikotlin.js
Module not found: Error: Can't resolve 'MVIDroid-utils-internal' in 'C:\Users\denis\IdeaProjects\untitled5\build\js\packages\untitled5-content\kotlin-dce'
@ ./kotlin-dce/MVIDroid-mvikotlin.js 3:4-111
@ ./kotlin-dce/untitled5-content.js
@ multi ./kotlin-dce/untitled5-content.js

ERROR in ./kotlin-dce/MVIKotlin-mvikotlin-extensions-coroutines.js
Module not found: Error: Can't resolve 'MVIKotlin-mvikotlin' in 'C:\Users\denis\IdeaProjects\untitled5\build\js\packages\untitled5-content\kotlin-dce'
@ ./kotlin-dce/MVIKotlin-mvikotlin-extensions-coroutines.js 3:4-136
@ ./kotlin-dce/untitled5-content.js
@ multi ./kotlin-dce/untitled5-content.js

  • Try:
    Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

  • Get more help at https://help.gradle.org

BUILD FAILED in 27s
5 actionable tasks: 5 executed

After investigating a bit the issue I've seen that the names on the js modules imported from bintray for MviKotlin do not match the ones generated upon building the project directly from the sources. The first screenshot shows the files imported from bintray, the second shows the ones generated when building from the MviKotlin source project

idea64_v9CNxdbXX4

7zFM_TChqXc4ixr

There is also a discrepancy between definitions inside the js between the two files besides the file name

imagen

I've generated the related js module from the source project and imported locally for my project and the build problem seems to be resolved (building the js file using the latest code on master, 1d19db5)

idea64_v4L5SlBhv7

Could these discrepancies be the problem when trying to use directly the bintray dependencies?

Caching of Labels if no consumers

Greetings again! Solution for saving Store is great, thanks! But I have one more question...

In the documentation for the library, you suggest using Label as a one-time event, and it's really very convenient. But we again encounter the Android lifecycle :(

For example, I want to notify View by means of Label about the need to display different dialogs or snackbars. But if Label comes when the application is in pause/stop/destroy state (depending on the BinderLifecycleMode), these Labels will be irretrievably lost. Perhaps a more logical solution would be to keep them while the consumer is absent? It seems to me to be quite common use case and it would be convenient to have something similar already integrated into the library.

However, if you don't share my opinion, I will try to implement something like CacheSubject and custom StoreFactory :)

Drop android() target and use plain jvm() for mvikotlin-main subproject

Can mvikotlin-main sub-project be plain jvm() target (without android)? I can't find any code which depends on Android in it or it's dependencies. My use-case would be having plain jvm feature modules in my android application.

Benefits for plain jvm feature modules:

  • leaner submodules (without android gradle plugin) = faster Gradle configuration
  • no separate artifact for android (mvikotlin-main-android )
  • no need to create AndroidManifest per each module

2.0.0 release - API feedback

I'm about to release first stable release. And any API change after the release will be very unwanted. So if somebody has any concerns it's a good time to raise them.

Personally I'm thinking about renaming Intent to Msg to avoid confusion with Android intents. But I'm not sure. Would like to hear about it as well.

Thanks!

Best practice: saving Store while configuration changes

In the sample application, I did not find an example of how to save the Store and get the result of an asynchronous operation that was started before the configuration change. I would be glad to see your vision for this case.

"Used by" list

It would be good to have a list of companies/users/projects where MVIKotlin is used. I sugest to stick to the following format, but feel free to ommit/add/modify any details:

Project name: [project name]
Project type: production/pet/sample
Company name: personal/[company name]
Supported targets: android, jvm, js, iosX64, iosArm64, macosX64, linuxX64
Sources link (if open source): [link]

Investigate (and create?) time travel Chrome plugin for JavaScript target

The plugin should work similar to the IDEA time travel plugin. It should somehow communicate to the TimeTravelController. Maybe we can implement a server similar to the TimeTravelServer for Android. And a client similar to TimeTravelClient.

The plugin should provide controls to:

  • connect to an app
  • disconnect from the app
  • start event recording
  • stop event recording
  • move to a previous state
  • move to a next state
  • move to a first state
  • move to a last state
  • cancel current time travel session

The plugin should display live event list. When an event is selected in the list, it's tree structure (values inspection) should be shown. If object parsing is not possible (in JVM it's done via reflection) then just print its toString().

Implement sample todo details screen for iOS

Details screen is already there for Android but is missing for iOS. Would be good to add one, same way as it is done for todo list screen: the business logic is already there and is shared, only UI part is missing (Swift UI).

Sample todo details screen for Android: https://github.com/arkivanov/MVIKotlin/tree/master/sample/todo-app-android/src/main/java/com/arkivanov/mvikotlin/sample/todo/android/details

Sample todo list screen for iOS: https://github.com/arkivanov/MVIKotlin/blob/master/sample/todo-app-ios/todo-app-ios/SceneDelegate.swift
Should be moved to a separate class (file) with proper navigation between the screens.

[Help] Upon List Item change in State , LazyColumn Item Not Recomposing

I have following State :
image

An Intent which cause reducer to change state by updating list item's property,like:
image

Consuming the state as following in UI:
image

ISSUE: after state change , listItem doesn't recomposes, but if I scroll the list out of screen and bring it back (i.e., Making It Recompose) the state update reflects in Item.

Help me , how to have the desired result?,I have tried keeping everything as close to TODO sample,only this issue is left,any help would be appreciated.

MVI Kotlin (JS) is compatible/tested with which coroutines version?

I just did setup for my project for JS-Browser target and I am getting following issue:
image

image

Issue seems like a coroutines bug to me.... So I tried with with 2 different version 1.4.3 and 1.4.2 , but no luck .
So to just confirm , MVI Kotlin + Decompose has to be proven working with which coroutines version?

for a reproducer , @arkivanov ,you are added as a collaborator in my project's private repo.
image

java.lang.IllegalArgumentException: Value was not initialized when calling dispatch(result) in Executor's init block

I've noticed that calling dispatch in SuspendExecutor init block results in the exception.
Library version: 2.0.1

Exception can be reproduced with the following code:

package com.domain

import com.arkivanov.mvikotlin.core.utils.isAssertOnMainThreadEnabled
import com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor
import com.arkivanov.mvikotlin.main.store.DefaultStoreFactory
import org.junit.jupiter.api.Test

internal class ExampleTest {

    @Test
    internal fun theTest() {
        isAssertOnMainThreadEnabled = false
        DefaultStoreFactory.create(
            initialState = State(),
            executorFactory = { Executor() },
        )
    }

    private class Result

    private class State

    private class Executor : SuspendExecutor<Nothing, Nothing, State, Result, Nothing>() {

        init {
            dispatch(Result())
        }
    }
}

Result:

java.lang.IllegalArgumentException: Value was not initialized

	at com.arkivanov.mvikotlin.utils.internal.AtomicExtKt.requireValue(AtomicExt.kt:11)
	at com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor.dispatch(SuspendExecutor.kt:80)
	at com.domain.ExampleTest$Executor.<init>(ExampleTest.kt:26)
	at com.domain.ExampleTest$theTest$1.invoke(ExampleTest.kt:15)
	at com.domain.ExampleTest$theTest$1.invoke(ExampleTest.kt:15)
	at com.arkivanov.mvikotlin.main.store.DefaultStore.<init>(DefaultStore.kt:31)
	at com.arkivanov.mvikotlin.main.store.DefaultStoreFactory.create(DefaultStoreFactory.kt:21)
	at com.arkivanov.mvikotlin.core.store.StoreFactory$DefaultImpls.create$default(StoreFactory.kt:20)
	at com.domain.ExampleTest.theTest$ui_debug(ExampleTest.kt:13)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
	at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
	at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:210)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at java.util.ArrayList.forEach(ArrayList.java:1257)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$5(NodeTestTask.java:143)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$7(NodeTestTask.java:129)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:127)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:69)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:33)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:230)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:58)
Process finished with exit code -1

I'd appreciate any feedback if calling dispatch(result) in init block is an anti-pattern. The real world example where I've discovered the issue was similar to:

class MyViewModel : ViewModel() { // AndroidX ViewModel

    private inner class Executor :
        SuspendExecutor<Intent, Nothing, State, Result, Label>(viewModelScope.coroutineContext) {

        init {
            viewModelScope.launch {
                observeDatabase() // returns `Flow<List<Item>>` 
                    .collectLatest { list ->
                        dispatch(Result.ListUpdate(list))
                    }
            }
        }

Add sample todo for JS

Would be nice to add some samples for JS. Consider using Kotlin React wrappers.

Conditional GH Actions determined by module/sourceset/architecture

Just something that I thought might be worth adding. I made a small PR here that would have only affected the sample/todo-app-android module but the Checks that are required for the PR build all the architectures.

Just to save on some โŒš and ๐Ÿ’ฐ

References

Issue with Time travel debug

When I try to connect to device, always receive the error: Error forwarding port via ADB. Make sure there is only one device connected. "
reproduced on emulator and real device.
adb devices shows only one device connected
OS: Mac os catalina
Plugin version: 2.0.0-rc1
Android studio version: 4.0.0, 4.1.0 beta 3

Error: java.lang.NoClassDefFoundError: android/os/Looper in: DefaultStore.kt:31

Caused by: java.lang.NoClassDefFoundError: android/os/Looper
at kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:55)
at kotlinx.coroutines.android.AndroidDispatcherFactory.createDispatcher(HandlerDispatcher.kt:52)
at kotlinx.coroutines.internal.MainDispatchersKt.tryCreateDispatcher(MainDispatchers.kt:57)
at kotlinx.coroutines.internal.MainDispatcherLoader.loadMainDispatcher(MainDispatchers.kt:38)
at kotlinx.coroutines.internal.MainDispatcherLoader.(MainDispatchers.kt:22)
at kotlinx.coroutines.Dispatchers.getMain(Dispatchers.kt:58)
at com.arkivanov.mvikotlin.extensions.coroutines.SuspendExecutor.(SuspendExecutor.kt:23)
at com.shabinder.common.main.store.SpotiFlyerMainStoreProvider$ExecutorImpl.(SpotiFlyerMainStoreProvider.kt:57)
at com.shabinder.common.main.store.SpotiFlyerMainStoreProvider$provide$1$1.invoke(SpotiFlyerMainStoreProvider.kt)
at com.shabinder.common.main.store.SpotiFlyerMainStoreProvider$provide$1$1.invoke(SpotiFlyerMainStoreProvider.kt)
at com.arkivanov.mvikotlin.main.store.DefaultStore.(DefaultStore.kt:31)
... 143 more

image

My Store Creator:
image

My Suspend Executor:
image

All methods like openPlatform,etc are just expect/actual empty blocks
image

PS:
1.All Implementations Are very similar to as shown in todo app , except these are coroutine/suspend instead of Reaktive
2.Crash is in Desktop only , same implementation works good in Android app(All Code is Common/Shared module)

Add time travel plugin for Android Studio

Would be nice to have a plugin for Android Studio that will allow us to:

  • Control the time travel feature
  • Browse through recorded events
  • Click on an event to see its details
  • Fire an event for debugging

Needs in sample project

Add some sample project, please, for quick start.

Nice to see cases like:

  • Very simple screen
  • Screen with adapter
  • Screen with lot of logic
  • Shared logic in more then one screen

And of cause in context of library:

  • Using labels
  • Complex component

Allow Stores instantiation from non-main threads

Jetpack Compose will have multi-threaded compositions at some point, and so it should be possible to instantiate Stores from non-main threads. The Bootstrapper is currently invoked from Store constructor, it should continue be executing on main thread. We probably need an ability to manually trigger the Bootstrapper.

Receiving output from Bootstrapper at start

I have an activity and couple of feature fragments in my application. I would like to decide what fragment to open at start before any user input. To do that I need to do some preparations, so Bootstrapper looks like the right thing to use.

I created simple sample illustrating what I'm trying to achieve, but it is not working. I don't know what is the exact problem, but RootFragment#listOutput is not invoking for the Label.MakeToast. In my application I tried to use SimpleBootstrapper (not working) and SuspendBootstrapper with or without delay before dispatch. Everything works as expected when there is a small delay before publish, but it is not working without it.

In supplied sample it is just enough to move this when block to the end of the method.

At this point I don't really know is there something I missed or everything works as expected. So how can I achieve desired behaviour?

Thanks in advance!

Logo wanted!

The project needs logo but I'm not good at design. I will appreciate any help here. Ideally it should highlight Kotlin and MVI (or unidirectional data flow). I'm quite flexible here.

Job does not have time to cancel and calls render after a view is destroyed

Hello! In my application, I store a navigation action in a state, and also use the NavController::addOnDestinationChangedListener to tell the Store that the navigation action has been completed and can be nullified. Thus, Store generates a new state at the very edge before destroying the view. Today I ran into the fact that 1 time out of 10, the state arrives in the render method too late, and the application crashes due to the fact that the view has already nullified. Changing BinderLifecycleMode from START_STOP to RESUME_PAUSE had no effect. I assumed that a coroutine does not have time to cancel Job and added yield() here, before assertOnMainThread(), and it seems to help. I was unable to reproduce the problem again. It will be great if you do it in the library :)

Issue with mvikotlin-extensions-coroutines based store implementation on JS target

Hi,

I'm encountering the following error when trying to use the store and related coroutine based implementations of the mvikotlin-extensions-coroutines package on a JS browser target.

ReferenceError: observer$ObjectLiteral is not defined

Both with my project and with the sample Todo project the issue is met. The easiest way to reproduce the error is executing the browserDevelopmentRun task after changing the FrameworkType from REAKTIVE to COROUTINES on the todo-app-js module

val mFrameworkType: FrameworkType = FrameworkType.COROUTINES inside todo-app-js/src/main/kotlin/Util.kt

This is the console log of the web browser for the error

ReferenceError: observer$ObjectLiteral is not defined
doResume Utils.kt:16
lambda_0 Utils.kt:15
collectTo_sz7tnc$ kotlinx-coroutines-core.js:19406
doResume kotlinx-coroutines-core.js:19440
collectTo_sz7tnc$ kotlinx-coroutines-core.js:19470
doResume kotlinx-coroutines-core.js:21186
resumeWith_tl1gpc$ CoroutineImpl.kt:47
run kotlinx-coroutines-core.js:34305
process kotlinx-coroutines-core.js:37640
lambda kotlinx-coroutines-core.js:37595
promise callback*WindowMessageQueue.prototype.schedule kotlinx-coroutines-core.js:37600
enqueue_771g0p$ kotlinx-coroutines-core.js:37627
dispatch_5bn72i$ kotlinx-coroutines-core.js:37551
dispatch_5bn72i$ kotlinx-coroutines-core.js:37335
resumeCancellableWith kotlinx-coroutines-core.js:34143
startCoroutineCancellable_0 kotlinx-coroutines-core.js:35142
invoke_3o0yor$ kotlinx-coroutines-core.js:2186
start_b5ul0p$ kotlinx-coroutines-core.js:446
launch kotlinx-coroutines-core.js:829
handleAction SuspendExecutor.kt:52
handleAction LoggingExecutor.kt:34
process_fpuznb$ TimeTravelStoreImpl.kt:146
process_fpuznb$ TimeTravelStoreImpl.kt:113
process_0 TimeTravelControllerImpl.kt:239
onEvent_0 TimeTravelControllerImpl.kt:170
observerThreadLocalHolder MVIKotlin-mvikotlin-timetravel.js:138
onNext ObserverBuilder.kt:9
lambda_1 TimeTravelControllerImpl.kt:56
onNext ObserverBuilder.kt:9
drain_0 ThreadLocalSubject.kt:66
drainIfNeeded_0 ThreadLocalSubject.kt:56
onNext ThreadLocalSubject.kt:43
onEvent_0 TimeTravelStoreImpl.kt:124
lambda TimeTravelStoreImpl.kt:98
action MVIKotlin-mvikotlin.js:755
invoke _Arrays.kt:13416
init TimeTravelStoreImpl.kt:101
attachStore_x1ts8j$ TimeTravelControllerImpl.kt:61
attachTimeTravelStore TimeTravelControllerProvider.kt:21
create$default Standard.kt:98
create StoreFactory.kt:12
create$default LoggingStoreFactory.kt:65
create StoreFactory.kt:12
TodoListStoreAbstractFactory$create$ObjectLiteral TodoListStoreAbstractFactory.kt:24
create TodoListStoreAbstractFactory.kt:24
lambda TodoListCoroutinesController.kt:49
getOrCreateStore InstanceKeeperExt.kt:35
TodoListCoroutinesController TodoListCoroutinesController.kt:43
createController_0 TodoListComponent.kt:68
componentDidMount TodoListComponent.kt:51
React 6
unstable_runWithPriority scheduler.development.js:653
React 10
render_0 ReactDOM.kt:7
Application$start$lambda Main.kt:17
start Main.kt:16
main Main.kt:30
MVIKotlin-todo-app-js.js:1756
MVIKotlin-todo-app-js.js:1759
js todo-app-js.js:6355
webpack_require todo-app-js.js:30
0 todo-app-js.js:6543
webpack_require todo-app-js.js:30
todo-app-js.js:94
todo-app-js.js:97
webpackUniversalModuleDefinition todo-app-js.js:9
todo-app-js.js:10
kotlinx-coroutines-core.js:37289
ReferenceError: observer$ObjectLiteral is not defined
doResume Utils.kt:16
lambda Utils.kt:15
collectTo_sz7tnc$ kotlinx-coroutines-core.js:19406
doResume kotlinx-coroutines-core.js:19440
collectTo_sz7tnc$ kotlinx-coroutines-core.js:19470
doResume kotlinx-coroutines-core.js:21186
resumeWith_tl1gpc$ CoroutineImpl.kt:47
run kotlinx-coroutines-core.js:34305
process kotlinx-coroutines-core.js:37640
lambda kotlinx-coroutines-core.js:37609
WindowMessageQueue kotlinx-coroutines-core.js:37591
WindowDispatcher kotlinx-coroutines-core.js:37548
asCoroutineDispatcher kotlinx-coroutines-core.js:37810
createDefaultDispatcher kotlinx-coroutines-core.js:37251
Dispatchers kotlinx-coroutines-core.js:37310
Dispatchers_getInstance kotlinx-coroutines-core.js:37321
MVIKotlin-todo-coroutines.js:2278
MVIKotlin-todo-coroutines.js:5
MVIKotlin-todo-coroutines.js:8
js todo-app-js.js:6388
webpack_require todo-app-js.js:30
MVIKotlin-todo-app-js.js:1759
js todo-app-js.js:6355
webpack_require todo-app-js.js:30
0 todo-app-js.js:6543
webpack_require todo-app-js.js:30
todo-app-js.js:94
todo-app-js.js:97
webpackUniversalModuleDefinition todo-app-js.js:9
todo-app-js.js:10

Both with the sample todo-app-js module and with a store implementation based on mvikotlin-extensions-reaktive for my project no error is thrown and web app seems to work just fine

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.