Build reactive web-apps in pure Kotlin

fritz2 is a lightweight, typesafe, data-driven library for building reactive web-apps in pure Kotlin, heavily depending on coroutines and flows.

FEATURES

Why you should use fritz2

Pure Kotlin

fritz2 makes heavy use of Kotlin standard features (especially flows) and has no external dependencies. Based upon Kotlin's outstanding capabilities to build DSLs, fritz2 offers a nice declarative and typesafe syntax to structure your UI code in a comfortable and easy to read way.

Precise Data-Binding

fritz2 offers precise data binding for your ui-elements. This means that when parts of your data model change, exactly those and only those DOM-nodes depending on the changed parts will automatically change as well - no virtual dom needed.

Easy to Learn

fritz2 is built around just a few basic concepts (Stores, Handlers, Tags, etc.). You can easily learn how to use them from our documentation or the examples we provide. Enjoy!

App-Scaling

fritz2 allows you to quickly build small apps with a few lines of code, as well as enterprise scale applications with a focus on reusable components, clean code, and structure. The functional reactive concept makes the resulting code easy to read and to maintain.

Everything you need

fritz2 includes everything you need to start your project: state management even for complex nested models, validation, routing, REST, WebSockets, and WebComponents as well as a set of headless components.

Reusability

fritz2 uses Kotlin's multiplatform-abilities, so you'll write your data classes and validation code just once and use it for both client and server. Use your favorite tool-chain (Gradle, IDEA, etc.) to jump right in and build your apps.

SHOW ME CODE

How your web-app looks in fritz2

Getting Started

Let's start with creating our first elements, or tags as we call them, using the DSL. First we need to call the render function in order to create a RenderContext for our tags. Every tag is just a function representing an HTML-element, <div> in this case. You can nest them just like in HTML. To style your tags, add CSS-class-names directly as first parameter.

fun main() {
render {
div("my-style-class") {
h2 {
+"Hello Peter!"
}
}
}
}
Result
Structuring your UI

To divide your code into reusable fragments, simply write functions with a RenderContext receiver - they can be called from inside any render-context. In this example we create a greet function and call it multiple times with different names.

fun RenderContext.greet(name: String) {
h2 {
+"Hello $name!"
}
}

render {
div("my-style-class") {
greet("Peter")
greet("Paul")
greet("Marry")
}
}
Result
Getting Reactive

Let's get a little more reactive by using a Store to store our data. Stores are needed for two-way data binding in fritz2. Access the store's data as a Flow by calling store.data, then render it into the value attribute of the <input> tag (this is one-way data binding). To update the data inside the store, call the update handler by using the handledBy function (two-way data binding).

render {
val store = storeOf("Hello Peter")

div("...") {
input("...") {
type("text")
value(store.data)
changes.values() handledBy store.update
}
p("...") {
store.data.renderText(into = this)
}
}
}
Result
Validation

As you have seen, fritz2 considers data-handling a first class citizen of any reactive web-app. Think of the UI as a rule driven, derived representation of the application data. That's why our framework also natively supports validation as part of the data-handling.

In order to evaluate data correctness, create a container for the messages by implementing ValidationMessage. You also have to provide a Validation instance by using the factory function validation. The latter contains all the domain rules to check the validity state. There is a special storeOf factory function which directly accepts a Validation-object. It will be automatically evaluated on each update. Last but not least, use the provided store.messages flow to display errors and warnings for the user.

class Message(
override val path: String,
val text: String
): ValidationMessage {
override val isError: Boolean = true
}

val mailRegex = Regex("""\S+@\S+\.\S+""")
val validation = validation<String, Message> {
if(!mailRegex.matches(it.data))
add(Message(it.path,"Not a valid mail address"))
}

render {
val store = storeOf("", validation)

div("...") {
input("...") {
type("text")
placeholder("Enter e-mail address")
value(store.data)
changes.values() handledBy store.update
}
store.messages.renderEach {
p("...") {
+it.text
}
}
}
}
Result
paradigm shift for components

Latest Blog Article

What we have learned about reuse and components in web front-end development.

This article is about how we built and delivered components with fritz2 so far, which problems we noticed more and more and why a new approach is the solution for us. At the same time we say goodbye to the components delivered with fritz2 up to verson 0.14 and introduce the new and imho better replacement for them.