Class Binders

java.lang.Object
com.codename1.binding.Binders

public final class Binders extends Object

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.BinderBootstrap and splices a new cn1app.BinderBootstrap(); into the per-build application stub before Display.init.
  • JavaSE simulator + desktop -- JavaSEPort#postInit calls Class.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:

  1. 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 calls notifyChanged(this).
  2. 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 Details

    • register

      public static <T> void register(Binder<T> binder)
      Installs binder under binder.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

      public static <T> Binder<T> get(Class<T> type)
      Looks up the binder for type (by type.getName()) or null when none is registered.
    • bind

      public static <T> Binding bind(T model, Container container)
      Pushes the model values into the matching components in container, wiring up the two-way listeners declared on its @Bind fields. Throws IllegalStateException when no binder is registered for model.getClass().
    • notifyChanged

      public static void notifyChanged(Object model)

      Called from the build-time-instrumented setter at every return point. Walks the live bindings for model.getClass().getName() and refreshes those whose source is model. 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

      public static void registerBinding(NotifiableBinding binding)
      Generated binders call this to enroll a new live binding in the per-class registry. Call unregisterBinding from disconnect to remove it.
    • unregisterBinding

      public static void unregisterBinding(NotifiableBinding binding)
      Inverse of registerBinding. Called from Binding#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 per enterUpdate.
    • 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.