marcsieber / ComposeForms

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ReadMe Compose Forms

German ReadMe: See document README_GERMAN.adoc

Compose Forms is a library that allows you to create beautiful & user-friendly forms quickly and easily.

desktop form

The code for this example can be found here:

demo > src > main > kotlin > demo > personForm

An additional user-supporting feature is the possibility to use the smartphone for the input of the individual fields. A boolean can be used to decide whether the smartphone input option should be enabled for the form. The end user is free to decide whether to use the smartphone or not. A button in the form allows the smartphone to be connected to the desktop form via QR code. Individual input masks for the desktop-focused input field are then displayed on the smartphone.

smartphone calc new

Used Technologies: Kotlin, Compose for Desktop, Jetpack Compose

Note: The library is currently under development and is being improved continuously, therefore not all functionalities are available yet.

Installation

You need:

Example: Using IntelliJ

  • Download and install IntelliJ.

  • Download and install Git.

  • Open terminal and clone ComposeForms project

  • Open IntelliJ > New Project > Select ComposeForms

If there is no Android SDK yet, it will be asked for it:

missingAndroidSdk

If no Android SDK is available yet, download Android Studio (or Android SDK) and install/open it. Then specify the path to the installed Android Sdk in the IntelliJ window above.

If it is not clear where the Android SDK was saved:
Start Android Studio and create a new empty project. Then open Project Structure at the top right:

projectStructureAndroidStudio

This is the location of the Android SDK:

storagePlaceAndroidSdk

This can be copied and then entered into the IntelliJ as android sdk location.

  • If the Andoid SDK is available, Gradle will start automatically. The first time this can take a little longer, because all files must be downloaded first.

  • When the Gradle is ready, the demo starter class main.kt can be started. ( demo > src > main > main.kt )

Overview

For the creation of a form the following 3 classes are basically needed:

  • Model, in which attributes and groups are defined.

  • Enum, in which the label names are defined

  • Starter class

Model

To create a form, first a model must be created. This should implement the BaseModel.

class PersonModel : BaseModel(iLabel = PersonLabels.SIZE, smartphoneOption = true) { ... }

( demo > src > main > kotlin > demo > personForm > PersonModel )

The BaseModel has 2 parameters: iLabel and smartPhoneOption.
We will come back to the iLabel parameter later when creating the attribute labels.
Below is an overview of the two parameters and their effects:

Table 1. Attribute parameters
Property Type Description

iLabel *

ILabel

Here should be passed any entry of the label enum that has to be created. More info under the attribute parameter label.
(The specification of the parameter iLabel is necessary so that the form can automatically recognize the languages used).

smartphoneOption

Boolean

Here can be passed true if the smartphone use should be allowed. (The user will then be shown the QR code button to connect to the cell phone).
If nothing is passed, the smartphone option is set to false by default.

Parameters with * are mandatory. Parameters without * are optional.

In the model body, the application title can now be defined in an init block, which is executed 1 time at startup:

init {
        setTitle("Clients")
}

( demo > src > main > kotlin > demo > personForm > PersonModel )

Currently there is no multilingual option available for the form title. However, this will change in the future.

Now all needed attributes can be defined here in the model.

Attributes

Overview

An attribute is a reflection of an object of a certain type with all values and information needed for interaction (e.g. saving etc.).

In this API there exist following attribute types:

attribute types

Orange: Abstract classes
Blue: Concrete implementations. These can be used.

This is how an attribute is created:

Example:
A StringAttribute is created in which the street name should be stored.

val street = StringAttribute(model = this, label = PersonLabels.STREET)

( demo > src > main > kotlin > demo > personForm > PersonModel )

An attribute has various parameters which are used to configurate it. Certain parameters must be specified (marked with * in the following), others are optional and already have a default value set.

Table 2. Attribute parameter
Property Type Description

model *

IModel

Model, which manages all attributes

label *

L

Label text

value

T?

Last valid value

required

Boolean

Is the attribute a required field?

readOnly

Boolean

Is the attribute a read-only field that cannot be modified?

onChangeListeners

List<(T?) → Unit>

Listeners that react to value changes

validators

List<SemanticValidator<T>>,

Validators that determine when the user input is valid, invalid & onRightTrack

convertibles

List<CustomConvertible>

