etiennelenhart / Eiffel

Redux-inspired Android architecture library leveraging Architecture Components and Kotlin Coroutines
MIT License
211 stars 14 forks source link
android android-architecture kotlin kotlin-coroutines kotlin-flow redux state-management viewmodel

Eiffel

Build Status JitPack

Logo

A Redux-inspired Android architecture library leveraging Architecture Components and Kotlin Coroutines.

Quick example

data class HelloEiffelState(val greeting: String = "Hello Eiffel") : State

sealed class HelloEiffelAction : Action {
    object NowInFrench : HelloEiffelAction()
    data class Greet(val name: String) : HelloEiffelAction()
}

val helloEiffelUpdate = update<HelloEiffelState, HelloEiffelAction> { action ->
    when (action) {
        is HelloEiffelAction.NowInFrench -> copy(greeting = greeting.replace("Hello", "Salut"))
        is HelloEiffelAction.Greet -> copy(greeting = "Salut ${action.name}")
    }
}

class HelloEiffelViewModel(initialState: HelloEiffelState) :
    EiffelViewModel<HelloEiffelState, HelloEiffelAction>(initialState) {
    override val update = helloEiffelUpdate
}

class HelloEiffelFragment : Fragment() {
    private val viewModel: HelloEiffelViewModel by eiffelViewModel()
    ...
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        // example with View Bindings
        binding = FragmentHelloEiffelBinding.inflate(inflater, container, false)

        viewModel.state.observe(viewLifecycleOwner) { state ->
            binding.greetingText.text = state.greeting
        }
        binding.frenchButton.setOnClickListener { viewModel.dispatch(HelloEiffelAction.NowInFrench) }

        return binding.root
    }
    ...
}

Installation

build.gradle (project)

repositories {
    maven { url 'https://jitpack.io' }
}

build.gradle (module)

dependencies {
    implementation 'com.github.etiennelenhart.eiffel:eiffel:5.0.0'
    implementation 'com.github.etiennelenhart.eiffel:eiffel-test:5.0.0'
}

Features

Apart from providing Redux-like reactive ViewModels, Eiffel includes the following features to simplify common Android-related tasks and architecture plumbing:

Info on all of these and more can be found in the Wiki.

Interceptions DSL

Eiffel includes an easy-to-use Domain-specific language for creating a chain of Interceptions. This allows you to define the logic of your ViewModel domain in a simple and declarative way. Iterating on the quick example above, this is how you can define a set of interceptions in a few lines of code:

val helloEiffelInterceptions = interceptions<HelloEiffelState, HelloEiffelAction> {
    add(CustomInterception()) // your custom interception
    pipe { _, action -> Analytics.log("HelloEiffel", action) } // log something to analytics
    on<HelloEiffelAction.Greet> { // following will only react to 'Greet' action
        adapter("Upper case name") { _, action ->
            HelloEiffelAction.Greet(action.name.toUpperCase())
        }
        filter { state, action -> // ignore duplicate button presses and empty names
            !state.greeting.contains(action.name) || action.name.isNotBlank()
        }
    }
}

class HelloEiffelViewModel(initialState: HelloEiffelState) :
    EiffelViewModel<HelloEiffelState, HelloEiffelAction>(initialState) {
    override val update = helloEiffelUpdate
    override val interceptions = helloEiffelInterceptions
}

Migration

Migration guides for breaking changes: