GithubHelp home page GithubHelp logo

copper-leaf / ballast Goto Github PK

View Code? Open in Web Editor NEW
143.0 143.0 11.0 59.32 MB

Opinionated Application State Management framework for Kotlin Multiplatform

Home Page: https://copper-leaf.github.io/ballast/

License: BSD 3-Clause "New" or "Revised" License

Kotlin 100.00% JavaScript 0.01%
hacktoberfest hacktoberfest2023 kotlin kotlin-multiplatform mvi

ballast's People

Contributors

asapha avatar charlee-dev avatar cjbrooks12 avatar dennistsar avatar iruizmar avatar seljabali avatar ubuntudroid 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

ballast's Issues

Cannot resolve debugger dependency

While setting up ballast I'm not able to properly define the debugger dependency. I always get the following error while syncing:

Could not find io.github.copper-leaf:ballast-debugger:4.0.0

I did a quick maven artifact search and the artifact indeed isn't available. There are some ballast related debug artifacts (ending with android-debug, but I'm not sure those are the same.

Refactor Repository module

After some time using and thinking about the Repository module, I've come to the conclusion that it's current stated use-case is not the most ideal for structuring your application. Most notably, the method of using an EventBus for communication between Repository instances is flawed. Instead, Repositories that depend on the state of other Repositories should be arranged in a hierarchy (either a true tree, or a Directed Acyclic Graph (DAG)), rather than something like an unstructured Graph as is the current setup.

The inspiration for this change comes from thinking about the application as a whole, and understanding the purpose of the Repository layer and the Repositories within that. Just as the UI screens and their ViewModels only ever passively observe from Repositories, and thereby isolate themselves from the implementation details of the Repository while also eliminating strange communication patterns by enforcing the UDF flow, the same should be true of Repositories amongst themselves. Parent repositories should send their state to children, which eventually flows to UIs, to deeper components within the UI, etc.

Ultimately, it should follow a flow that is something like this:

flowchart TD
    Global["Global State (authentication)"]
    Router
    Repositories
    Screens
    Components
    
    Global --> Router
    Global --> Repositories
    Router --> Screens
    Repositories --> Screens
    Screens --> Components

The extent of changes should be as follows:

  • Remove the Repository base classes. Just use the standard ViewModels already available. Each Repository should define its own Events as with any other ViewModel (though there may not be many use-cases for them in Repositories)
  • Make the Cached API generic enough such that it could be used in any ViewModel, or even outside of a ViewModel if it needs to be done in a standard suspending function
  • Create an out-of-the-box implementation for the Contract and InputHandler that makes it very easy to plug the Cached wrapper into a Repository ViewModel without all the boilerplate required now
  • Remove the EventBus
  • Rewrite documentation to encourage passing parent ViewModels into the constructor of another, rather than reading from an EventBus

Additional nice-to-have features include:

  • Support additional methods of busting the Cached values in the Repository, such as time-based or (depending on platform) based on total size of data stored in the ViewModel

Support WASM target

Create a version of Ballast that supports WASM targets. It will likely need to wait until all of its libraries also support WASM (coroutines, serialization, UUID, etc.) or only develop this on another branch and only release the modules which don't have any external dependencies other than Coroutines.

[ballast-test] Refactor test module to rely entirely upon an Interceptor

Right now, the Test module implements a custom ViewModel and wraps everything in a really terrible, hacky way so that it can inspect each Input and wait for them to be completed. But now, with a more robust Interceptor API, it should be possible to directly observe what's sent through the Interceptor to implement the same test logic of waiting for Inputs to complete, without messing with the internals of the ViewModel or creating special APIs just for the Test module (BallastViewModelImpl.awaitSideEffectsCompletion())

Add module for automatic retries

Using annotations on inputs, have an interceptor track whether an input failed, and if so, send it again to be retried. Include a callback which is notified when the same input has failed many times, exceeding some user-defined threshold.

Compose's onValueChange sometimes results in IllegalStateException: VM is cleared!

Hi,

I'm seeing a few crashes from users exiting a specific screen.
As the app uses Compose's NavHost, the culprit VM is scoped to a NavBackStackEntry.

I can't reproduce the crash on my devices but here's what I could gather:

  • User enters a screen with an OutlinedTextField (uses a TextFieldValue)
  • Writes into it
  • Navigates back (I'm assuming via AppBar's navigation icon)
  • The VM gets cleared
  • onValueChange is called
  • ballast calls checkValidState

To investigate, I've overridden my ViewModel's onCleared

class MyVM : AndroidViewModel<Inputs, Events, State>(
   [...]
    override fun onCleared() {
        Logs.i("onCleared")
        super.onCleared()
    }

And added logs to onValueChange

    onValueChange = {
        Logs.i("onValueChange")
        currentName = it
        vm.trySend(NameEditorContract.Inputs.ChangeName(it.text))
    }

Then, with a Pixel 4A 5G:
It's possible to have onValueChange displayed after onCleared, but only if the cursor is not placed at the end of the OutlinedTextField.
(Note that it doesn't crash for me as BallastViewModelImpl.onCleared hasn't been called yet)

But on a LGV30, onValueChange is never displayed after onClearedโ€ฆ

Not sure if ballast is too strict or if compose doesn't respect some sort of contract here,
what do you think?

Stacktrace:

Fatal Exception: java.lang.IllegalStateException: VM is cleared!
       at com.copperleaf.ballast.internal.BallastViewModelImpl.checkValidState(BallastViewModelImpl.kt:198)
       at com.copperleaf.ballast.internal.BallastViewModelImpl.trySend-JP2dKIU(BallastViewModelImpl.kt:102)
       at com.copperleaf.ballast.core.AndroidViewModel.trySend-JP2dKIU(AndroidViewModel.kt:2)
       at com.azefsw.audioconnect.settings.ui.name.NameEditorViewKt$NameEditorView$2$1$1.invoke(NameEditorView.kt:59)
       at com.azefsw.audioconnect.settings.ui.name.NameEditorViewKt$NameEditorView$2$1$1.invoke(NameEditorView.kt:57)
       at androidx.compose.foundation.text.BasicTextFieldKt$BasicTextField$7$1.invoke(BasicTextField.kt:279)
       at androidx.compose.foundation.text.BasicTextFieldKt$BasicTextField$7$1.invoke(BasicTextField.kt:277)
       at androidx.compose.foundation.text.TextFieldState$onValueChange$1.invoke(CoreTextField.kt:762)
       at androidx.compose.foundation.text.TextFieldState$onValueChange$1.invoke(CoreTextField.kt:757)
       at androidx.compose.foundation.text.TextFieldDelegate$Companion.onBlur$foundation_release(TextFieldDelegate.java:242)
       at androidx.compose.foundation.text.CoreTextFieldKt.onBlur(CoreTextField.kt:840)
       at androidx.compose.foundation.text.CoreTextFieldKt.access$onBlur(CoreTextFieldKt.java:1)
       at androidx.compose.foundation.text.CoreTextFieldKt$CoreTextField$2$invoke$$inlined$onDispose$1.dispose(Effects.kt:485)
       at androidx.compose.runtime.DisposableEffectImpl.c(Effects.kt:4)
       at androidx.compose.runtime.CompositionImpl$RememberEventDispatcher.dispatchRememberObservers(Composition.kt:995)
       at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:774)
       at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:794)
       at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:526)
       at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:454)
       at androidx.compose.ui.platform.AndroidUiFrameClock$withFrameNanos$2$callback$1.doFrame(AndroidUiFrameClock.android.kt:8)
       at androidx.compose.ui.platform.AndroidUiDispatcher.performFrameDispatch(AndroidUiDispatcher.java:109)
       at androidx.compose.ui.platform.AndroidUiDispatcher.access$performFrameDispatch(AndroidUiDispatcher.java:41)
       at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:69)
       at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1002)
       at android.view.Choreographer.doCallbacks(Choreographer.java:816)
       at android.view.Choreographer.doFrame(Choreographer.java:748)
       at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:990)
       at android.os.Handler.handleCallback(Handler.java:873)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:193)
       at android.app.ActivityThread.main(ActivityThread.java:6762)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

ballast version 1.1.0
compose version: 1.2.0-beta02

Add sample application to the docs site

Using Compose/Web create an interactive example TODO application based on Ballast. It should highlight some of the core concepts of Ballast, and also display the Kotlin source code within that page so users can see how it all fits together.