Convertibles that allow the user an easier input

meaning

SemanticMeaning<T>

Give the value a semantic meaning (for example, that it is a currency).

Certain attributes have additional parameters:

Table 3. More attribute parameters
Attribute(s) Property Type Description

FloatingPointAttribute

decimalPlaces

Int

Defines the number of allowed decimal places.

SelectionAttribute

possibleSelections *

Set<String>

Sets the set of possible selections.

Groups

After all attributes have been created, they can now be assigned to groups. Only attributes that are assigned to at least one group are displayed in the form.

It is important that all attributes assigned to the group have the same model as the group.

Example: Creating a group in the model:

val group2 = Group(model = this, title = "Adress",
        Field(postCode),
        Field(place),
        Field(street),
        Field(houseNumber)
    )

( demo > src > main > kotlin > demo > personForm > PersonModel )

A group has the following parameters:

Table 4. Group parameters
Property Type Description

model *

IModel

Model, which manages all attributes and groups.

title *

String

Group title

field

vararg

All fields that should be displayed.

The attributes can be placed here deliberately in the desired order inside the groups. The size of the fields can also be determined (SMALL or NORMAL).

Create a starter class

The model is now ready. Only the starter class is missing, in which the UI is connected to the model to start the application.

A new Kotlin class is created, in the following example Main.kt.

@ExperimentalFoundationApi
fun main() = Window() {
        val model = remember { PersonModel() }
        Form().of(model)
}

( demo > src > main > main.kt )

The annotation @ExperimentalFoundationApi and the remember are important when initializing the model.

For the window, the following parameters are useful to display the window optimally:

Table 5. Helpful window parameters (by Compose for Desktop)
property type description

title

String

Define the title of the window

size

IntSize

Define the size of the window at startup.

Start application

Start desktop application

Start the starter class via the development environment.

startDesktopApp

Install smartphone app on a device

Before the smartphone app can connect to the desktop via QR code, the app must be installed on the Android device (or emulator). To do this, simply launch the ComposeForms.app in the development environment.

smartphoneApp

Start smartphone app via QR code.

If the smartphone is to connect via QR code (via the desktop application), the app must already be installed on the smartphone and the smartphone must be in the same WLAN as the desktop application.

Note: To be able to use the smartphone, the parameter smartphoneOption must be set to true in the Model.

Start the smartphone app via development environment

In order to be able to properly connect the smartphone app to the desktop application via the development environment, the IP address of the desktop device must be set in the variable mqttBroker in the model ( app > src > main > kotlin > ch > model > Model ).

var mqttBroker    = "192.168.0.94" //Beispiel

Find out IP address:
Open terminal and enter ifconfig.
In the output under en0: you can find among other things the IP address (behind the word inet).

After that the smartphone app can be started in the development environment:

smartphoneApp

Note: It is important that the desktop appliaction is started first and afterwards the smartphone, otherwise no connection can be established between the two applications.

Modify UI (optional)

Change colors

The form colors can easily be modified in the file FormColors.kt by adjusting the color values in the enums.

The file is located here:

common > src > main > kotlin > ui > theme > FormColors.kt

User Experience

Available actions

header functions
Table 6. User actions
functionality called model function effect
language

setCurrentLanguageForAll( lang : String)

Sets the current language. The languages must be defined beforehand in the label enum. (see label) +. This functionality is not fully implemented yet.

reset

resetAll()

Resets the values of all attributes to the last saved values.

save

saveAll()

Saves the values (user inputs) of all attributes, if they are all in a valid state.

qrCode
qr code

Generates a QR code so that the user can connect to the smartphone.

3 states

For a better user experience, attributes can be in 3 different states.
With the rightTrack state the times are over where the user gets upset about red error messages that are timed from the beginning before the user even had the chance to try to get a proper input. The rightTrack status indicates whether the user is on the right track to a valid input. Only if the input is really going in the wrong direction the input is shown as invalid.

Table 7. Overview of the 3 states
Appearance Status Description
valid

valid

The input is in a valid state and can therefore be saved. Valid means the input (String):
- can be converted to the attribute type
- is in a valid state regarding the parameter required
- is in a valid state regarding all set validators

invalid

invalid

The input is in an invalid state and cannot be saved. If the input (String) is not valid it is in an invalid state.

