Skip to content
Etienne Lenhart edited this page Jun 9, 2020 · 3 revisions

Eiffel Architecture

Eiffel aims to provide a modern approach to Redux-like architecture by leveraging existing tools like Android Architecture Components, Kotlin Coroutines and Domain Specific Languages. It is a Kotlin-only library which allows for a greatly simplified API taking full advantage of Lambdas with Receivers and Extension Functions.

This Wiki is definitely a work-in-progress and will hopefully grow with time. Contributions would be greatly appreciated.

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()
}

fun 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
    }
    ...
}

_This is all you need to get up and running with the basic building blocks. Define a State the view can observe, some Actions representing user input and create an Update that modifies the State depending on these Actions.

In its simplest form, the ViewModel only needs this update function to work. In your Activity or Fragment simply obtain an instance of a ViewModel to observe its State or dispatch Actions._

Concepts (Overview)

State

State should be a Data class with immutable properties that provide the necessary information for rendering your view. Try avoiding specifics like resource IDs and stick to a more high level description of what the view should look like.

Action

Action should be implemented using a Sealed class that wraps all possible actions, that might occur in the scope of the corresponding part of your app. These can be user input or events and results from your business logic or other dependencies. They may update the current State and trigger side effects like network requests or database calls.

Update

Update refers to an invokable class that performs an update to the current State according to a provided Action. This allows you to define exactly how your state is able to be updated and what information is required for these updates.

ViewModel

EiffelViewModel is based on the existing ViewModel from Android's Architecture Components. It acts as a hub providing an observable State, the dispatching of Actions and ties these together with the given Update. State is also retained across configuration changes and part or all of it may be restored after process death. For advanced use cases it optionally takes a chain of Interceptions.

Interceptions

Interceptions allow you to interact with an incoming Action before it reaches the corresponding Update. When an Action is dispatched, the first item in the ViewModel's Interception chain is invoked. The Interception can simply ignore the Action and forward it, do some unrelated work like logging, pass on a modified or completely different Action, block it from ever reaching the Update or simply consume it and return a resulting Action. For a lot of these use cases, Eiffel already provides built-in Interceptions but you can also simply implement your own.

Commands

Command is a special type of Interception that allows you to run asynchronous side effects without blocking state updates. It leverages the full power of Kotlin Coroutines and provides a CoroutineScope bound to the ViewModel's lifecycle to support easy cancellation of asynchronous work. There also is a LiveCommand variant with first-class support for Kotlin Flow.

Clone this wiki locally