Skip to content

Configuring new Flutter constructors for f mx

Kenneth Tilton edited this page Feb 2, 2023 · 7 revisions

Motivation

Matrix code has always involved simple trees of models. This in turn makes coding simpler, with nesting alone expressing trees. HTML and many UI libraries work the same.

Not Flutter. As a full-blown OO library, it simply produced different structures for different widgets, where HTML adopted all-children-all-the-time. Furthermore, with Dart a strongly typed language, it was not an option to use :children for widgets with only one child. :children expects a collection.

As I cooked up the Flutter/MX wrappers for Flutter widgets, I did what I could to recreate the HTML consistency.

How I make a new wrapper

  • First, find the Flutter constructor in the reference. For example, googling "flutter scaffold" offered many hits, but I grabbed the one advertising the Scaffold class - material library - Flutter - Dart API docs.

  • Next, is it stateful? Not always easy to determine! When in doubt, I look for a section called "Inheritance". Here is the entry for Scaffold:

Inheritance
Object > DiagnosticableTree > Widget > StatefulWidget (bingo!) > Scaffold
  • Next, does it take :child or :children?

Scaffold does not.

  • Next, is there some attribute that would be a child in HTML, such as the content of a text widget?

Scaffold takes a body, a great candidate for a nested child. So:

(deftag tiltontec.flutter-mx.factory/k1-body-stateful scaffold m/Scaffold)

...which says we can code scaffolds as if they have one child, and the f/mx logic will pass that along to Flutter as :body.

(fx/scaffold
      {:appBar               (fx/app-bar
                               {:title (m/Text "Flutter/MX Counter")})
       :floatingActionButton (cF (fx/floating-action-button
                                   {:onPressed (as-dart-callback []
                                                 (mswap! (fm* :z-counter) :value inc))
                                    :tooltip   "Increment"}
                                   (m/Icon m.Icons/add)))}
      (fx/center
        (fx/column {:mainAxisAlignment m.MainAxisAlignment/center}
          (fx/text "You have pushed (+) this many times:")
          (fx/text {:style (fx/in-my-context [me ctx]
                             (.-headline4 (.-textTheme (m.Theme/of ctx))))}
            {:name  :z-counter
             :value (cI 0)}
            (str (md/my-value))))))

That is just one example of how the answers to these questions guide my choice of existing wrappers, and may even force the creation of new wrappers. Ping me any time you need any help here, ideally with an existing Dart/Flutter or CLJD/Flutter example to replicate.

Now let us work thru some more examples to discover the edge cases I have handled so far.

Existing wrappers

  • k1-child-stateful: only one child is allowed, and that will be passed as :child to the constructor.
  • k1-child-stateless: see stateful variant
  • k1-content-stateless: only one child is allowed, and that will be passed as :content to the constructor.
  • children-stateless: the MX kids get passed as :children.
  • childless-stateless: simple. Examples: AppBar, DataTable, RoundedRectangleBorder, ListTile, FlutterLogo
  • konly-param1-stateless: only one child allowed, and that is passed as the first parameter to the constructor. Example: Text.
  • konly-param1-stateful: same as stateless ^^ variant, but stateful. Only example is the f/mx text! widget. See "Exceptions" below.
  • childless-stateful: nice, terminal widgets. Examples: CheckBox, TextField.
  • prop-param1-childless-stateful: same as childless-stateful, with a twist. Consider Flutter m/Icon. It wants the icon as the first parameter to the constructor. In CLJD-ese:
(m/Icon m.Icons/add .color m.Colors/black)

But f/mx works by building proxy objects, not by providing new constructors. So we need to provide "parameter one" as a property, and let f/mx take care of calling the Flutter constructor appropriately:

(fx/icon {:icon m.Icons/add :color m.Colors/black})
  • k1-home-stateful: The Flutter widget takes a :home property that works better as the only child. Example: material-app.
  • prop-param1-childless-stateless: unused. Vestigial?

Exceptions

Here is one interesting exception, anyway.

Text

Flutter has the Text widget as stateless. Matrix GUIs do quite a lot with reactive text. An example from f/mx TodoMVC:

(defn to-do-display [todo]
  (fx/list-tile
    {:leading  (completion-toggler todo)
     :trailing (delete-button todo)
     :title    (cF (let [comp-toggle (md/mget me :leading)]
                     (fx/text!
                       {:style (cF (if (md/mget comp-toggle :value)
                                     (p/TextStyle
                                       .color m.Colors/grey
                                       .decoration p.TextDecoration/lineThrough)
                                     (p/TextStyle .color
                                       (if (mget todo :ae-events?)
                                         m.Colors/red
                                         m.Colors/blue))))}
                       (td-title todo))))}
    {:name        :my-list-tile
     :todo        todo}))

If I use a stateless option:

(deftagleaf tiltontec.flutter-mx.factory/konly-param1-stateless text m/Text)

The Todo item does not get highlighted until something else triggers a larger scope redraw. So we keep that as presumably more light weight, then add a second definition involving state:

(deftagleaf tiltontec.flutter-mx.factory/konly-param1-stateful text! m/Text)

FYI, the expansion of that reifies m/StatefulWidget, while the build function invokes the m/Text constructor. This lets f/mx dispatch a precision targeted .setState, rather than do so excessively just to get one text widget redrawn. No idea if there was a better way.