rigthtTrack

rightTrack

(on the right way to a valid state)

The rightTrack state is in addition to the two above. (So if the rightTrack state is set, the input is in a rightTrack state and an invalid or valid state at the same time. If it is not set, the input is invalid).

The input is displayed to the user as rightTrack state if the input is in a state that is on the right track to a valid state. Once the state is valid the attribute is displayed as valid.
The rightTrack state is only displayed if the attribute has not yet been focused or is currently focused, after which it changes back to an invalid state.

Example

The above input fields belong to the following example:

example stati

The input must contain between 3 and 10 characters to be valid.

Table 8. Behavior of the input fields

Number of Characters
(input length)

Appearance of focused field

Appearance of unfocused field
(after it has already been focused)

0 - 2

rigthtTrack

rightTrack

rightTrack unfocused

invalid

3 - 10

valid

valid

valid unfocused

valid

10 - …​

invalid

invalid

invalid unfocused

invalid

Deeper information / assistance

Startup complications

What to do if the application does not start?

Class not Found → Missing VM options

Check if the variable -cp $Classpath$ is set in the configurations for the starter class under VM options. If not, this should still be set.

edit configurations
configuration window

Language Not Found

If this exception appears at startup the parameter iLabel (see in section Model) was not specified.

languageNotFound

Attribute parameters

The following is a detailed explanation of the correct creation of the attribute parameters

model (mandatory)

The model of the group in which the attribute is located must be the same as the model of the attribute. (If this is not the case, an exception will be thrown).

Example 1 - All in one model
The attributes and groups are created in the same model. For both the attribute and the group, this is passed as model.

( see: demo > src > main > kotlin > demo > personForm > PersonModel )

Example 2 - Attributes and groups distributed in several classes +. In addition to the attributes from example 1, another attribute should appear on the form. In this example, this attribute is defined directly in the starter class and passed to its own group. In order for it to appear on the same form, the same model must be passed as parameter.

example parameter model

Now the additional group with the attribute appears in the form among those already defined in the PersonModel:

example result parameter model

label (mandatory)

To ensure multilingualism, the label texts must be specified in an enum. The enum must implement the interface ILabel and define all desired languages as parameters with type String.
In the enum you can now specify code variables in which the translations of the label texts can be defined.

label code

( demo > src > main > kotlin > demo > personForm > PersonLabels )

At the attributes you only have to specify the correct code variable as label.

label in model

( demo > src > main > kotlin > demo > personForm > PersonModel )

For the languages to be recognized automatically by the form, the iLabel parameter must be given to the model. Any label from the created label enum class can be specified here.

model params

value (optional).

If an attribute should not be empty the first time the form is opened, the value parameter can be used to set the value.

value example

( demo > src > main > kotlin > demo > personForm > PersonModel )

The value must be of the same type as the attribute!
If not specified, the default value null is set.

required (optional)

If an attribute should be a required field (the user should be forced to make an entry and not leave the field empty), true can be passed in for the required parameter.

required example

( demo > src > main > kotlin > demo > personForm > PersonModel )

If not specified, the default value false is set.

readOnly (optional)

If an attribute should only be readable but not modifiable for the user, true can be passed in the readonly parameter.

value example

( demo > src > main > kotlin > demo > personForm > PersonModel )

If not specified, the default value false is set.

onChangeListeners (optional).

OnChangeListeners can be used to change the attribute on which the ChangeListeners are passed as parameters when the values of other attributes are changed. A list with onChangeListeners must be passed since there can be several onChangeListeners for one attribute.

Define an onChangeListener:

attribute whose value should be observed addOnChangeListener {
lambda parameter for this attribute , lambda parameter for value to be observed What when should happen }

Example:
As soon as something is entered for the occupation attribute (value != null), the tax number should become a mandatory field.

onChangeListener

( demo > src > main > kotlin > demo > personForm > PersonModel )

If no value is entered, the default value emptyList() is set.

validators (optional)

You can pass so-called semantic validators to the attribute, with which you can restrict the validity of the attribute value.

validators

( demo > src > main > kotlin > demo > personForm > PersonModel )

Since you can specify multiple validators, they must always be passed in a list ( listOf(..) ).

Chnage validator at runtime

