This example project is an effort to show how an MVVM architecture can work with Roku SceneGraph.
This example project allows you to add multiple counters/incrementers to the screen. CounterCollectionView
has buttons to add or remove the counters. When a counter is added, the view reacts to add additional CounterView
components.
The counters themselves are initialized to zero, but their values are stored in the registry on each update. This means that each time the app is launched, if a counter had been previously created, it will be automatically set to the last stored value.
- Run
npm install
- Create an
.env
file like:
ROKU_HOST=<IP address of Roku Device>
ROKU_PASSWORD=<Developer password of Roku Device>
- Run from command line:
npm run sideload
- Run from VSCode
The Model-View-ViewModel architecture pattern aims to separate the view/user-interface from the business logic of the app.
In this example, that is achieved through using SceneGraph components that extend from BaseView
and BaseViewModel
.
The model is a component that encapsulates simple state or data. In order to observe fields on the model, and so it can be passed across component boundaries, the model must be a SceneGraph node. There is no specific base component for these models, although in many cases, the SceneGraph node ContentNode
could be used.
The view is responsible for display, navigation/focus and signaling events based on user interaction only. Any updates to the view are based on observing properties/fields of the view model. In this example, all views extend the component BaseView
.
Using the function bindViewModelField
views can bind values directly from the view model, or receive events when a view model's field changes.
The view model is responsible for handling commands generated by the view associated with user interaction, coordinating those events with any changes that may happen to the underlying data. It is event-driven based on observing fields of the view. It may have an underlying model that represents the internal data. In this example, all view models extend the component BaseViewModel
.
In order to encapsulate specific processes (either for data management, external communication, handling specific view logic like focus or screen stacks, etc.), managers are used. These are singleton components that all other components have access to. Managers have public functions for the specific tasks they can do. Managers extend the component BaseManager
. Managers are analogous to services in some other frameworks.
This project uses Brighterscript Version 1.
Brighterscript V1 allows rich typing of components and other entities. Specific types declarations for each aspect of the app are included in files named interfaces.bs
in the same folder. These are then included in each component scope via an import
statement.
When declaring the types for a SceneGraph component, there are two different interfaces that should be created:
one to represent the properties on m
, and potentially an interface narrowing of the component node's fields.
In the CounterViewModel
component, the model
field is defined as a node
, but for our purposes, we know it will be the specific node of subtype CounterModel
. This we extend the given type roSGNodeCounterViewModel
with the specifics:
interface CounterViewModelTyped extends roSgNodeCounterViewModel
model as roSGNodeCountModel
end interface
Likewise, inside the component, we know there will be specific properties available on m
. One of these is m.top
, which will be the specific type CounterViewModelTyped
:
interface CounterViewModelM extends BaseViewModelM
top as CounterViewModelTyped
end interface
Then at the top of each file specific to that component, the interfaces.bs
file in imported, and we tell the compiler what to treat m
as using the typecast
statement:
import "interfaces.bs"
typecast m as CounterViewModelM
Brighterscript can now validate against those types, ensuring type-safety at code- and compile-time for all properties and fields for that component.
This project assumes that you will be using VSCode as your code editor.
-
Go to the
Run and Debug
panel. -
Select the option
Launch roku-mvvm (dev)
-
build
: Builds your project withbrighterscript
. Includes source maps. -
build:prod
: Builds your project without source maps. -
lint
: Lints your source files with@rokucommunity/bslint
-
lint:fix
: Lints your source files and applies automatic fixes. -
format
: Formats your source files withbrighterscript-formatter
-
format:fix
: Formats your source files and applies automatic fixes.
This project was created using
npx create-roku-app