HAL

General

Category
Free
Tag
FSM
License
MIT License
Min SDK
16 (Android 4.1 Jelly Bean)
Registered
Jul 1, 2019
Favorites
1
Link
https://github.com/adrielcafe/HAL
See also
Ken-Ken-Pa
TinyMachine
Engine
Kaskade
EasyFlow

Additional

Language
Kotlin
Version
0.0.2 (Jun 30, 2019)
Created
Jun 21, 2019
Updated
Jul 1, 2019
Owner
Adriel Café (adrielcafe)
Contributor
Adriel Café (adrielcafe)
1
Activity
Badge
Generate
Download
Source code
APK file

Advertisement

HAL is a non-deterministic finite-state machine for Android & JVM built with Coroutines and LiveData.

Why non-deterministic?

Because in a non-deterministic finite-state machine, an action can lead to one, more than one, or no transition for a given state. That way we have more flexibility to handle any kind of scenario.

Use cases:

  • InsertCoin transition to Unlocked
  • LoadPosts transition to Loading then transition to Success or Error
  • LogMessage don't transition

Why HAL?

It's a tribute to HAL 9000 (Heuristically programmed ALgorithmic computer), the sentient computer that controls the systems of the Discovery One spacecraft.

"I'm sorry, Dave. I'm afraid I can't do that." (HAL 9000)

This project started as a library module in one of my personal projects, but I decided to open source it and add more features for general use. Hope you like!


Next, implement the HAL.StateMachine<YourAction, YourState> interface in your ViewModel, Presenter, Controller or similar.

The HAL class receives the following parameters:

  • A CoroutineScope (tip: use the built in viewModelScope)
  • An initial state
  • An optional capacity to specify the Channel buffer size (default is Channel.UNLIMITED)
  • An optional CoroutineDispatcher to run the reducer function (default is Dispatcher.DEFAULT)
  • A reducer function, suspend (action: A, transitionTo: (S) -> Unit) -> Unit, where:
    • suspend: the reducer runs inside a CoroutineScope, so you can run IO and other complex tasks without worrying about block the Main Thread
    • action: A: the action emitted to the state machine
    • transitionTo: (S) -> Unit: the function responsible for changing the state

You should handle all actions inside the reducer function. Call transitionTo() whenever you need to change the state (it can be called multiple times).

class MyViewModel(private val postRepository: PostRepository) : ViewModel(), HAL.StateMachine<MyAction, MyState> {

    override val hal by HAL(viewModelScope, MyState.Init) { action, transitionTo ->
        when (action) {
            is MyAction.LoadPosts -> {
                transitionTo(MyState.Loading)
                
                try {
                    // You can run suspend functions without blocking the Main Thread
                    val posts = postRepository.getPosts()
                    // And emit multiple states per action
                    transitionTo(MyState.PostsLoaded(posts))
                } catch(e: Exception) {
                    transitionTo(MyState.Error("Ops, something went wrong."))
                }
            }
            
            is MyAction.AddPost -> {
                /* Handle action */
            }
        }
    }
}

Finally, choose a class to emit actions to your state machine and observe state changes, it can be an Activity, Fragment or any other class.

If you want to use a LiveData-based state observer (highly recommended if you're on Android), just pass your LifecycleOwner to observeState(), otherwise HAL will use a default Callback-based state observer (which is best suited for JVM-only applications).

class MyActivity : AppCompatActivity() {

    private val viewModel by viewModels<MyViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
    
        // Easily emit actions to your State Machine
        loadPostsBt.setOnClickListener {
            viewModel + MyAction.LoadPosts
        }
        
        // Observe and handle state changes backed by a LiveData
        viewModel.observeState(lifecycleOwner = this) { state ->
            when (state) {
                is MyState.Init -> showWelcomeMessage()
                
                is MyState.Loading -> showLoading()
                
                is MyState.PostsLoaded -> showPosts(state.posts)
                
                is MyState.Error -> showError(state.message)
            }
        }
    }
}

Custom StateObserver

You can easily create your custom state observer by implementing the StateObserver<State> interface:

class MyCustomStateObserver<S : HAL.State>(
    private val myAwesomeParam: MyAwesomeClass,
    override val observer: (S) -> Unit
) : StateObserver<S> {

    override fun transitionTo(newState: S) {
        // Do any kind of operation here and call `observer(newState)` in the end
        // IMPORTANT: this method runs on the Main Thread!
    }
}

And to use, just create an instance of it and pass to observeState() function:

viewModel.observeState(MyCustomStateObserver(myAwesomeParam) { state ->
    // Handle state
})

Import to your project

  1. Add the JitPack repository in your root build.gradle at the end of repositories:
allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}
  1. Next, add the desired dependencies to your module:
dependencies {
    // Core with callback state observer
    implementation "com.github.adrielcafe.hal:hal-core:$currentVersion"

    // LiveData state observer only
    implementation "com.github.adrielcafe.hal:hal-livedata:$currentVersion"
}

Current version:

Platform compatibility

hal-core hal-livedata
Android
JVM