If you want to change a validator at runtime, you have to define it as a variable outside the attribute and then pass it to the attribute:

validatorOutsideAttribute

( demo > src > main > kotlin > demo > personForm > PersonModel )

With override…​Validator() (for …​ insert the type used) the validator can be changed at runtime.

Example:
If the person is >= 1m, then he must be at least 6 years old. If not, they can be younger. (The size attribute was defined before).

overrideValidator

( demo > src > main > kotlin > demo > personForm > PersonModel )

Overview of validators

For certain attribute types, certain validators with different parameters are available. The following is an overview of all semantic validators and their parameters:

Number-Validator (for all number attributes)
Table 9. Number-Validator-Properties
Property Type Description

lowerBound

T?

Lower limit for input

upperBound

T?

Upper limit for input

stepSize

T?

Step size

stepStart

T?

Number from which the steps are calculated up/down

onlyStepValuesAreValid

Boolean

If true all numbers between the steps are invalid.
(Default value: false)

validationMessage

String

Message to be displayed to the user if the input is invalid due to this validator.

FloatingPoint validator (for all FloatingPoint attributes)
Table 10. FloatingPoint-Validator-Properties
Property Type Description

decimalPlaces

Int

Number of allowed decimal places
(Default value: 10)

validationMessage

String

Message to be displayed to the user if the input is invalid due to this validator.

Selection validator (for all selection attributes)
Table 11. Selection-Validator-Properties
Property Type Description

minNumberOfSelections

Int

Minimum number of selections that must be selected by the user.
(Default value: 0)

maxNumberOfSelections

Int

Maximum number of selections that must be selected by the user.
(Default value: Int.MAX_VALUE)

validationMessage

String

Message to be displayed to the user if the input is invalid due to this validator.

String validator (for all string attributes)
Table 12. String-Validator-Properties
Property Type Description

minLength

Int

Minimum length of the input word. (number of characters)
(Default value: 0)

maxLength

Int

Maximum length of the input word. (number of characters)
(Default value: 1_000_000)

validationMessage

String

Message to be displayed to the user if the input is invalid due to this validator.

Regex validator (for all attributes)
Table 13. Regex-Validator-Properties
Property Type Description

regexPattern

String

Regex that specifies when the input word is valid, as String.

rightTrackRegexPattern

String

Regex specifying when the input word is onRightTrack, as String.

validationMessage

String

Message to be displayed to the user if the input is invalid due to this validator.

Example:

regexValifdator

( demo > src > main > kotlin > demo > personForm > PersonModel )

Custom validator (for all attributes)
Table 14. Custom-Validator-Properties
Property Type Description

validationFunction

(T?) → Boolean

Regex that specifies when the input word is valid, as String.

rightTrackFunction

((T?) → Boolean) ?

Regex specifying when the input word is onRightTrack, as String.

validationMessage

String

Message to be displayed to the user if the input is invalid due to this validator.

Example:

customValidator

convertibles (optional)

With a convertible you can give the user an alternative notation to the value that is actually saved. The alternative notation is then converted to the correct value. (For example, you can give the user the opportunaty to use commas instead of periods, even though type double is required). Here is an example call:

sizeConvertible

Since you can have multiple convertibles in one attribute, you must always specify a list of convertibles. There is only one form of convertible, namely the CustomConvertible. This has 3 parameters:

Custom-Convertible (for all attributes)
Table 15. custom convertible parameters
Parameter Type Description

replaceRegex *

List<ReplacementPair>

A ReplacementPair contains a convertibleRegex string (the additional valid input form that the user can use) and a convertIntoRegex string (into what the former should be converted to).
Caution: The convertIntoRegex string must be convertible to the type of the attribute!

convertUserView

Boolean

With this parameter you can specify whether the user view should be updated to the converted value (so that the user sees the value being saved) or not.
(Default value: true)

convertImmediately

Boolean

With this parameter you can define when to convert. If convertImmediately = true, then it will be converted as soon as the input is convertible. If convertImmediately = false, then the input will be converted when the user leaves the field (fielf not longer focused).
(Default value: false)

(Parameters with * are mandatory, the others are optional)

meaning (optional)

The meaning parameter can be used to give a meaning to the attribute value. For example, that the value is given in meters:

meaningMeter
sizeInMeter

