Overview

The View framework allows us to generate Roku screens and components, which have a known lifecycle. If you've done much Roku dev, you know how little of a framework exists for reasoning about a view's lifecycle events, such as being shown, getting focus, keys, etc. The Base view classes allow us to simply override abstract functions to seamlessly get lifecycle hooks for:

In addition the view framework contains many base classes that can be used

Mixins

Maestro makes use of many different mixin classes, which handle different aspects of view management (e.g. utils, focus, key handling), and then bundles these together in base classes (views and screens).

Aggregate views for tab (i.e. iOS style TabController navigation) and stack (i.e. iOS style NavController navigation) are provided

Loading styles and bundles

A great way of making maestro apps is to use nodeclasses, with json view definitions. It has many benefits:

Styles and Style manager

e.g.

style.json

{
  "fonts": {
    "app": {
      "title": "SystemBold, 40",
      "subtitle": "SystemBold, 20",
    },
    "menu": {
      "normal": "System, 30",
      "focused": "SystemBold, 30"
      }
  }
}

Style references

You can then reference these elements in other json styles. e.g.

MenuButton: {
  normalFontKey: "~fonts.menu.normal",
  focusedFontKey: "~fonts.menu.focused"
}

Referencing in this way works for any style that is loaded.

Loading styles

Styles are loaded into style manager with the loadJson method call. e.g. - in your initialization

      styleUrl = "pkg:/meta/Styles.json"
      m.log.info("loading styles from", styleUrl)

      styleManager = mc.createSGNode("mv_StyleManager", invalid, "styleManager")
      m.setInstance("styleManager", styleManager)
      m.global.addFields({ "styleManager": styleManager })

      styleJson = m.loadJson(styleUrl)
      styleManager@.setStyleJson(styleJson)

Creating views from json:

Simply load your style (or any json), and use the maestro view (mv) utility method, as such:

    private function createViews()
      style = m.styleManager@.getStyle("screens.SettingsScreen.views")
      m.createViewsFromStyleJson(style, m.top)
    end function

Note that creation is done view the mc.createViewsFromStyleJson method (which for convenience is in BaseClass, BaseCell and BaseViewModel, to facilitate testing)

Rules

observing

In the latter case we are using directives to guide how the observe should be setup (in this case, pass both the node and the value, and only fire it once.). The args in that case are: `[ handler function name, send mode (both|node|none|value) - default value, fire once (true|false) - default false]

Updating views from json:

When you update views, you provide an assocarray of views by their id, with the values you wish to change, and call the mc.updateViewsWithStyleJson method (which for convenience is in BaseClass, BaseCell and BaseViewModel, to facilitate testing)

  m.updateViewsWithStyleJson(m.style.normal)

Note, the format for overriding here (an aa), will soon be updated to use the bundle override format described in the next section.

One can make it very trivial to update views, in response to certain states.

e.g. given a Button control:

    private function applyState(isFocused as boolean, isDisabled as boolean) as void
      'ensure view is correctly configured
      m.updateViewsWithStyleJson(m.style.normal)

      if isDisabled
        m.updateViewsWithStyleJson(m.style.disabled)
      else if isFocused
        m.updateViewsWithStyleJson(m.style.focused)
      end if
    end function

Loading bundles

Bundles are blobs of json that can represent views or other data. They are loaded on a file by file basis, and have the ability to have language overrides (or any key for that matter). They are stored in a special folder named NAME.bundle, with specifically named files, e.g:


NAME.bundle/
NAME.json - root file locale "en"
NAME.de.json - overrides, in "de" locale
NAME.fr.json - overrides, in "fr" locale

Note, the locale keys happen to be language codes here; but they could be anything. Casing is important and the naming must follow this convention. See source/view/SettingsScreen.spec.bundle for the exact format.

To use a bundle in your app, do the following:

private function createViews()
bundle = m.styleManager@.loadBundle("pkg:/source/view/SettingsScreen.bundle")
m.createViewsFromStyleJson(bundle.views, m.top)
end function

Bundle locales

Notes