[ballast-debugger] Add generic Debugger interface for reporting custom data

It would be nice to see other information reported in the Debugger that is defined by the end-user. Examples would be Analytics events, HTTP calls, or SQL queries. This would bring more information to one single place that would be very useful in local development. And if I could create a standalone Desktop Application or Browser Extension for the debugger, would even be useful for QA/UAT engineers to verify things like analytics are being tracked properly.

This would add a feature where one could register some generic "logging" interface anywhere in their code that also connects to the Debugger. These logging events aren't necessarily part of a ViewModel, but instead would go directly to the Connection and be sent to the DebuggerUI, showing up as an extra panel in the connection view. A basic DSL would allow one to specify properties such as the title and description (shown as list items in the UI) and a map of extra properties. It should also allow one to create IDs for these events and update the status of a single event over time (for example, logging an HTTP call before it's made, then setting the response status code and body after the call finishes).

There should be out-of-the-box adapters available for Okhttp and Ktor HTTP clients, Ballast Analytics, and Sqldelight. End-users would be free to create their own reporters for anything else they would like to track as well.

Add module for formal Finite State Machine

Ballast is basically an FSM already, so it would be nice to have a proper FSM DSL like Tinder/StateMachine backed by a Ballast ViewModel, so that it could be used in a multiplatform project.

It would also be nice to extend that DSL to track additional state in the machine rather than just the declarated Type of each state, and send data through the Edges to create distinct States. Additionally, being able to extend the FSM with push-down automata would also be great, being able to inspect a stack of states and use that to determine the results of new actions.

Being able to integrate this DSL as an InputFilter would able be nice

[ballast-repository] Support observing flows and time-based expiry

Add built-in features to ballast-repository module to observe flows instead of only doing one-shot fetches. Also, it should support time-based expiry for cache, such that it may still be refreshed when fetchWithCache is called even if forceRefresh is false. it might also make sense to support a pluggable refresh policy that has implementations for force refresh and/or time-based expiry.

For an example use-case, consider the refreshing logic in the repository layer of KaMPKit

Config classes have lifecycles that are required to match the lifecycle of VMs

VM config classes (and even config builder classes) are not independent in terms of lifecycle from the VMs constructed with them.

When this invariant is not held, then the system completely breaks. Adding logging shows that it throws ClosedSendChannelExceptions and then becomes unresponsive, as shown in the second reproduction in https://github.com/rocketraman/test-ballast-4-csce.

The workaround is simply to instantiate the config builder and config each time the VM is itself instantiated. In other words, when using a DI system, use a factory or provider rather than a singleton.

However, noting this issue because the API design allows for configs to be instantiated separately, but the implementation does not. This isn't the best API affordance for consumers, and should be "fixed" using one of the following solutions -- and I'm putting them in the order that I think is best to worst, from the perspective of an API consumer:

  1. The implementation of Ballast's VMs should be modified so that the lifecycle of a config is not tied to the lifecycle of the VM it is injected into.

  2. Modify the API in such a way that API consumers cannot do something so simple to break Ballast. The constructor of a VM could accept the config parameters directly, or it could accept a builder object which creates new instances of any stateful classes specified by the config at VM construction time.

  3. At the least, update documentation to explain the lifecycle of a config class, and update kdocs as well.

Reference conversation from Slack: https://kotlinlang.slack.com/archives/C03GTEJ9Y3E/p1699907007258999?thread_ts=1699623937.047949&cid=C03GTEJ9Y3E.

awaitViewModelStart not awaiting

Unless I'm missing something, the current implementation of awaitViewModelStart doesn't do anything, as take isn't a terminal operator.

/**
 * Suspend until the ViewModel has started
 */
public suspend inline fun <Inputs : Any, Events : Any, State : Any> Flow<BallastNotification<Inputs, Events, State>>.awaitViewModelStart() {
    filter {
        it is BallastNotification.ViewModelStatusChanged<Inputs, Events, State> && it.status == Status.Running
    }.take(1)
}

[examples] Split examples into separate sub-projects, rather than separated by platform

