7. UI Data Binding
Learn how data binding works in MobileUI.
Layout and logic of a MobileUI app are separate concerns. UI data binding is the "magic glue" that ties together your UI and logic. To realize this, you write short expressions in your layout file to trigger actions (Action Bindings) or realize one-way or two-way property synchronization (Property Bindings). These expressions are formulated in MVEL 2.0, which is a mighty Java-like expression language with high performance.
Action Bindings
If you have worked with JavaScript and HTML or maybe Java Server Faces before, you are familiar with action bindings: They allow defining methods that are called when the user performs certain actions on a UI element. The following binding shows an OnClickBinding.
<Button text="Do Magic" onClick="#{performMagic()}" />
In this example, the method performMagic()
is called, when the user clicks the button. In MobileUI Layouts, expressions are surrounded by the hashed mustache #{...}
.
Binding expressions are always evaluated against the MobileUILayout
's bound object. In this case, this object needs a corresponding function as given in the following listing:
public void performMagic() {
System.out.println("Abracadabra");
}
Action bindings are small MVEL scripts. Therefore, you are allowed to code everything that can be evaluated against your current context. MVEL uses coercion to find the best matching method. For you this means, that you can pass parameters without much hassle as shown in the following listing:
<Button text="Do Magic 1" onClick="#{performMagic('Abracadabra')}" />
<Button text="Do Magic 2" onClick="#{performMagic('Hocus Pocus')}" />
<Button text="Do Magic 3" onClick="#{performMagic('Sim Sala Bim')}" />
Then you extend the existing method with a parameter and you have greatly avoided repeating yourself:
public void performMagic(String spell) {
System.out.println(spell);
}
Property Bindings
Property Bindings allow you to dynamically bind a value at runtime to an attribute of your layout. In the following example, the lastSpell
String property of the given context is bound to a TextView
's text
attribute.
<TextView text="#{lastSpell}" />
public class MainController {
public String lastSpell = "";}
This kind of binding is called output binding, as it is a one-way connection from the business logic to the UI. The text will be displayed, but as of the nature of the TextView, is not editable. You can control many attributes via output bindings. Typical examples are text and colors.
It is possible to use the same approach with a different UI element to realize an input/output binding enabling text input. By applying an EditTextView
to the same property, it is now possible to change the String by entering a new text in the displayed input field.
<EditTextView text="#{nextSpell}" />
public class MainController {
public String nextSpell = ""; //Optional public void setNextSpell(String newSpell) { nextSpell = newSpell; //Process input here, if needed }}
There are many different UI elements that support input/output bindings. Examples: EditTextViews, Checkboxes, RadioButtons and more.
Hint: Sometimes, you need to process or react on user input that is bound to a property. In the above example, a setter is used to further process the input. If a setter is present, it will always be preferred over the direct field access by the MVEL engine.
Firing property changes
All output bindings are executed by the framework after a MobileUILayout is drawn for the first time.
If you change a value during a MobileUILayout's active state, you need to tell the framework, that the value has changed to trigger the re-execution of a binding. This is done with the firePropertyChanged
method as shown in the following example.
import io.nevernull.mobileui.core.MobileUI;
public class MainController {
public String nextSpell = "";
public void someMethod() {
nextSpell = "Abracadabra";
MobileUI.firePropertyChanged(this, "nextSpell"); }
}
Hint: All bindings need to be executed on the main thread. The method
MobileUI.firePropertyChanged(...)
will automatically switch to the main thread for execution, if needed. If you are calling the abovesomeMethod()
from a background thread, you only need to make sure, that your bound properties are volatile or thread-safe by other means.
View Templates for List Bindings
Some widgets like the ListView bind to data in form of collections or arrays. The items in these collections are typically rendered in a uniform way.
View templates allow you to define a template for the collection items. You define a template by adding a Template
element to the collection view as shown in the following listing.
public List<String> listItems = Arrays.asList("one", "two", "three");
<ListView width="fill_parent" height="fill_parent" items="#{listItems}">
<Template>
<TextView text="#{item}" />
</Template>
</ListView>
Within the template, the variable item
is implicitly assigned with the current item in the collection that is bound to the parent view (listItems
in this example). You can bind properties of the item to attributes in the template view.
Conditional Templating
Sometimes, you need a possibility to select a template depending on the item's state. In this case, you can apply the if
attribute to the Template
element and check for a certain condition. In the following example, the first template is only applied, if the item (a String in this case) starts with the word 'ERROR'. If this is not the case, the following template is checked. If the item starts with the word 'WARN', it is applied. Only if this condition is not true, the last template is applied.
<ListView width="fill_parent" height="fill_parent" items="#{listItems}">
<Template if="#{item.startsWith('ERROR')}" >
<TextView text="#{item}" textColor="red" />
</Template>
<Template if="#{item.startsWith('WARN')}">
<TextView text="#{item}" textColor="yellow" />
</Template>
<Template>
<TextView text="#{item}" textColor="green" />
</Template>
</ListView>