Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

VisualFSM - Kotlin Multiplatform library for FSM based MVI with visualization and analysis tools

License

NotificationsYou must be signed in to change notification settings

Kontur-Mobile/VisualFSM

Repository files navigation

VisualFSM

MavenCentralTelegramTelegram

ENG |RUS

VisualFSM is a Kotlin Multiplatform library for implements anFSM-based (Finite-state machine)[2]MVI pattern(Model-View-Intent)[1] and a set of tools for visualization and analysis ofFSM's diagram of states.

The graph is being built from source code ofFSM's implementation. There is no need of customwritten configurations forFSM, you can just create new State and Action classes, they would beautomatically added to the graph of States and Transitions.

Source code analysis and the graph built are being performed with reflection and declared as aseparate module that would allow it to be connected to testing environment.

Contents

Overview of library modules

Quickstart

External state source

VisualFSM Pros

Structure of VisualFSM

Samples of usage

Overview of library modules

Base classes for Android, JVM and KMP projects (Feature and AsyncWorker coroutines edition)

implementation("ru.kontur.mobile.visualfsm:visualfsm-core:$visualfsmVersion")

Support of RxJava 3 (FeatureRx, AsyncWorkerRx and dependent classes)

implementation("ru.kontur.mobile.visualfsm:visualfsm-rxjava3:$visualfsmVersion")

Code generation

ksp("ru.kontur.mobile.visualfsm:visualfsm-compiler:$visualfsmVersion")

Classes for easy getting generated code

implementation("ru.kontur.mobile.visualfsm:visualfsm-providers:$visualfsmVersion")

Graph creation and analysis

testImplementation("ru.kontur.mobile.visualfsm:visualfsm-tools:$visualfsmVersion")

Quickstart

See inQuickstart

External state source

See inExternal state source (optional)

VisualFSM Pros

Visualization of FSM graph

Visualization lets you spend less time on understanding complex business process and makes it easierfordebugging,adding new features, andrefactoring old ones.

graph

A simplified FSM graph sample of user authorization and registration.

Visualization customization

To increase the readability of the graph, you can control the rendering rules using the 'DotAttributes' object when generating the graph.You can use 'DotAttributesDefaultPreset' class or create own preset for your project.

graph with attributes

Sample of usage DotAttributes

Analysis

Validation on reachability for all states, on set of terminal states and lack of unexpected dead-endstates, custom graph checks in unit tests.

Managing asynchronous operations

Every async work can be represented by separate states, because of this we can have a common set ofstates that are lining up to a directed graph.

An AsyncWorker allows you to simplify the processing of states with asynchronous work.

Structure of VisualFSM

The main entities areState,Action,Transition,Feature,AsyncWorker,TransitionCallbacks.

State

State is an interface to mark State classes.

Action

Action is a base class for action, used as an input object for FSM and describes the transitionrules to other states byTransition classes. A state is being selected depending of the currentFSM'sState and provided predicate (thepredicate function). There are two scenarios that wouldsay the transition rules were set wrong:

  1. If there are severalTransitions that would fit the specified conditions: aState the FSM wasin is inside aTransition and apredicate returnstrue — there would be an error passed toaTransitionCallbacks,onMultipleTransitionError would be called, and the firstsuitableTransition would be executed.
  2. In case noTranstion will do, an error would be passed to aTransitionCallbacks,onNoTransitionError would be called, and aState won't be changed.

Transition

Transition is a base transition class and is declared as an inner class in anAction. There mustbe two genericStates for everyTransition: aState, the one the transition is going from, andaState that is going to be current for FSM after atransofrm execution.

For the inherited classes ofTransition you need to override atransform method andapredicate method, butpredicate must be overridden only if you have more than oneTransitionwith similar startStates.

The predicate and transform functions of Transition

  • predicate describes the conditions of aTransition's choice depending on input data that waspassed to anAction's constructor. It is a one of conditions for the choice ofTransition. Thefirst condition is that the currentState has to be the same as theTransition's startStatewhich was specified in generic. You might not to overridepredicate if you don't have more thanoneTransition with matching startStates.
  • transform creates a newState for aTransition.

Types of Transition

Transition is a basic type ofTransition. It can accept the following generic parameters:State or a set ofState as a sealed class

Transitions forming for `Transition` Let's take a look at the example
sealedclassFSMState :State {data objectInitial :FSMState()sealedclassAsyncWorkerState :FSMState() {data objectLoadingRemote :AsyncWorkerState()data objectLoadingCache :AsyncWorkerState()    }data objectLoaded :FSMState()}

Ifdata object Initial anddata object Loaded are passed to the generic parameter

innerclassTransition :Transition<Initial,Loaded>() {overridefuntransform(state:Initial):Loaded {// ...    }}

A possibility of the following transitions appears in the FSM:

  • Initial ->Loaded

Ifdata object Initial andsealed class AsyncWorkerState are passed to the generic parameter

innerclassTransition :Transition<Initial,AsyncWorkerState>() {overridefuntransform(state:Initial):AsyncWorkerState {// ...    }}

A possibility of the following transitions appears in the FSM:

  • Initial ->AsyncWorkerState.LoadingRemote
  • Initial ->AsyncWorkerState.LoadingCache

Ifsealed class AsyncWorkerState andsealed class AsyncWorkerState are passed to the generic parameter

innerclassTransition :Transition<AsyncWorkerState,AsyncWorkerState>() {overridefuntransform(state:AsyncWorkerState):AsyncWorkerState {// ...    }}

A possibility of the following transitions appears in the FSM:

  • AsyncWorkerState.LoadingRemote ->AsyncWorkerState.LoadingRemote
  • AsyncWorkerState.LoadingRemote ->AsyncWorkerState.LoadingCache
  • AsyncWorkerState.LoadingCache ->AsyncWorkerState.LoadingCache
  • AsyncWorkerState.LoadingCache ->AsyncWorkerState.LoadingRemote

Ifsealed class AsyncWorkerState anddata object Loaded are passed to the generic parameter

innerclassTransition :Transition<AsyncWorkerState,Loaded>() {overridefuntransform(state:AsyncWorkerState):Loaded {// ...    }}

A possibility of the following transitions appears in the FSM:

  • AsyncWorkerState.LoadingRemote ->Loaded
  • AsyncWorkerState.LoadingCache ->Loaded

SelfTransition is a type ofTransition that implements a transition fromState toState with the same type. It can accept the following generic parameters:State or a set ofState as a sealed class

Transitions forming for `SelfTransition` Let's take a look at the example
sealedclassFSMState :State {data objectInitial :FSMState()sealedclassAsyncWorkerState :FSMState() {data objectLoadingRemote :AsyncWorkerState()data objectLoadingCache :AsyncWorkerState()    }data objectLoaded :FSMState()}

Ifdata object Initial is passed to the generic parameter

innerclassTransition :SelfTransition<Initial>() {overridefuntransform(state:Initial):Initial {// ...    }}

A possibility of the following transitions appears in the FSM:

  • Initial ->Initial

Ifsealed class AsyncWorkerState is passed to the generic parameter

innerclassTransition :SelfTransition<AsyncWorkerState>() {overridefuntransform(state:AsyncWorkerState):AsyncWorkerState {// ...    }}

A possibility of the following transitions appears in the FSM:

  • AsyncWorkerState.LoadingRemote ->AsyncWorkerState.LoadingRemote
  • AsyncWorkerState.LoadingCache ->AsyncWorkerState.LoadingCache

AsyncWorker

AsyncWorker

AsyncWorker controls the start and stop of async tasks.AsyncWorker starts async requests orstops them it it gets specifiedState via a subscription. As long as the request completes witheither success or error, theAction will be called and the FSM will be set with a newState. Forconvenience those states that are responsible for async tasks launch, it is recommended to join theminAsyncWorkState.

To subscribe toState, you need to override theonNextState method, and for each state to constructAsyncWorkerTask for processing in the AsyncWorker.For each operation result (success and error) you must call the proceed method and passAction to handle the result.Don't forget to handle each task's errors inonNextState method, if an unhandled exception occurs,then fsm may stuck in the current state and the onStateSubscriptionError method will be called.

There might be a case when we can get aState via a subscription that is fully equivalent tocurrent running async request, so for this case there are two type of AsyncWorkTask:

  • AsyncWorkerTask.ExecuteIfNotExist - launch only if operation with equals state is not currently running (priority isgiven to a running operation with equals state object)
  • AsyncWorkerTask.ExecuteIfNotExistWithSameClass - launch only if operation with same state class is not currently running (priority isgiven to a running operation with same state class, used for tasks that deliver the result in several stages)
  • AsyncWorkerTask.ExecuteAndCancelExist - relaunch async work (priority is for the new on).

To handle a state change to state without async work, you must use a task:

  • AsyncWorkerTask.Cancel - stop asynchronous work, if running

Feature

Feature is the facade for FSM, provides subscription on currentState, andproceeds incomingActions.

TransitionCallbacks

TransitionCallbacks gives access to method callbacks for third party logic. They are great forlogging,debugging,metrics, etc. on six available events: when initialState is received, whenAction is launched,whenTransition is selected, a newState had been reduced, and two error events —noTransitions or multipleTransitions available.

LogParams

Logging parameters for the built-in logger, if the capabilities of the built-in logger are not enough,useTransitionCallbacks to implement your own logging andLoggerMode.NONE in theLogParams arguments.

Tools of VisualFSM

Static tools for graph construction and verification

  • VisualFSM.generateDigraph(...): String - generate a FSM DOT graph for visualization in Graphviz (graphviz cli on CI orhttp://www.webgraphviz.com/ in browser).Transition class name used as the edge name, you can use the@Edge("name") annotation on theTransition class to set a custom edge name.For customization entire graph, colors and shapes of nodes or edges you can use theattributes argument to graph rendering customization.

  • VisualFSM.getUnreachableStates(...): List<KClass<out STATE>> - get all unreachable states from initial state

  • VisualFSM.getFinalStates(...): List<KClass<out STATE>> - get all final states

  • VisualFSM.getEdgeListGraph(...): List<Triple<KClass<out STATE>, KClass<out STATE>, String>> - builds an Edge List

  • VisualFSM.getAdjacencyMap(...): Map<KClass<out STATE>, List<KClass<out STATE>>> - builds an Adjacency Map of states

Code generation tools

File with all transitions

To analyze FSM using third-party tools, it is possible to generate a csv file with all transitions.To generate a file, you need to pass thegenerateAllTransitionsCsvFiles parameter with the valuetrue to the ksp parameters.

ksp {    arg("generateAllTransitionsCsvFiles","true")}

In the package that contains theFeature, a file called[Base State Name]AllTransitions.csv will be generated with lines in the manner:

[Name of the transition],[Name of the State from which the transition executes],[Name of the State to which the transition executes]

Example

Samples of usage

A tests sample for FSM of user authorization andregistration:AuthFSMTests.kt

The DOT visualization graph for graphviz is being generated using theVisualFSM.generateDigraph(...) method.

For CI visualization usegraphviz, for the local visualization (on yourPC) useedotor,webgraphviz, or other DOT graph visualization tool.

AuthFeature.kt

// Use Feature with Kotlin Coroutines or FeatureRx with RxJava@GenerateTransitionsFactory// Use this annotation for generation TransitionsFactoryclassAuthFeature(initialState:AuthFSMState) : Feature<AuthFSMState, AuthFSMAction>(    initialState = initialState,    asyncWorker =AuthFSMAsyncWorker(AuthInteractor()),    transitionCallbacks =TransitionCallbacksImpl(),// Tip - use DI    transitionsFactory = provideTransitionsFactory(),// Get an instance of the generated TransitionsFactory// Getting an instance of a generated TransitionsFactory for KMP projects:// Name generated by mask Generated[FeatureName]TransitionsFactory()// transitionsFactory = GeneratedAuthFeatureTransitionsFactory(), // Until the first start of code generation, the class will not be visible in the IDE.)val authFeature=AuthFeature(    initialState=AuthFSMState.Login("",""))// Observe states on FeatureauthFeature.observeState().collect { state-> }// Observe states on FeatureRxauthFeature.observeState().subscribe { state-> }// Proceed ActionauthFeature.proceed(Authenticate("",""))

AuthFSMState.kt

AllStates are listed in a sealed class. For the convenienceStates that call async work isrecommended to group inside innerAsyncWorkState sealed class.

sealedclassAuthFSMState :State {data classLogin(valmail:String,valpassword:String,valerrorMessage:String? =null    ) : AuthFSMState()data classRegistration(valmail:String,valpassword:String,valrepeatedPassword:String,valerrorMessage:String? =null    ) : AuthFSMState()data classConfirmationRequested(valmail:String,valpassword:String    ) : AuthFSMState()sealedclassAsyncWorkState :AuthFSMState() {data classAuthenticating(valmail:String,valpassword:String        ) : AsyncWorkState()data classRegistering(valmail:String,valpassword:String        ) : AsyncWorkState()    }data classUserAuthorized(valmail:String) : AuthFSMState()}

AuthFSMAsyncWorker.kt

AsyncWorker subscribes on state changes, starts async tasks for those inAsyncWorkState group, andcallsAction to process the result after the async work is done.

classAuthFSMAsyncWorker(privatevalauthInteractor:AuthInteractor) : AsyncWorker<AuthFSMState, AuthFSMAction>() {overridefunonNextState(state:AuthFSMState):AsyncWorkerTask<AuthFSMState> {returnwhen (state) {isAsyncWorkState.Authenticating-> {AsyncWorkerTask.ExecuteAndCancelExist(state) {val result= authInteractor.check(state.mail, state.password)                    proceed(HandleAuthResult(result))                }            }isAsyncWorkState.Registering-> {AsyncWorkerTask.ExecuteIfNotExist(state) {val result= authInteractor.register(state.mail, state.password)                    proceed(HandleRegistrationResult(result))                }            }else->AsyncWorkerTask.Cancel()        }    }}

HandleRegistrationResult.kt

HandleRegistrationResult is one ofActions of the sample authorization and registration FSM thatis called fromAsyncWorker after the result of registration is received. It consists oftwoTransitions, the necessaryTransition is chosen afterpredicate function result.

classHandleRegistrationResult(valresult:RegistrationResult) : AuthFSMAction() {innerclassSuccess :Transition<AsyncWorkState.Registering,Login>() {overridefunpredicate(state:AsyncWorkState.Registering)=            result==RegistrationResult.SUCCESSoverridefuntransform(state:AsyncWorkState.Registering):Login {returnLogin(state.mail, state.password)        }    }innerclassBadCredential :Transition<AsyncWorkState.Registering,Registration>() {overridefunpredicate(state:AsyncWorkState.Registering)=            result==RegistrationResult.BAD_CREDENTIALoverridefuntransform(state:AsyncWorkState.Registering):Registration {returnRegistration(state.mail, state.password,"Bad credential")        }    }innerclassConnectionFailed :Transition<AsyncWorkState.Registering,Registration>() {overridefunpredicate(state:AsyncWorkState.Registering)=            result==RegistrationResult.NO_INTERNEToverridefuntransform(state:AsyncWorkState.Registering):Registration {returnRegistration(state.mail, state.password, state.password,"No internet")        }    }}

AuthFSMTests.kt

classAuthFSMTests {    @TestfungenerateDigraph() {println(VisualFSM.generateDigraph(                baseAction=AuthFSMAction::class,                baseState=AuthFSMState::class,                initialState=AuthFSMState.Login::class,            )        )Assertions.assertTrue(true)    }    @TestfunallStatesReachableTest() {val notReachableStates=VisualFSM.getUnreachableStates(            baseAction=AuthFSMAction::class,            baseState=AuthFSMState::class,            initialState=AuthFSMState.Login::class,        )Assertions.assertTrue(            notReachableStates.isEmpty(),"FSM have unreachable states:${notReachableStates.joinToString(",")}"        )    }    @TestfunoneFinalStateTest() {val finalStates=VisualFSM.getFinalStates(            baseAction=AuthFSMAction::class,            baseState=AuthFSMState::class,        )Assertions.assertTrue(            finalStates.size==1&& finalStates.contains(AuthFSMState.UserAuthorized::class),"FSM have not correct final states:${finalStates.joinToString(",")}"        )    }}

AuthFSMStateAllTransitions.csv

Success,AsyncWorkState.Registering,LoginBadCredential,AsyncWorkState.Registering,RegistrationConnectionFailed,AsyncWorkState.Registering,Registration

What is MVI

MVI stands forModel-View-Intent. It is an architectural pattern that utilizesunidirectionaldata flow. The data circulates betweenModel andView only in one direction - fromModeltoView and fromView toModel.

More on hannesdorfmann

What is FSM

Afinite-state machine (FSM) is an abstract machine that can be in exactly one of a finite numberof states at any given time. TheFSM can change from one state to another in response to someinputs.

More on wikipedia

About

VisualFSM - Kotlin Multiplatform library for FSM based MVI with visualization and analysis tools

Topics

Resources

License

Stars

Watchers

Forks

Contributors5


[8]ページ先頭

©2009-2025 Movatter.jp