Class Binders
Public entry point for the build-time component binding framework.
@Bindable classes get a generated binder at build time. Each binder's
static initializer self-registers with this registry. The registry stays
empty until something triggers each generated class's <clinit>:
- iOS / Android -- the build server probes the project zip for
cn1app.BinderBootstrapand splices anew cn1app.BinderBootstrap();into the per-build application stub beforeDisplay.init. - JavaSE simulator + desktop --
JavaSEPort#postInitcallsClass.forName("cn1app.BinderBootstrap")so the registry is populated on the same boundary. - Unit tests / manual init -- application code can call
Binders.register(...)directly.
Two-way bindings and the change-notification contract
A binding declared twoWay = true flows in both directions:
- Component -> model. The generated binder installs a listener on
the editable component (
TextField,CheckBox, etc.). When the user mutates the component, the binder calls the model's setter (or writes the public field directly), and the setter's instrumented exit callsnotifyChanged(this). - Model -> component. Application code that mutates the model
through a setter (instrumented by the build-time processor) causes
notifyChanged(this)to fire, which walks every live binding for that model and pushes the new value into the matching component.
The two paths together would loop forever if left to themselves -- the
model setter fires a change event, which refreshes the component, which
fires its own change event, which calls the model setter again. To
break the loop, every framework-initiated mutation runs inside an
"update region" guarded by a thread-local flag. While the flag is set,
notifyChanged is a no-op, and component listeners short-circuit.
Concretely:
Binders.bind(model, container)enters an update region for the initial model -> component push.Binding#refresh()enters an update region for every subsequent model -> component push.Binding#commit()enters an update region for the component -> model pull.- The generated component listener enters an update region before calling the setter.
Limitation: when a setter synchronously mutates a second bound
field (e.g. setFirstName also calls setFullName), the second field's
notification is also suppressed -- the user must call
binding.refresh() explicitly if they want the second component to
catch up. See Annotation-Component-Binding.asciidoc for details.
-
Method Summary
Modifier and TypeMethodDescriptionstatic <T> BindingPushes the model values into the matching components incontainer, wiring up the two-way listeners declared on its@Bindfields.static voidEnter an update region.static voidExit an update region.static <T> Binder<T> Looks up the binder fortype(bytype.getName()) or null when none is registered.static booleanTrue while the calling thread is inside a binding update region.static voidnotifyChanged(Object model) Called from the build-time-instrumented setter at every return point.static <T> voidInstallsbinderunderbinder.type().getName().static voidregisterBinding(NotifiableBinding binding) Generated binders call this to enroll a new live binding in the per-class registry.static voidunregisterBinding(NotifiableBinding binding) Inverse ofregisterBinding.
-
Method Details
-
register
Installsbinderunderbinder.type().getName(). The generated per-class binder's static initializer calls this; hand-written binders for classes outside the build's annotation scan call it explicitly. -
get
-
bind
-
notifyChanged
Called from the build-time-instrumented setter at every return point. Walks the live bindings for
model.getClass().getName()and refreshes those whose source ismodel. Short-circuits when the calling thread is already inside an update region, breaking the model -> component -> model loop.Application code rarely calls this directly; the build-time instrumentation wires it into generated setters automatically.
-
registerBinding
Generated binders call this to enroll a new live binding in the per-class registry. CallunregisterBindingfromdisconnectto remove it. -
unregisterBinding
Inverse ofregisterBinding. Called fromBinding#disconnect. -
enterUpdate
public static void enterUpdate()Enter an update region. Generated binder code calls this around every framework-initiated mutation -- model->component pushes and component->model pulls -- so the setter notification and the component change listener both short-circuit while we're inside. -
exitUpdate
public static void exitUpdate()Exit an update region. Call once perenterUpdate. -
isInUpdate
public static boolean isInUpdate()True while the calling thread is inside a binding update region. Generated component listeners check this and bail out early.
-