The current examples are separated by Platform (web, android, desktop), and the same features are re-implemented in each platform with very little difference between them. This also makes the example projects have a bunch of additional "architecture" that may make it difficult to understand the intended use-case and configuration of these features.

Instead, let's break these examples out into a handful of separate, much simpler example projects. The majority of these should be defined as Compose Multiplatform projects with Android, Desktop, and Web targets, with as much as possible defined in the commonMain sourceset. These examples can then each be individually embedded into their respective documentation pages.

The goal is for each project to include the absolute minimum amount of boilerplate necessary to demonstrate a particular feature of Ballast, as well as avoid duplication of the examples. This will also allow more flexibility in different kinds of examples, as each one is easier to setup and maintain. The idea is to show less about how to set up your KMP repos, and focus more on how to setup and use Ballast.

[ballast-debugger] Save ViewModel states/inputs, and run "scripts" of multiple Inputs

Now that the Debugger allows one to send States or Inputs as JSON, the next logical improvement to this feature is to make it easier to use across multiple sessions. This would involve several additional features:

  • Let users create a "library" of Inputs/States, so they can easily send the same values back to their ViewModels. These should be scoped to the Project, as well as the ViewModel name.
  • Let users define "scripts", which send multiple JSON inputs in succession. These Inputs would be ones defined in their "library", or listed ad-hoc for a single run. Additionally, users can configure custom delays between Inputs, or configure it to wait for confirmation that the Input has completed.
  • Once "scripts" are ready, allow one to "record" a series of actions in the ViewModel, save them to their library, and then replay that script.

[ballast-debugger] Allow client to work with Ktor client 1.x and 2.x

Currently, because of a mismatch in Kotlin versions between Ktor 1.x, Ktor 2.x, and Compose, the current version of the Ballast debugger only works with the Ktor 1.x. The Websocket client should be pluggable so that the default behavior is the 1.x client, but if needed one could connect to a 2.x client they write themselves, or even use a different HTTP client library altogether

Add KSP plugin for generating boilerplate, to make Ballast less verbose

Basic idea: add annotations to Contract object, and generate typealiases and ViewModel implementations that make it much nicer to read Ballast code you write.

Example:

@BallastContract
object ExampleContract {
    // ...
}

Would generate:

typealias ExampleInputHandler = InputHandler<ExampleContract.Inputs, ExampleContract.Events, ExampleContract.State>
// similar typealiases for other interfaces you'll implement,and their scopes

Ideally, the processor would be smart enough that it could generate typealiases and ViewModels of arbitrary types without special support, meaning plugins could have their boilerplate generated using the same semantics without needing to create their own processors. It would require the user passing the class reference to the @BallastContract annotation, and those classes having the right number of type parameters, a single public constructor, etc.

IntelliJ Plugin

The Debugger UI is nice, but it would be nicer if it was bundled as an IntelliJ plugin instead. IntelliJ plugins already support Compose Desktop, so it's more of a matter of getting the project structured to build and publish the plugin, and move the debugger UI code there instead

Send inputs on ViewModel initialization

Sometimes there's the need to launch an Input right on ViewModel init. For example, when you need to fetch some data to shown on the screen.

Checking the examples, I've seen that the way it's been done is to use the Activities onCreate, but in other architectures like a pure Compose app, using lifecycle is not really the way. Also, delegating this kind of internal actions to the view doesn't look completely correct.

Another approach I've been using is to use the init block of the ViewModel, but this is a bit cumbersome since, ideally, you will declare different ViewModels for each platform and you'll have to replicate this logic in each of them:

class MyViewModel(
    config: BallastViewModelConfiguration<MyContract.Input, MyContract.Event, MyContract.State>,
) : AndroidViewModel<MyContract.Input, MyContract.Event, MyContract.State>(config) {
    init {
        trySend(MyContract.Input.MyInput)
    }
}

My proposal is to be able to indicate a list of desired Inputs to be launched on ViewModel initialization. You can see how some other state management libraries are doing it here.

Ideally this will be set in the InputHandler since this is the class written in common code.

Add distributed queue module

Another server-side use case is a background queue, such as AWS SQS. This module could allow the Ballast API to run on top of an SQS queue, implemented as an Input strategy.

Inputs sent to the VM would be automatically serialized and placed into the queue. Any other distributed server could then read from the queue, deserialize the input, and then process it.

A first POC should disable the use of State through the use of a custom InputStrategy Guardian. This would effectively make a Ballast distributed queue a purely Stateless queue. Future work could add some way to store and rehydrate state for each Input pulled from the queue, which would allow it to process longer distributed "user journeys".

Further extensions could support specific types of common Java server-side queues, like sending transactional emails, SMS messages, or push notifications.

Open questions are mostly the same as with the Scheduler.

How should Ballast be used from Swift code?

I am not an iOS developer, so I don't really know how Ballast should be integrated into an iOS app. I've got some basic documentation showing how it could be done, how I've managed to make it work as a POC, but the solution there is far from optimal.

I need someone who's done a lot of KMM development to help me figure out the best way to integrate Ballast into an iOS app. I would like to have patterns for both a pure Swift UI app, as well as a traditional UIKit application. The biggest unknowns for me right now are how to handle navigation from a Ballast Event Handler, and how to manage the lifecycle of the ViewModel.

Add module to handle undo/redo functionality

Add a module to implement undo/redo functionality. it should contain a "controller" which can be used by one or many ViewModels, and your application code calls .undo() or .redo() on it.

The controller keeps track of the states and Inputs that went through each of its connected ViewModels in a buffer of "frames" (whose entries can be configured to expire by either time, number of frames or total size in memory). It captures changes into frames, and calling undo/redo will restore the state of the previous/next frame.

It is probably out of scope to handle undo/redo of persistent things (would not un-delete a file for example). Maybe an annotation or interface to give manual control of what to undo might be doable.

[Intellij plugin] Replace Material UI with Jewel

The Material UI definitely looks out of place in the IDE, but Jetbrains has started working on a theme to integrate Compose Desktop apps seamlessly with IntelliJ, Jewel.

The UI could also do with some better organization to make the actions on each component in the Debugger more clear, and also provide more info at-a-glance. Here's a screenshot from initial unfinished updates with a 3rd-party Intellij theme, showing how it could be improved

Screen Shot 2022-12-12 at 4 09 22 PM

[ballast-saved-state] add option for a minimum delay between "save" requests

For ViewModels that are updated very quickly, it may be desirable to skip some State emissions in order to not overload the disk and make sure that writes to the disk don't slow down the main ViewModel's processing.

To that end, all writes should be persisted asynchronously, and a configurable delay between emissions. By default, the delay will be 0. If there is a delay, then that much time will wait before sending the state to the adapter. If a new State is emitted before that delay completes, then the latest State value will be persisted. This needs to be more clever than a simple .mapLatest, as we don't want to reset the timer because emissions may continually prevent the adapter from ever being called.

[ballast-debugger] Add timeline and diagram view of activity

Show another tab of a selected View model which displays a graphical view of ViewModel activity.

  • TImeline: The view should scroll automatically with time, and show lines depicting when inputs, events, and side jobs were processing, as well as dots for when states changed.
  • Diagram: Show a diagram similar to that in the mental model doc. Highlight the various nodes and edges, to show what's currently active in the VM at any given time. Maybe even animate the objects moving along lines to really help drive it home whats going on

Ballast Community Chat

Hello,

I think it'd be beneficial to create a Slack channel in the Kotlin Slack Workspace for this project. There's much to talk about in making this project bigger.

Best,
-Sami

P.S. I'm looking for a solid KMM MVI library after finding Orbit MVI not well supported when it comes to KMM/iOS, hence want to help in that department in any way possible.

iOS instructions out of date?

I'm investigating which state management library I want to use for my multi-platform (iOS, Android) app. From what I'm reading Ballast seems like an interesting alternative.

I'm following the instructions on the web site: https://copper-leaf.github.io/ballast/wiki/platforms/ios/, but I get compiler errors when trying to compile the CombineAdapters file (copied from the KaMPKit repo as per the instructions): FlowAdapter and EventHandler types cannot be found by Xcode.