There are several meanings that you can create:

Table 16. Meanings
Meaning Beschreibung Beispiel-Code

Currency( currency )

(only for number attributes)

Takes a currency iso-code and converts it to the appropriate currency sign, which then appears on the form.

codeEur

Percentage( )

(only for number attributes)

A % sign appears on the form.

percentageCode

CustomMeaning( customText )

Takes a string, which then appears exactly like it is on the form.

kgCode

decimalPlaces (optional)

Only for floating point attributes.

For attributes that save decimals as values, you can also specify the maximum number of decimal places that are desired. As soon as the user enters more decimal places, the input gets invalid.

decimalPlaces

( demo > src > main > kotlin > demo > personForm > PersonModel )

If no value is specified, the default value 8 is set.

possibleSelections (mandatory)

Only for selection attributes.

For the selection attribute, the selection options available to the user must be specified via the possibleSelections parameter. The selections must be passed as Strings in a set.

possibleSelections

At the moment there is no multilingual option available here. This will change in the future.

Group parameters

The following is a detailed explanation of the correct creation of the Group parameters.

model (mandatory).

The model of the group must be the same as the model of the attributes that are in the group (in the fields parameter). If this is not the case an exception will be thrown.
Examples: see the model parameter for the attribute.

groupModel

( demo > src > main > kotlin > demo > personForm > PersonModel )

title (mandatory).

A group title must be specified as String.

groupModel

( demo > src > main > kotlin > demo > personForm > PersonModel )

At the moment there is no multilingual option available here. This will change in the future.

fields (optional)

The fields parameter can be used to add attributes as fields.

Take care that the correct classes are imported for Group and Field :

importsGroup

The fields can be specified simply separated by commas:

defineGroupFields

A Field has the parameters attribute (mandatory), where an attribute can be passed and fieldSize (optional), which can be used to determine the size of the input field.

If no fieldSize is passed to the field, the default value FieldSize.NORMAL is set.

The fields appear in the same order as they are passed to the group.
FieldSize.SMALL is half the size of FieldSize.NORMAL:

groupWithFieldSizes

If no fields are passed to the group, only the group title is displayed in the form.

Configuration at runtime

These functions are useful when using onChange listeners for example.

Attribute functions

The following functions can be called on attributes:

Table 17. Some helpful setter functions of an attribute
Function Description

setValAsText(valueAsText : String)

Set the user input

setRequired(isRequired : Boolean)

See attribute parameter required

setReadOnly(isReadOnly : Boolean)

See attribut parameter readOnly

Table 18. Some helpful getter functions of an attribute
Funktion Rückgabe-Typ Beschreibung

getId()

Int

Returns the Id of the attribute

getValue()

T?

Returns the last valid value.

getSavedValue()

T?

Returns the stored value.

getRightTrackValue()

T?

Returns the last right-track-valid value.

getValueAsText()

String

Returns the current user input string.

getPossibleSelections()

Set<String>

For the selection attribute, the set with the set choices is returned. For other attribute types, the set’s choices consist of previous user inputs (not yet implemented).

getLabel()

String

Returns the current label text.

isRequired()

Boolean

Returns whether an input is required or not.

isReadOnly()

Boolean

Returns whether the value may be changed or not.

isChanged()

Boolean

Returns whether value is the same as savedValue or not.

isValid()

Boolean

Returns whether the user input (valueAsText) is valid or not.

isRightTrackValid()

Boolean

Returns whether the user input is on the right track to a valid input or not.

getErrorMessages()

List<String>

Returns all ErrorMessages that apply to the current input (valueAsText).

Model-Funktionen

The following functions can be called on a model:

Table 19. Some helpful functions of the base model (these functions influence all attributes)
Funktion Beschreibung

setValAsText(valueAsText : String)

Set the user input.

setRequired(isRequired : Boolean)

See attribute parameter required.

setReadOnly(isReadOnly : Boolean)

See attribute parameter readOnly.

Validator functions

The following functions can be called on a validator:

Table 20. Function to be able to modify a validator at runtime
Function Description

override…​Validator()

(for …​ insert the correct type)

This function can be used to overwrite the respective validator. The same parameters are available as for the initialization of the respective validator. See validators.

About


Languages

Language:Kotlin 100.0%