A simple MVI framework for Kotlin Multiplatform and Android

Overview

Orbit Multiplatform

CI status codecov Download License

Logo

Get in touch

slack logo twitter logo

What is Orbit

Orbit is a Redux/MVI-like library - but without the baggage. It's so simple we think of it as MVVM+.

  • Simple, type-safe, coroutine-style, extensible API
  • Multiplatform, targetting Android and iOS (iOS support is in alpha and being actively worked on)
  • Full support for Kotlin Coroutines (it's built on top of them after all)
  • Lifecycle-safe collection of infinite flows
  • ViewModel support, along with SavedState
  • Optional, simple unit test library
  • Built-in espresso idling resource support
  • Compatible with RxJava, LiveData etc. through coroutine wrappers
  • And more...

Documentation

Articles & Talks

Getting started

Download

implementation("org.orbit-mvi:orbit-core:<latest-version>")
// or, if on Android:
implementation("org.orbit-mvi:orbit-viewmodel:<latest-version>")

// Tests
testImplementation("org.orbit-mvi:orbit-test:<latest-version>")

Define the contract

data class CalculatorState(
    val total: Int = 0
)

sealed class CalculatorSideEffect {
    data class Toast(val text: String) : CalculatorSideEffect()
}

Create the ViewModel

  1. Implement the ContainerHost interface
  2. Override the container field and use the ViewModel.container factory function to build an Orbit Container in your ContainerHost
class CalculatorViewModel: ContainerHost<CalculatorState, CalculatorSideEffect>, ViewModel() {

    // Include `orbit-viewmodel` for the factory function
    override val container = container<CalculatorState, CalculatorSideEffect>(CalculatorState())

    fun add(number: Int) = intent {
        postSideEffect(CalculatorSideEffect.Toast("Adding $number to ${state.total}!"))

        reduce {
            state.copy(total = state.total + number)
        }
    }
}

We have used an Android ViewModel as the most common example, but there is no requirement to do so.

Connect to the ViewModel in your Activity or Fragment

class CalculatorActivity: AppCompatActivity() {

    // Example of injection using koin, your DI system might differ
    private val viewModel by viewModel<CalculatorViewModel>()

    override fun onCreate(savedState: Bundle?) {
        ...

        addButton.setOnClickListener { viewModel.add(1234) }

        viewModel.observe(state = ::render, sideEffect = ::handleSideEffect)
    }

    private fun render(state: CalculatorState) {
        ...
    }

    private fun handleSideEffect(sideEffect: CalculatorSideEffect) {
        when (sideEffect) {
            is CalculatorSideEffect.Toast -> toast(sideEffect.text)
        }
    }
}

With Jetpack Compose wire up the ViewModel as follows:

@Composable
fun CalculatorScreen(viewModel: CalculatorViewModel) {

    val state = viewModel.container.stateFlow.collectAsState().value

    LaunchedEffect(viewModel) {
        launch {
            viewModel.container.sideEffectFlow.collect { handleSideEffect(navController, it) }
        }
    }

    // render UI using data from 'state'
    ...
}

private fun handleSideEffect(sideEffect: CalculatorSideEffect) {
    when (sideEffect) {
        is CalculatorSideEffect.Toast -> toast(sideEffect.text)
    }
}

Contributing

Please read contributing for details on our code of conduct, and the process for submitting pull requests to us.

Versioning

We use SemVer for versioning. For the versions available, see the tags on this repository.

License

License

This project is licensed under the Apache License, Version 2.0 - see the license file for details

Comments
  • Compose TextField state is mangled when updating via intent

    Compose TextField state is mangled when updating via intent

    There is an issue when using orbit with a Samsung device. The device must use the Samsung keyboard and not an alternative one to reproduce this issue.

    When using a Textfield that displays a value that is from a state and updates that value in the state by using the onValueChange value with orbits intent and reduce function, the Textfield value will randomly reset if the user is using a Samsung keyboard.

    Example:

    class MainActivity : ComponentActivity() {
        private val viewModel = ExampleViewModel()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyApplicationTheme {
                    // A surface container using the 'background' color from the theme
                    Surface(color = MaterialTheme.colors.background) {
                        Screen(viewModel)
                    }
                }
            }
        }
    }
    
    @Composable
    fun Screen(viewModel: ExampleViewModel) {
    
        val state = viewModel.container.stateFlow.collectAsState().value
    
        TextField(value = state.text, onValueChange = { it ->
            viewModel.updateText(it)
        })
    
    }
    
    data class ViewState(
        val text: String = ""
    )
    
    sealed class SideEffect
    
    class ExampleViewModel : ContainerHost<ViewState, SideEffect>, ViewModel() {
    
        override val container = container<ViewState, SideEffect>(ViewState())
    
        fun updateText(text: String) = intent {
    
            reduce {
                state.copy(text = text)
            }
        }
    }
    

    Example project: https://github.com/DarrenMiddleton/orbit-mvi-keyboard-issue

    opened by DarrenMiddleton 10
  • Warnings on all simple syntax DSL methods

    Warnings on all simple syntax DSL methods

    At the moment all my simple syntax DSL methods like reduce and postSideEffect are marked with a red line with the following warning:

    image

    Is this a known issue and can I do something about that? It compiles and runs just fine, but all the warnings are triggering me 😂

    opened by sunilson 5
  • Unit test never completes when collecting Flow

    Unit test never completes when collecting Flow

    I'm attempting to assert state changes when a certain intent is invoked.

    This is my relevant testing code:

    private val getCryptoAssetsUseCase = mockk<GetCryptoAssetsUseCase>()
    private val testCoroutineDispatcher = StandardTestDispatcher()
    private val assetName = "Bitcoin"
    private val pagingData = PagingData.from(listOf(Asset(name = assetName)))
    
    @Before
    fun setUp() {
        coEvery { getCryptoAssetsUseCase() } returns(flowOf(pagingData))
    }
    
    @Test
    fun getCryptoList_producesCorrectState() = runTest {
        val initialState = CryptoListState()
        val viewModel = CryptoListViewModel(getCryptoAssetsUseCase, testCoroutineDispatcher).test(
            initialState = initialState
        )
    
        viewModel.runOnCreate()
    
        viewModel.assert(initialState) {
            states(
                { copy(loading = true) },
                { copy(list = pagingData, loading = false) }
            )
        }
    }
    

    And this is my VM code:

    @HiltViewModel
    class CryptoListViewModel @Inject constructor(
        private val getCryptoAssetsUseCase: GetCryptoAssetsUseCase,
        @IoDispatcher private val coroutineDispatcher: CoroutineDispatcher
    ) : ContainerHost<CryptoListState, CryptoListSideEffect>, ViewModel() {
    
        override val container = container<CryptoListState, CryptoListSideEffect>(CryptoListState()) {
            getCryptoList()
        }
    
        private fun getCryptoList() = intent(registerIdling = false) {
            reduce { state.copy(loading = true) }
            withContext(coroutineDispatcher) {
                getCryptoAssetsUseCase().cachedIn(viewModelScope).collect {
                    reduce { state.copy(list = it, loading = false) }
                }
            }
        }
    
        fun userSelectedAsset(assetName: String) = intent {
            postSideEffect(CryptoListSideEffect.NavigateToAssetDetail(assetName))
        }
    }
    

    However the test just hangs and never completes. I'm sure there's something funky I need to do with the testCoroutineDispatcher or something to force the Flow to complete, I'm just not sure what I'm doing wrong.

    opened by charlie-niekirk 4
  • Side effects are not cached if there are no observers.

    Side effects are not cached if there are no observers.

    Hi ! The Orbit documentation said that Side effects are cached if there are no observers, guaranteeing critical events such as navigation are delivered after re-subscription.

    But with this sample :

    class MainActivity : ComponentActivity() {
    
        private val buttonViewModel: ButtonViewModel by viewModels()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                ButtonToClick(buttonViewModel, ::handleSideEffect)
            }
        }
    
        private fun handleSideEffect(sideEffect: ButtonSideEffect) {
            when (sideEffect) {
                is ButtonSideEffect -> Toast.makeText(this, sideEffect.message, Toast.LENGTH_LONG).show()
            }
        }
    }
    
    sealed class ButtonState
    object IdleState: ButtonState()
    object ClickedState: ButtonState()
    
    data class ButtonSideEffect(val message: String)
    
    @Composable
    fun ButtonToClick(buttonViewModel: ButtonViewModel, handleSideEffect: (ButtonSideEffect) -> Unit = {}) {
        val buttonState = buttonViewModel.container.stateFlow.collectAsState().value
    
        LaunchedEffect(buttonViewModel) {
            launch {
                buttonViewModel.container.sideEffectFlow.collect { sideEffect ->
                    handleSideEffect(sideEffect)
                }
            }
        }
    
        Button(onClick = { buttonViewModel.onButtonClicked() }, enabled = buttonState is IdleState){
            Text(text = "Click me")
        }
    }
    
    class ButtonViewModel: ViewModel(), ContainerHost<ButtonState, ButtonSideEffect> {
        override val container: Container<ButtonState, ButtonSideEffect> = container(IdleState){
            intent { reduce { IdleState } }
        }
    
        fun onButtonClicked() = intent {
            reduce { ClickedState }
    
            delay(5000)
            postSideEffect(ButtonSideEffect("Button clicked"))
    
            reduce { IdleState }
        }
    }
    

    When I :

    1. Click the button
    2. Turn off my phone screen
    3. Wait for 5 second
    4. Turn it back on

    the Toast doesn't appear.

    In my "real world" scenario the SideEffect is a navigation event happening on a loading screen. The loading take a while and if the user put is phone aside and come back later, he's stuck on the loading screen.

    Am I doing someting wrong with the collection of the side effects ?

    bug 
    opened by DupinC 4
  • Sponsorship / Donations

    Sponsorship / Donations

    Hello, The company I work for is organizing a donation wave to support projects/teams that are doing a tremendous job helping out the open source community. Orbit-Mvi it's one of these, no doubt.

    I've talked already with @mattmook on slack, but I'll leave this "issue" open to better track it's progress.

    Are you open for donations/sponsorship?

    Regards

    opened by GuilhE 4
  • State flows calls only when fragment is created

    State flows calls only when fragment is created

    I was trying to use it in my project and found out any life cycle events call only once. After button click it does nothing and stateFlow from fragment does not return anything.

    opened by ThihaKaungSet3 4
  • Using simple syntax with Coroutine Flows

    Using simple syntax with Coroutine Flows

    I can't find an example in the documentation how I am supposed to use the intent syntax with a coroutine Flow.

    For example, how would i wrap the following code:

        fun test() {
            job?.cancel()
            job = viewModelScope.launch { 
                testFlow.collect{
                    //set state here
                }
            }
        }
    

    If I understand it correctly, if I put intent directly after test(), then my collect{} function will always get the same state as it was when the function was first called. Do I put the intent inside the collect{}block?

    And how would I do this with the strict syntax? (Especially the cancelling part)

    Thanks!

    question 
    opened by sunilson 4
  • How to use suspend room fuction in viewmodel intent?

    How to use suspend room fuction in viewmodel intent?

    In my test ViewModel

    
        private val _testFlow = MutableStateFlow(0)
        val testFlow: StateFlow<Int> = _testFlow.asStateFlow()
    
        fun testFlow() {
            viewModelScope.launch {
                val values = repository.getAllItems()
                _testFlow.emit(values.count())
            }
        }
    
        fun testIntent() = intent() {
            reduce { state.copyToLoading() }
            val values = repository.getAllItems()
            reduce {
                state.copyToSuccess(values)
            }
        }
    

    DAO looks like

    @Dao
    interface TestDao {
      @Query("SOME SQL WITH INNER JOIN")
      suspend fun getAllItems(): List<TestView>
    }
    

    in first case with testFlow repository.getAllItems returns 3 records from the Room, but second case with testIntent returns 0 records.

    It's look like i incorrect use intents with suspend functions. Could you help me?

    opened by avvensis 3
  • StateFlowExtesions

    StateFlowExtesions

    This PR adds StateFlowExtesions. This necessity was born from #122 where the author asks if it is possible to "hide" the ContainerHost. And It is, by doing:

    /**
     * Just to keep it internal without public access.
     * @param initialState The initial state of the container.
     */
    internal fun <STATE : Any, SIDE_EFFECT : Any> CoroutineScope.containerHostVisibilityWrapper(initialState: STATE) =
        object : ContainerHost<STATE, SIDE_EFFECT> {
            override val container = [email protected]<STATE, SIDE_EFFECT>(initialState)
        }
    

    using it like:

    data class MyState(val message: String = "")
    
    class MyViewModel : ViewModel() {
        private val host = viewModelScope.containerHostVisibilityWrapper<MyState, Nothing>(MyState())
        val stateFlow = host.container.stateFlow
    }
    

    But unfortunately, we need a ContainerHost<STATE, SIDE_EFFECT> to use compose extensions: collectSideEffect, collectState and collectAsState.

    With this PR, we're able to use them - collectSideEffectLifecycleAware, collectStateLifecycleAware, collectAsStateLifecycleAware -, like:

    @Composable
    fun Screen(viewModel: MyViewModel) {
        with(viewModel.stateFlow.collectAsStateLifecycleAware().value) {
             ...
        }
    }
    
    opened by GuilhE 2
  • Manage state with Compose TextField value lagging

    Manage state with Compose TextField value lagging

    I really would like to thank you all for the amazing library that makes our life easier. I'm trying to manage Compose state following the state-hoisting concept as recommended by Google. The problem is that makes TextField writing or clearing lagging. check my code:

    @Composable
    fun LoginScreen(viewModel: LoginViewModel = hiltViewModel()) {
        val localFocusManager = LocalFocusManager.current
        val spacing = LocalSpacing.current
        val state = viewModel.collectAsState().value
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Username(
                localFocusManager = localFocusManager,
                username = state.username,
                onUsernameChange = { viewModel.onUsernameChanged(it) })
            Spacer(modifier = Modifier.height(spacing.spaceMedium))
            OutlinedTextField(
                value = state.password,
                onValueChange = { viewModel.onPasswordChanged(password = it) },
                label = { Text(stringResource(id = R.string.password)) },
                visualTransformation = PasswordVisualTransformation(),
                keyboardOptions = KeyboardOptions(
                    keyboardType = KeyboardType.Password,
                    imeAction = ImeAction.Done
                ),
                keyboardActions = KeyboardActions(onDone = { localFocusManager.clearFocus() })
            )
            Spacer(modifier = Modifier.height(spacing.spaceMedium))
            Button(onClick = { viewModel.login() }) {
                Text(text = stringResource(id = R.string.login))
            }
        }
    }
    
    @Composable
    fun Username(
        localFocusManager: FocusManager,
        username: String,
        onUsernameChange: (String) -> Unit
    ) {
        OutlinedTextField(
            value = username,
            onValueChange = onUsernameChange,
            label = { Text(stringResource(id = R.string.username)) },
            keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next),
            keyboardActions = KeyboardActions(onNext = { localFocusManager.moveFocus(FocusDirection.Down) })
        )
    }
    

    ViewModel:

    @HiltViewModel
    class LoginViewModel @Inject constructor(
        savedStateHandle: SavedStateHandle,
        private val loginUseCase: LoginUseCase
    ) : ViewModel(), ContainerHost<LoginState, LoginSideEffect> {
    
        override val container by lazy {
            container<LoginState, LoginSideEffect>(
                initialState = LoginState(),
                savedStateHandle = savedStateHandle
            )
        }
    
        fun onUsernameChanged(username: String) = intent {
            reduce { state.copy(username = username) }
        }
    
        fun onPasswordChanged(password: String) = intent {
            reduce { state.copy(password = password) }
        }
    
        fun login() = intent {
            val loginResult = loginUseCase.invoke(LoginParams(state.username, state.password))
            reduce { state.copy(loginResult = loginResult) }
        }
    
    }
    

    LoginState:

    @Parcelize
    data class LoginState(
        val username: String = "",
        val password: String = "",
        val loginResult: Result<UserEntity>? = null
    ) : Parcelable
    

    It happens when I write long text and then click and hold on the X button on the keyboard it supposes to clear all characters but it stops after clearing a number of characters. Any suggestion to solve this without using var username by remember { mutableStateOf("") }?

    opened by IbrahimDisouki 2
  • Support Apple Silicon simulators

    Support Apple Silicon simulators

    Based on #97, just fixed detekt

    See https://kotlinlang.org/docs/mpp-share-on-platforms.html#target-shortcuts-and-arm64-apple-silicon-simulators for details.

    This is needed so projects using Orbit can run on the iOS Simulators on Apple Silicon (M1) Macs.

    opened by mattmook 2
  • Bump loader-utils from 2.0.2 to 2.0.4 in /website

    Bump loader-utils from 2.0.2 to 2.0.4 in /website

    Bumps loader-utils from 2.0.2 to 2.0.4.

    Release notes

    Sourced from loader-utils's releases.

    v2.0.4

    2.0.4 (2022-11-11)

    Bug Fixes

    v2.0.3

    2.0.3 (2022-10-20)

    Bug Fixes

    • security: prototype pollution exploit (#217) (a93cf6f)
    Changelog

    Sourced from loader-utils's changelog.

    2.0.4 (2022-11-11)

    Bug Fixes

    2.0.3 (2022-10-20)

    Bug Fixes

    • security: prototype pollution exploit (#217) (a93cf6f)
    Commits

    Dependabot compatibility score

    Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting @dependabot rebase.


    Dependabot commands and options

    You can trigger Dependabot actions by commenting on this PR:

    • @dependabot rebase will rebase this PR
    • @dependabot recreate will recreate this PR, overwriting any edits that have been made to it
    • @dependabot merge will merge this PR after your CI passes on it
    • @dependabot squash and merge will squash and merge this PR after your CI passes on it
    • @dependabot cancel merge will cancel a previously requested merge and block automerging
    • @dependabot reopen will reopen this PR if it is closed
    • @dependabot close will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
    • @dependabot ignore this major version will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this minor version will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
    • @dependabot ignore this dependency will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
    • @dependabot use these labels will set the current labels as the default for future PRs for this repo and language
    • @dependabot use these reviewers will set the current reviewers as the default for future PRs for this repo and language
    • @dependabot use these assignees will set the current assignees as the default for future PRs for this repo and language
    • @dependabot use this milestone will set the current milestone as the default for future PRs for this repo and language

    You can disable automated security fix PRs for this repo from the Security Alerts page.

    dependencies 
    opened by dependabot[bot] 0
  • Turbine test

    Turbine test

    Adds new testing API that uses Turbine under the hood.

    • Improved, flexible API
    • No more "magic" - everything is explicit
    • No assertions to maintain
    opened by Rosomack 0
  • KMM example (orbit-sample-posts-multiplatform) not compiling

    KMM example (orbit-sample-posts-multiplatform) not compiling

    I am using M1 Pro. With org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20 which give the following error when executing target iosApp with iosSimulatorArm64() profile Here is my common setting info:

    plugins {
        kotlin("multiplatform")
        kotlin("native.cocoapods")
        kotlin("kapt")
        id("com.android.library")
        id("kotlin-parcelize")
        kotlin("plugin.serialization") version "1.5.21"
        id("org.orbit-mvi.orbit.swift") version "0.1.0"
    }
    
    // CocoaPods requires the podspec to have a version.
    
    kotlin {
        android()
        ios()
        iosSimulatorArm64()
        cocoapods {
            version = "1.0.0"
            summary = "test"
            homepage = "https://test.com/"
            ios.deploymentTarget = "14.1"
            podfile = project.file("../iosApp/Podfile")
            framework {
                baseName = "shared"
            }
        }
    
        sourceSets {
    
            @Suppress("UnusedPrivateMember")
            val commonMain by getting {
                dependencies {
                    api("org.orbit-mvi:orbit-core:4.3.2")
                    api("dev.icerock.moko:mvvm-core:0.13.0") // only ViewModel, EventsDispatcher, Dispatchers.UI
                    implementation("dev.icerock.moko:parcelize:0.8.0")
    
                    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
    
                    implementation("io.ktor:ktor-client-core:1.6.8")
                    implementation("io.ktor:ktor-client-serialization:1.6.8")
    
                    implementation("io.insert-koin:koin-core:3.1.6")
                }
            }
            @Suppress("UnusedPrivateMember")
            val commonTest by getting {
                dependencies {
                    implementation(kotlin("test-common"))
                    implementation(kotlin("test-annotations-common"))
                }
            }
            @Suppress("UnusedPrivateMember")
            val androidMain by getting {
                dependencies {
                    implementation("io.ktor:ktor-client-android:1.6.8")
    
                    implementation("org.orbit-mvi:orbit-viewmodel:4.3.2")
    
                    implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0-alpha03")
    
                    // Dependency Injection
                    implementation("io.insert-koin:koin-android:3.1.2")
                }
            }
    //        @Suppress("UnusedPrivateMember")
    //        val androidTest by getting {
    //            dependencies {
    //                implementation(kotlin("test-junit"))
    //                implementation("junit:junit:4.13.2")
    //            }
    //        }
            @Suppress("UnusedPrivateMember")
            val iosMain by getting {
                dependencies {
                    implementation("io.ktor:ktor-client-ios:1.6.8")
                }
            }
            @Suppress("UnusedPrivateMember")
            val iosTest by getting
        }
    }
    
    android {
        compileSdk = 30
        sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
        defaultConfig {
            minSdk = 23
            targetSdk = 30
        }
    }
    
    
    
    スクリーンショット 2022-07-31 17 44 01 スクリーンショット 2022-07-31 17 42 27
    opened by Guo-astro 2
  • Implement debugMode to verify `container` uses a backing field

    Implement debugMode to verify `container` uses a backing field

    Fixes #117

    So the checks aren't duplicated I've had to make debugModeContainerHostVerification public.

    This PR only introduces a guardrail for ensuring container uses a backing field

    opened by mattmook 0
  • Create guardrails

    Create guardrails

    For example https://airbnb.io/mavericks/#/debug-checks

    We've seen a few bug reports where ppl have not stored the container in a backing field and it would be great to spot that rather than have an issue raised against the project

    Calling intent from outside of a container host

    opened by mattmook 0
Releases(4.5.0)
  • 4.5.0(Dec 2, 2022)

    What's Changed

    • Fix "Edit this page" links by @nateridderman in https://github.com/orbit-mvi/orbit-mvi/pull/142
    • Misc Documentation Improvements by @nateridderman in https://github.com/orbit-mvi/orbit-mvi/pull/144
    • Added inline intents to be able to store text inside orbit's state by @Rosomack in https://github.com/orbit-mvi/orbit-mvi/pull/148

    New Contributors

    • @nateridderman made their first contribution in https://github.com/orbit-mvi/orbit-mvi/pull/142

    Full Changelog: https://github.com/orbit-mvi/orbit-mvi/compare/4.4.0...4.5.0

    Source code(tar.gz)
    Source code(zip)
  • 4.4.0(Sep 25, 2022)

    Features

    • Improved the DSL used to change settings - introduced scoped builders so only the things that should be configurable are exposed
    • Compose extension functions now have configurable lifecycle requirements (thanks @GuilhE)

    Fixes

    • liveTest could not be tested using a custom dispatcher and advanceUntilIdle - this was caused by the dispatcher being replaced in RealContainer and is now fixed
    • Documentation fixes (thanks @jisungbin @jlmcdonnell)
    • Testing documentation updates

    As always, massive thank you to our community for the suggestions, discussions and contribution 🙇

    What's Changed

    • Fix example code error by @jisungbin in https://github.com/orbit-mvi/orbit-mvi/pull/112
    • Adds state parameter to ContainerHostExtensions with Lifecycle.State.… by @GuilhE in https://github.com/orbit-mvi/orbit-mvi/pull/120
    • Update testing and compose documentation by @Rosomack in https://github.com/orbit-mvi/orbit-mvi/pull/133
    • Dependency update 09/22 by @Rosomack in https://github.com/orbit-mvi/orbit-mvi/pull/132
    • Fix live test issue by @Rosomack in https://github.com/orbit-mvi/orbit-mvi/pull/135

    New Contributors

    • @jisungbin made their first contribution in https://github.com/orbit-mvi/orbit-mvi/pull/112
    • @GuilhE made their first contribution in https://github.com/orbit-mvi/orbit-mvi/pull/120

    Full Changelog: https://github.com/orbit-mvi/orbit-mvi/compare/4.3.2...4.4.0

    Source code(tar.gz)
    Source code(zip)
  • 4.3.2(Feb 10, 2022)

    This is a minor release with the following features:

    Features

    • Initial state can now be omitted when putting the ContainerHost in test mode, in which case the real initial state will be used
    • Jetpack Compose version updated to 1.1.0

    Fixes

    • Jetpack Compose collect functions now correctly subscribe only when the lifecycle owner is at least STARTED
    • repeatOnSubscription used to hang forever in unit tests - now it always executes in a unit test

    As always, massive thank you to our community for the suggestions, discussions and contribution 🙇

    Special thanks:

    • @kaleidot725

    What's Changed

    • Fix architecture documentation. by @kaleidot725 in https://github.com/orbit-mvi/orbit-mvi/pull/104
    • Fix repeatOnSubscription in tests by @Rosomack in https://github.com/orbit-mvi/orbit-mvi/pull/106
    • Fixes to compose collect functions by @mattmook in https://github.com/orbit-mvi/orbit-mvi/pull/107
    • Use released version of compose by @mattmook in https://github.com/orbit-mvi/orbit-mvi/pull/110
    • Allow omitting initial state in tests by @Rosomack in https://github.com/orbit-mvi/orbit-mvi/pull/109

    New Contributors

    • @kaleidot725 made their first contribution in https://github.com/orbit-mvi/orbit-mvi/pull/104

    Full Changelog: https://github.com/orbit-mvi/orbit-mvi/compare/4.3.1...4.3.2

    Source code(tar.gz)
    Source code(zip)
  • 4.3.1(Feb 3, 2022)

    This is a minor release with the following features:

    Features

    • Support for Apple Silicon simulators
    • Updated to Kotlin 1.6.10 and coroutines 1.6.0

    Fixes

    • Minor documentation fixes
    • Add package to compose utility functions
    • Fix smart casting in reducers

    As always, massive thank you to our community for the suggestions, discussions and contribution 🙇

    Special thanks:

    • @Jon889
    • @mdigiovanni-wf
    • @fmendes6
    Source code(tar.gz)
    Source code(zip)
  • 4.3.0(Nov 12, 2021)

    This is a minor release fixing issues with creating DSL extensions and a adding few other minor features.

    Features

    • Expose classes necessary to create custom Orbit DSL extension
    • Updated androidx.lifecycle to 2.4.0 🎉
    • Updated to Kotlin 1.5.31 and coroutines 1.5.0
    • Other dependency updates
    • Added Flow<*>subscribe convenience function for iOS
    • Added convenience functions for Jetpack Compose 🚀 collectSideEffect and collectAsState in a new module.

    Fixes

    • Minor documentation fixes

    As always, massive thank you to our community for the suggestions, discussions and contribution 🙇

    Special thanks:

    • @StephanSchuster
    • @almozavr
    • @benoitthore
    Source code(tar.gz)
    Source code(zip)
  • 4.2.0(Aug 5, 2021)

    New features:

    • repeatOnSubscription operator allows you to safely collect infinite flows. The flows inside collect only when there are subscribers connected to the Container - as opposed to being collected over the container's lifecycle

    ⚠️ This release uses an alpha version (2.4.0-alpha02) of androidx.lifecycle libraries. This new version of the library introduces a new way to collect flows in Android components that ultimately fixes a bug which could cause flow emissions to be dropped. This Android bug affected our side effect flow.

    We tested the new approach and decided that on balance it was worth the risk to fix the long-standing issue.

    If you don't agree, feel free to skip this version until a suitable beta or full release version is included with orbit.

    Source code(tar.gz)
    Source code(zip)
  • 4.1.3(Jul 19, 2021)

    • Fixes an issue where the parent scope would not cancel coroutines launched when an exception handler was set in container settings.. Thanks @almozavr for the fix.
    Source code(tar.gz)
    Source code(zip)
  • 4.1.2(Jul 10, 2021)

    It seems there is a bug in kotlin multiplatform gradle where forcing a dependency does not work on the jvm* target we had to add the latest JUnit dependency to jvmMain.

    Additionally this release upgrades a bunch of dependencies, including kotlin to 1.5.20

    Source code(tar.gz)
    Source code(zip)
  • 4.1.1(Jul 8, 2021)

  • 4.1.0(Jul 7, 2021)

    • Now allowing testIntent to run functions that don't actually start any intent

    BREAKING CHANGES

    • runOnCreate is no longer a boolean parameter in the test and liveTest functions. It's now a function on *TestContainerHost. In the case of SuspendingContainerHost it is suspending. This is to enable running the onCreate lambda within the test's coroutine context.
    Source code(tar.gz)
    Source code(zip)
  • 4.0.0(Jun 28, 2021)

    BREAKING CHANGES

    Thanks to contributors and our own experience we've realised the current testing framework had a few problems:

    • "Blocking" test mode was going through the Orbit framework
    • As a result of the above, fine control over dispatching in tests was not possible
    • Tests were slightly slow
    • Test mode was too implicit and could be confusing

    As a result, we went back to the drawing board, focusing on the test framework API and blocking test mode. The blocking test mode is no longer blocking, now it's suspending. The tests bypass Orbit container dispatching and execute your intent blocks as suspending functions within the test's scope.

    Migration tips

    • No longer possible to execute ContainerHost intent functions directly after calling test() - the function now returns a test wrapper around the ContainerHost. Use testIntent to wrap the call.
    • testIntent is suspending, so you need to run your test in runBlockingTest (Android & JVM only) or runBlocking
    • Tests that rely on orbit dispatching (non-blocking tests) now need the ContainerHost to be configured using liveTest() instead of test()
    Source code(tar.gz)
    Source code(zip)
  • 3.1.1(Apr 30, 2021)

    This is a hotfix release that should fix infinite flow subscriptions not working and the event loop clogging up in general when an exception handler is installed,

    Source code(tar.gz)
    Source code(zip)
  • 3.1.0(Apr 26, 2021)

    In this release of orbit, we've prepared the project for the multiplatform future.

    BREAKING CHANGES

    • Removed currentState from Container and exposed Container.stateFlow as a kotlin StateFlow, please use Container.stateFlow.value now!

    Other changes

    • Added possibility to install flow exception handler (thanks @almozavr)
    • Minor housekeeping
    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(Feb 18, 2021)

    In this release of orbit, we've prepared the project for the multiplatform future.

    BREAKING CHANGES

    • The strict syntax has been removed. This was done for two reasons. Firstly, we think the default syntax aligns better with our idea of what Orbit should be. Additionally, removing this syntax allowed us to drop 7000+ lines of code so that will ease maintenance in the long term.

    If you are using the strict syntax this is still supported in the stable 2.2.0 release. However, we encourage you to migrate so you can benefit from our new features and improvements!

    Other changes

    • The core and test modules now support Android, JVM and iOS platforms. iOS support is currently very basic, we are working on bringing it up to feature parity with Android and JVM.
    Source code(tar.gz)
    Source code(zip)
  • 2.2.0(Jan 5, 2021)

Kotlin multi platform project template and sample app with everything shared except the UI. Built with clean architecture + MVI

KMMNewsAPP There are two branches Main News App Main The main branch is a complete template that you can clone and use to build the awesome app that y

Kashif Mehmood 188 Dec 30, 2022
简明易用框架,解决 MVI 实战痛点

?? English README 研发故事:《解决 MVI 架构实战痛点》 上期《Jetpack 架构组件设计拆解及改善建议》侧重拆解 “领域层” 设计误区,并给出改善建议 —— 通过 MVI-Dispatcher 承担 Event-Handler, 然有小伙伴表示,不仅要 MVI-Dispatc

KunMinX 91 Dec 8, 2022
Simple and extendable code highlighter in Kotlin Multiplatform

Kighlighter Simple and extendable code highlighter in Kotlin Multiplatform with a composable output to display the code highlighted on Android and Des

Gérard Paligot 21 Dec 2, 2022
Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP)

Mockative Mocking for Kotlin/Native and Kotlin Multiplatform using the Kotlin Symbol Processing API (KSP). Installation Mockative uses KSP to generate

Mockative 121 Dec 26, 2022
Jetpack Compose for Desktop and Web, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.

Jetpack Compose for Desktop and Web, a modern UI framework for Kotlin that makes building performant and beautiful user interfaces easy and enjoyable.

JetBrains 10k Jan 7, 2023
Integration Testing Kotlin Multiplatform Kata for Kotlin Developers. The main goal is to practice integration testing using Ktor and Ktor Client Mock

This kata is a Kotlin multiplatform version of the kata KataTODOApiClientKotlin of Karumi. We are here to practice integration testing using HTTP stub

Jorge Sánchez Fernández 29 Oct 3, 2022
FlowExt is a Kotlin Multiplatform library, that provides many operators and extensions to Kotlin Coroutines Flow

FlowExt | Kotlinx Coroutines Flow Extensions | Kotlinx Coroutines Flow Extensions. Extensions to the Kotlin Flow library | kotlin-flow-extensions | Coroutines Flow Extensions | Kotlin Flow extensions | kotlin flow extensions | Flow extensions

Petrus Nguyễn Thái Học 151 Jan 1, 2023
Commands - Simple work in progress command framework

Commands Purpose commands is a performant, flexible, future-rich, and easy-to-us

rawr 5 Nov 10, 2022
This Kotlin Multiplatform library is for accessing the TMDB API to get movie and TV show content. Using for Android, iOS, and JS projects.

Website | Forum | Documentation | TMDb 3 API Get movie and TV show content from TMDb in a fast and simple way. TMDb API This library gives access to T

Moviebase 37 Dec 29, 2022
Kotlin-phoenix - A set of tools aimed to bridge Phoenix with the Kotlin Multiplatform world

Kotlin Phoenix This project is aimed to allow developers to work with Phoenix Ch

Adrien Jacquier Bret 5 Sep 21, 2022
MMKV for Kotlin Multiplatform is a wrapper for MMKV using Kotlin API

MMKV for Kotlin Multiplatform is a wrapper for MMKV using Kotlin API

Ctrip, Inc. 65 Dec 29, 2022
Create libraries for all types of Kotlin projects: android, JVM, Multiplatform, Gradle plugins, and so on.

JavierSC Kotlin template Create libraries for all types of Kotlin projects: android, JVM, Multiplatform, Gradle plugins, and so on. Features Easy to p

Javier Segovia Córdoba 2 Dec 14, 2022
SSU u-saint parser with Kotlin-Multiplatform and Ktor.

kusaint Soongsil University(SSU) u-Saint Parser with Kotlin Multiplatform. Prerequisites JVM !!IMPORTANT!! To run kusaint as a library in JVM environm

Hyomin Koo 3 Mar 23, 2022
A Modern Kotlin-Ktor RESTful API example. Connects to a PostgreSQL database and uses Exposed framework for database operations.

kotlin-ktor-rest-api A Modern Kotlin-Ktor RESTful API example. Connects to a PostgreSQL database and uses Exposed framework for database operations. F

Selim Atasoy 32 Dec 20, 2022
The Okila server project uses the Spring boot framework and uses the Kotlin language

Okila-Server The Okila server project uses the Spring boot framework and uses the Kotlin language Format Response //The response successfully format

Nankai 1 Oct 25, 2021
A modular framework for building Discord bots in Kotlin using Kordex and Kord

Mik Bot A modular framework for building Discord bots in Kotlin using Kordex and Kord **If you are here for mikmusic, click here and there Deployment

Michael Rittmeister 31 Dec 24, 2022
MaxonBank is a Kotlin + Spring Boot + Axon Framework application that supports opening, depositing to, and withdrawing from accounts.

MaxonBank MaxonBank is a Kotlin + Spring Boot + Axon Framework application that supports opening, depositing to, and withdrawing from accounts. The ap

Max 1 Dec 30, 2021
KotlinDL - High-level Deep Learning Framework written in Kotlin and inspired by Keras

Оригинальный репозиторий https://github.com/JetBrains/KotlinDL KotlinDL: High-le

Temur Yunusov 1 Feb 4, 2022
Framework for Mobile test automation (Native app and Browser) on Android and IOS devices

Appium Mobile Automation Framework Framework for Mobile test automation (Native app and Browser) on Android and IOS devices ?? ?? Quick Start - Appium

Thangaraj 40 Nov 18, 2022