After a lot of searching, I think the documentation might be out of date, because if I add the dependencies and exports for ballast-api and ballast-viewmodel, the file is compiled correctly.

    cocoapods {
        summary = "Some description for the Shared Module"
        homepage = "Link to the Shared Module homepage"
        version = "1.0"
        ios.deploymentTarget = "14.1"
        podfile = project.file("../TryBallast/Podfile")
        framework {
            baseName = "shared"
            isStatic = false // SwiftUI preview requires dynamic framework
            export("io.github.copper-leaf:ballast-api:3.0.1")           // <=====
            export("io.github.copper-leaf:ballast-core:3.0.1")
            export("io.github.copper-leaf:ballast-repository:3.0.1")
            export("io.github.copper-leaf:ballast-viewmodel:3.0.1")      // <=====
        }
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
                api("io.github.copper-leaf:ballast-api:3.0.1")           // <=====
                api("io.github.copper-leaf:ballast-core:3.0.1")
                api("io.github.copper-leaf:ballast-repository:3.0.1")
                api("io.github.copper-leaf:ballast-viewmodel:3.0.1")    // <=====
            }
        }
        [...]
    }

[ballast-repository] Add a generic caching Repository implementation

There is a lot of boilerplate involved in using Ballast Repository with Cached<T> values, and nearly all of that is entirely generic. The only part of it that is actually useful is the fetcher function itself. There should be a default implementation that manages its cached values in a map and defines all the Inputs necessary for the entire pattern, so users only need to provide functions to map Keys to cached values.

[ballast-test] Add a way to test Inputs in isolation

In addition to running "scripts" which send a series of Inputs and assert the result of all of them, there should be a way to mock everything out except the InputHandler. This new helper function should create a mock InputHandlerScope and stub out everything except for a StateFlow of the current state, and pass that with a single Input to an InputHandler. After running the test, it will collect TestResults and deliver those to the test for making assertions

Add module to synchronize clients across a network

A module to allow synchronizing multiple clients over a remote connection.

  • Implemented as an interceptor. Requires all clients to be on the same version.
  • One client is selected as the master. It receives serialized inputs from all other clients, and pushes the serialized state and events to those clients.
  • All other clients send Inputs back to the master client. These clients process their own Inputs for rapid feedback, but ultimately it should be expected that after local processing, the state will be updated from the master node later.
  • Inputs and State must be serializable. Provide the kserializer to the connection.
  • Communication is done over websockets. Ideally, find a way to communicate via websockets over the internet without port forwarding, and without an ngrok type middleman service. I'm not sure if that's even possible...
  • All Inputs must be delivered back to the master node. But states should be buffered as it is sent back to clients, to save on network bandwidth.
  • Don't impose any hard limits to the number of connected clients.
  • All clients should automatically reconnect
  • Allow both spectator and participant clients. Include APIs for filtering inputs spectators are allowed to send to the master
  • include an API for scrubbing the state as it is sent to each client, hiding data that is in the master node's state for management, but a client should not be able to see, or that one client should not see from another.
  • have a desktop app that runs the master node, and includes a lobby feature. But allow other clients (like web or mobile) to also be master node's
  • Pluggable communication protocols. Synchronize via TCP, LAN, Bluetooth, Bonjour, etc.

Add scheduling module

One use case for Ballast in server side applications, or in front end apps without Androids WorkManager, is in scheduling jobs to be run repeatedly in the background. Ballast could support this fairly easily by creating a new module that adds an Interceptor into a View model which dispatches inputs at the appropriate time.

A first POC could be a basic timer-based scheduler. The next addition would be supporting cron syntax for running jobs at a specific time interval.

A final step would be to somehow run the ViewModel in a WorkManager and tie into the filtering behaviors there. It would also be nice to support the same kind of filtering by network state or other properties in iOS or JS.

Open questions:

  • does it need a distributed lock, for running the same task in a server-side environment? Or simply delegate that to the end-user to figure out?
  • How to handle failures in the scheduled tasks? Automatic retires (possibly as a separate module)? How to coordinate those retries with what's sent by the scheduler?
  • should it collect statistics about failures/successes? For notifying or pausing the schedule like a circuit breaker?

[Intellij plugin] Add feature to send/receive app states as JSON

While it is useful to be able to rollback to previous states, it would be really nice to be able to upload JSON to the client application, which can deserialize it and set it as the ViewModel's State. Additionally, it would be nice for clients to be able to send serialized States rather than the .toString() version of State, so that they can be explored in a tree view in the UI.

Ideally, this would be implemented in a way completely agnostic of the client serialization framework. There would just be a callback to (de)serialize States, and the debugger just trusts that the client is configured correctly to accept it.

Add module to automatically save and restore state

The current implementation expects that ViewModel state that should be persisted between sessions, is stored with a custom persistent store. There is no built-in support for saving VM state, and restoring it at a later point in time. A custom Interceptor should be created that will watch all state changes and persist them with a pluggable "store". The interceptor will watch all state changes and persist the state, and it will restore the state when the VM is restarted.

Out-of-the-box, it should include "stores" for:

  • in memory (works on all targets)
  • Android SavedStateHandle
  • Simple API for manually observing/persisting/restoring properties of the state

Textfield adding extra characters on typing

Hi. I've got an issue with my text field adding extra characters. I checked it using mutableStateOf and it works as expected. Code related to the email below, nothing fancy...

in Contract

internal object LoginContract {
    data class State(
        val email: String = "",
    )

    sealed interface Inputs {
        data class ChangeEmail(val newEmail: String) : Inputs
    }

    ...
}

in Content

    OutlinedTextField(
        text = state.email,
        onTextChange = { vm.trySend(LoginContract.Inputs.ChangeEmail(it)) },

    ...
}

in Input

internal class LoginInputHandler : KoinComponent, InputHandler<LoginContract.Inputs, LoginContract.Events, LoginContract.State> {
    override suspend fun LoginInput.handleInput(input: LoginContract.Inputs) = when (input) {
        is LoginContract.Inputs.ChangeEmail -> updateState { it.copy(email = input.newEmail) }

        ...
    }
}
CleanShot.2023-02-13.at.10.27.45.mp4

[spike] Investigate the use-case for Ballast in server-side applications

With the 4.0.0 updates to InputStrategies, Ballast could conceivably be updated to support distributed processing in a server-side environment. Think of something like the State is held in Redis, and Inputs are AWS SQS messages. It would be interesting to see if it would be possible to build such an integration while maintaining compatibility with the existing Interceptors and API.

Add a "ViewModel provider" to Navigation

While the current recommendation for Ballast Navigation is to keep ViewModels ephemeral and manage data shared between screens in a Repository, there are still some valid use cases for keeping a ViewModel instance alive and tied to the state of the Backstack.

For instance, a sub-flow that shares data among multiple steps in a mobile view. This data is technically persistent in that it is shared by multiple screens, but ephemeral in that the data should be cleared once the user exits or completes the flow. The current method would require managing that data globally in an application Repository, but then you need to keep track of when the user has left the flow to know when to clear it. This is a lot of bookkeeping that is easy to get wrong if done manually.

I think the best solution is to have an optional feature in the ballast-navigation artifact that scopes CoroutineScope instances to each entry in the Backstack. The scope is killed once the Backstack entry leaves the Backstack. Users then register ViewModels or other dependencies against that CoroutineScope. Notably, this API is not a full DI feature. It allows you to register a factory function to create some instance, and an API to get or create those instances. The CoroutineScope allows one to hook into cleanup events.

This basically reimplements the Android ViewModel provider functionality, but in KMP, and with less ceremony and no reflection.

[ballast-navigation] Remove ktor-http dependency

The io.ktor:ktor-http dependency of Ballast Navigation module includes the necessary functionality for parsing URLs, but also includes a bunch of other stuff used by the Ktor HTTP server/client that is not necessary for parsing URLs. It's a much bigger dependency than I initially thought when adopting it, and is prohibitively large for JS applications, producing bundles that are unacceptably large, and it should be removed and replaced.

Ideally, the replacement would be a fully-multiplatform URL parser, like Ktor, but in the absence of such a library, it will likely be better to actual/expect the URL functionality.

KSP Processor for type-safe Navigation API

I recently created an example project showing how one might make an alternative API surface for Ballast Navigation. It's described as something that could be implemented with KSP, though the processor is not implemented. It simply shows what kind of code might be generated.

I don't think I want to maintain a KSP processor for this kind of Navigation API directly within the Ballast repo, mostly because there are many different ways this generated API could look, and the "best API" is going to be highly subjective. But it could be a great idea for someone to build and maintain as a community library!

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.