Skip to content

Customizing Views

With View, you can either customize existing views or create entirely new views.

Creating new views

As an example, we will create a text view that prepends a string to whatever text it displays.

Creating the view

You can create a new view by either subclassing the base class View or any of its subclasses.

When creating views it is preferable to use a set of view property delegates that make the development of views much easier and faster.

Please refer to the view property delegates documentation for more information.

import view.core.views.display.TextView

import view.core.views.propertyDelegates.ViewProperty
import view.utils.validators.Validator
import view.utils.validators.conditions.StringConditions

class PrependedTextView: TextView() {

    var prependedText by ViewProperty("Original Text: ", Validator(StringConditions.PRESENT))
}

Creating the view builder

A View Builder takes a map of key-value pairs and uses it to build the view. The map is parsed from a serialized format such as JSON or is built by the DSL.

View Builders are "map-based" classes. Please see the utils.mapBased package that contains utility classes that help with common operations.

import org.kodein.di.Kodein
import org.kodein.di.erased.bind
import org.kodein.di.erased.provider
import view.core.loaders.builders.ViewBuilder
import view.core.loaders.builders.display.TextViewBuilder
import view.utils.extensions.nonNull
import view.utils.mapBased.keys.delegates.nullable.StringRWKey
import view.di.KodeinContainer


class PrependedTextViewBuilder: TextViewBuilder() {

    override val view = PrependedTextView()

    var prependedText by StringRWKey

    override fun beforeProduction() {
        super.beforeProduction()
        prependedText.nonNull { view.prependedText = it }
    }
}

// add DI bindings to the library's bindings container
val newBindings = Kodein {
    bind<ViewBuilder<*>>("PrependedTextView") with provider { PrependedTextViewBuilder() }
}
KodeinContainer.addConfig(newBindings)

Creating the DSL

This step is optional.

object prependedTextView {

    operator fun invoke(init: PrependedTextViewBuilder.() -> Unit): PrependedTextView {
        return PrependedTextViewBuilder().apply {
            init()
        }.build() as PrependedTextView
    }
}

// can be used as follows
val pTV = prependedTextView {
    prependedText = "This is text will be prepended"
}

Adding a renderer

Please follow each renderer's documentation on how to add renderers for new views.

Creating a custom view property delegate

View property delegates are helpful as they allow us to control how view properties are set and retrieved. This package contains multiple delegates that can be used.

The main functions of view property delegates are: - Validate property values - Notify the renderer whenever the property's value changes

Currently, four property delegate classes are available:

1. AbstractViewProperty: Base class for mutable view property delegates

2. ViewProperty: Property delegate for non-nullable view properties

3. NullableViewProperty: Property delegate for non-nullable view properties

4. LateInitVal: Allows an immutable property to be initialized later in the class' lifetime.

As an example, we will create a property delegate that logs to the console whenever a property is accessed.

import view.core.views.View
import view.core.views.propertyDelegates.ViewProperty
import view.utils.validators.Validator
import kotlin.reflect.KProperty


class LoggerProperty<T: Any>(
        initValue: T,
        validator: Validator<T>? = null
): ViewProperty<T>(initValue, validator) {

    override fun getValue(thisRef: View, property: KProperty<*>): T {
        val value = super.getValue(thisRef, property)
        println("Retrieving value $value from property ${property.name}")
        return value
    }

    override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
        super.setValue(thisRef, property, value)
        println("Set property ${property.name} with value $value")
    }
}

// can be used as follows

import view.utils.validators.conditions.IntegerConditions

val counter by LoggerProperty(0, Validator(IntegerConditions.NON_NEGATIVE))