A Redux-inspired Android architecture library leveraging Architecture Components and Kotlin Coroutines.
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
}
...
}
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'
}
Apart from providing Redux-like reactive ViewModels, Eiffel includes the following features to simplify common Android-related tasks and architecture plumbing:
Interceptions
with an easy-to-use DSLBindableState
class to adapt one or more states for use with Data BindingIntent
extras and Fragment
arguments to a ViewModel's initial stateViewEvent
for one-off events inside of statesResource
wrapper to associate a status to LiveData
Activity
or Fragment
Interceptions
in isolationInfo on all of these and more can be found in the Wiki.
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 guides for breaking changes: