TEA Bag is simple implementation of TEA written in Kotlin. Tea-async is part of this project


Keywords
android, android-architecture, android-library, elm-architecture, kotlin, tea
License
MIT

Documentation

Maven Central Jetbrains Market Place

TEA Bag

Tea Bag Logo

Tea Bag is a simple implementation of TEA architecture written in Kotlin. This library is based on Kotlin's coroutines, extensively uses extension-based approach and supports many targets including jvm, js, wasmJs, ios, macos, tvos, watchos, linux and mingw.

This library is designed to be a lightweight, simple, and multiplatform-first implementation of the TEA architecture. It provides a minimal set of abstractions to build TEA app

Quick Sample

Nothing special: we just need to code our initializer, resolver (tracker function), updater (computeNewState function), and UI (renderSnapshot function). After that, we should pass them to an appropriate Component builder overload.

@file:OptIn(ExperimentalTeaApi::class)

package io.github.xlopec.counter

import io.github.xlopec.tea.core.*
import kotlinx.coroutines.runBlocking

/**Async initializer, provides initial state*/
suspend fun initializer(): Initial<Int, Int> = Initial(0)

/**Some tracker*/
fun track(
  event: Snapshot<Int, Int, Int>,
  ctx: ResolveCtx<Int>,
) {
  ctx sideEffect { println("Track: \"$event\"") }
}

/**App logic, for now it just adds delta to count and returns this as result*/
fun add(
  delta: Int,
  counter: Int,
): Update<Int, Int> = (counter + delta) command delta

/**Some UI, e.g. console*/
suspend fun display(
  snapshot: Snapshot<*, *, *>,
) {
  println("Display: $snapshot")
}

fun main() = runBlocking {
  // Somewhere at the application level
  val component = Component(
    initializer = ::initializer,
    resolver = ::track,
    updater = ::add,
    scope = this,
  )
  // UI = component([message1, message2, ..., message N])
  component(+1, +2, -3).collect(::display)
}

The sample above will print the following:

Display: Initial(currentState=0, commands=[])
Track: "Regular(currentState=1, commands=[1], previousState=0, message=1)"
Track: "Initial(currentState=0, commands=[])"
Track: "Regular(currentState=3, commands=[2], previousState=1, message=2)"
Track: "Regular(currentState=0, commands=[-3], previousState=3, message=-3)"
Display: Regular(currentState=1, commands=[1], previousState=0, message=1)
Display: Regular(currentState=3, commands=[2], previousState=1, message=2)
Display: Regular(currentState=0, commands=[-3], previousState=3, message=-3)

Real-world examples include Android and iOS app samples that use the same application component and share common entities, application, and navigation logic. IntelliJ plugin is built on top of the library as well.

For more info, visit the Wiki page.

Main Features

  • Multiplatform this library supports jvm, js, wasmJs, ios, macos, tvos, watchos, linux and mingw targets
  • Scalability it is build on the top of a simple idea of having pure functions that operate on plain data separated from impure one. Those functions are building blocks and form testable components that can be combined to build complex applications
  • Simplicity component implementation resides in a single file
  • Extensibility additional functionality and API is implemented as component extensions which means you can easily add your own
  • Debugger Intellij debugger plugin is available for this library, though it's not production ready yet

Gradle

Add the dependency:

implementation("io.github.xlopec:tea-core:[version]")
implementation("io.github.xlopec:tea-compose:[version]")
implementation("io.github.xlopec:tea-async:[version]")
implementation("io.github.xlopec:tea-async-compose:[version]")
implementation("io.github.xlopec:tea-navigation:[version]")

Make sure that you have mavenCentral() in the list of repositories.

Plugin

Demo

Plugin is available on JetBrains marketplace

Main Modules

  • tea-core - contains core types along with basic component implementation
  • tea-compose - contains Compose Multiplatform integration
  • tea-async - generic state containers (Loadable, Paginatable) for modelling the lifecycle of asynchronous operations (network fetches, form submissions, paginated lists, …)
  • tea-async-compose - Compose helpers for building UI from Loadable and Paginatable values. Exposes slot-based dispatchers (Loadable, Paginatable) and their LazyListScope variants (loadableItems, paginatableItems) that route each state of the container to its own composable slot
  • tea-navigation - contains navigation extensions as well as predictive back navigation for iOS target
  • tea-time-travel - contains debuggable version of the component
  • tea-time-travel-adapter-gson - implements debug protocol and serialization by means of Gson library. Should be added as dependency together with tea-time-travel module
  • tea-time-travel-protocol - contains debug protocol types definitions

How to build and run

To build the plugin from sources, use the ./gradlew tea-time-travel-plugin:buildPlugin command. The installable plugin will be located in the tea-time-travel-plugin/build/distributions directory.

To run IntelliJ IDEA with the installed plugin, use the ./gradlew tea-time-travel-plugin:runIde command.

To build the Android app sample, run ./gradlew :samples:app:assembleDefaultDebug or ./gradlew :samples:app:assembleRemoteDebug. The last command assembles a debuggable version of the application that connects to the currently running instance of the debugger (will try connecting to http://localhost:8080).

Planned features and TODOs

  • Re-implement client-server communication protocol from scratch.
  • Migrate from Gson to kotlinx.serialization.json.
  • Release v1.0.0.
  • Add GitHub Wiki.
  • Rework component builders and possibly replace them with some kind of DSL.
  • Add keyboard shortcuts for the plugin; consider improving plugin UX.

Contribution

Contributions are more than welcome. If something cannot be done, is not convenient, or does not work - create an issue or PR.