How to get data efficiently from a menu or input delegate to the view

Hi, I am doing my first steps in coding an app and wondering what's an efficient way to transfer data from the delegates to the view. I have done it currently via some global variables. Nevertheless, I have studied some apps in github and realized that some define even a global class which is used to store data. But maybe it is even more efficient to have a direct pointer to the view? That said it is also a question of maintainability.

How are you doing data transfer between those classes?
  • In terms of runtime efficiency and memory usage, it is probably best to use global variables and refer to them with the bling...

    var globalVar = 0;

    class MyDelegate extends Ui.BehaviorDelegate
    {
    function initialize() {
    BehaviorDelegate.initialize();
    }

    function onSelect() {
    $.globalVar = $.globalVar + 1;
    Ui.requestUpdate();

    return true;
    }
    }

    class MyView extends Ui.View
    {
    function initialize() {
    View.initialize();
    }

    function onUpdate(dc) {
    // snipped...
    dc.drawText(x, y, font, $.globalVar.toString(), 0);
    }
    }


    The $. prefix on the expression $.globalVar (the bling) tells the compiler not to look for the symbol globalVar in the scope of onUpdate() or the scope of MyView. It will just look for the name globalVar in the global scope. This makes the load of globalVar much more efficient. This code modifies the value directly, requests an update, and then reads the value directly. It doesn't get much better than that in terms of efficiency.

    If you want to keep your code clean, the ConnectIQ system lends itself to a Model-View-Controller type pattern (Model-View-Controller, Model-View-Presenter, Model-View-ViewModel, ...). Applying a pattern will likely reduce efficiency of the code, but it will usually have a positive impact on the quality and reusability.
  • Hi Travis,

    thanks for sharing this. The pattern you mentioned is exactly what I meant: Did I get that right here (click):
    The Model is the class 'View', the view is the class 'Menu' and the controller is the class 'MenuInputDelegate'.

    Thus I need just a backpointer from MenuInputDelegate to the class 'View' which I mentioned in my OP with a "direct pointer to the view". If I got that right, I am wondering why this would be less efficient than introducing a global variable.

    PS: Feel free to edit the UML if you want.
  • I can't see your file without logging into a gliffy account...

    Typically, when using globals, you are directly accessing variables (actually, the runtime system looks them up in a map maintained by the global object). When you start to break the functionality out, you would typically switch to using functions on the model so that it can control access and implement business logic. Adding the function calls adds overhead. I'm sure the cost is small, but some people freak out about this stuff before they actually know there is a problem (hello premature optimization).

    You could implement a Model-View-Controller-like pattern where the view and controller have direct access to the model (just like they both have access to global variables). The controller would update the model and trigger Ui updates, and the view would fetch data directly from the model for drawing. This should add minimal overhead.

    If you write a Model-View-Controller where the model tracks observers and notifies them of changes, you add additional overhead. The collection of observers (probably a map of objects to methods, or just an array of objects) will add memory costs, and you'll have to iterate over that collection to notify the observers when a change occurs, which will have some associated costs. You'll end up with cleaner code, but it may be slightly less efficient. For some reason this is the system that I like, but it requires jumping through a bunch of hoops to get it to work.

    The following is a MVC implementation that is similar to what I'm using for an app that I'm working on now. It is complicated, but it does separate concerns fairly well. I'm not really happy that the view looks directly at the model, but I could fix that by adding a mediator (a class to receive the notifications from the model and updates the view) if I felt it were really important.

    class MyModel
    {
    hidden var _observers;

    function initialize() {
    _observers = {};
    }

    function addObserver(observer, callback) {
    _observers.put(observer, callback);
    }

    function removeObserver(observer) {
    _observers.remove(observer);
    }

    function onChangeOccurred() {
    var callbacks = _observers.values();
    for (var i = 0, j = callbacks.size(); i < j; ++i) {
    callbacks.invoke(self);
    }
    }

    hidden var _value = 0;

    function getValue() {
    return _value;
    }

    function incrementValue() {
    _value += 1;
    onChangeOccurred();
    }
    }

    class MyController extends Ui.BehaviorDelegate
    {
    hidden var _model;

    function initialize(model) {
    BehaviorDelegate.initialize();
    _model = model;
    }

    hidden var _view;

    // it is often useful to know when the view is shown or hidden. for instance, if
    // we need access to GPS data, we now have entry points to enable/disable the
    // gps. we also have access to the view so we can send it input to control
    // presentation of the data.

    function onShow(view) {
    _view = view;
    _view.registerSelf(_model);
    }

    function onHide(view) {
    _view.deregisterSelf(_model);
    _view = null;
    }

    function onSelect() {
    _model.incrementValue();
    return true;
    }
    }

    class MyView extends Ui.View
    {
    // view has reference to controller _only_ so that it may
    // notify the controller when self is shown/hidden.
    hidden var _controller;

    function initialize(controller) {
    View.initialize();
    _controller = controller;
    }

    function onShow() {
    _controller.onShow(self);
    }

    function onHide() {
    _controller.onHide(self);
    }

    function registerSelf(model) {
    model.addObserver(self, method(:onModelChanged));

    // update the view from the model when we connect to it
    onModelChanged(model);
    }

    function deregisterSelf(model) {
    model.removeObserver(self);
    }

    hidden var _value;

    function onModelChanged(model) {
    _value = model.getValue();
    Ui.requestUpdate();
    }

    function onUpdate(dc) {
    // snipped...
    dc.drawText(x, y, font, _value.toString(), 0);
    }
    }
    [/code]
  • Thus I need just a backpointer from MenuInputDelegate to the class 'View' which I mentioned in my OP with a "direct pointer to the view". If I got that right, I am wondering why this would be less efficient than introducing a global variable.

    Sorry, I hadn't noticed mention of the menu delegate.

    In the case of menus, yes, the Menu is the view, the MenuInputDelegate is the controller, and there is no model. I typically use a method to communicate the information about the selecte item to any object that registers for the response.

    class MyColorMenuDelegate extends Ui.MenuInputDelegate
    {
    hidden var _callback;

    function initialize(callback) {
    MenuInputDelegate.initialize();
    _callback = callback;
    }

    function onMenuItem(item) {
    var colors = {
    :COLOR_RED => 0xFF0000,
    :COLOR_GREEN => 0x00FF00,
    :COLOR_BLUE => 0x0000FF
    };

    _callback.invoke(colors[item]);
    return true;
    }
    }


    That way the menu delegate doesn't have to know anything about who gets the input and there is no requirement on the name of the function. Then I use it like this...

    class MyViewDelegate extends Ui.BehaviorDelegate
    {
    hidden var _model;

    function initialize(model) {
    BehaviorDelegate.initialize();
    _model = model;
    }

    function onMenu() {
    var delegate = new MyColorMenuDelegate(self.method(:onColorSelected));
    Ui.pushView(new Rez.Menus.MyColorMenu(), delegate, Ui.SLIDE_UP);

    return true;
    }

    function onColorSelected(color) {
    _model.setColor(color);
    return true;
    }
    }


    Travis
  • Hi Travis,

    thanks for pointing me in the right direction. You probably mean:
    _callback.invoke(colors[item]);


    Right?
  • Yes. I write too much code in this forum post box.

    Additionally, you could have the menu delegate write directly to the model. The advantage of doing it this way is that you can reuse the color picker menu for different attributes.

    Travis
  • Hi Travis,

    I examined your code from post #4 in detail and have drawn this UML diagram:



    What I don't understand is:
    • Where can I find the pointer from MyView to MyModel as we can find it on that pattern: https://de.wikipedia.org/wiki/Model_View_Controller (note: just examine the jpg since the pattern's description is in german)
    • How or better where do you do the initialization of the 3 pointers?
  • The link from view to model is not a direct association.. The view in the posted code only gets a reference to the model when the onModelChanged callback is invoked.

    If you want this link to exist, you could add a _model member to MyView. You could remove the model parameter from onModelChanged, and the self argument from the callback invocation in onChangeOccurred.

    As for how to set the _model reference, I would move the code from registerSelf and deregisterSelf into onShow and onHide, and take a reference to the model as a parameter to initialize.

    As for how to setup all of the pointers, the classes do it themselves for the most part. The link from controller to view is done automatically when the controller is notified the view is being shown/hidden, and the link from model back to view would be automatic when the view is told to show/hide. This is nice because it avoids circular reference problems causing leaks without you having to write any brittle code. Assuming you initialize the view's moles reference in initialize, the setup would look like this..

    var model = new MyModel();

    var controller = new MyController(model);
    var view = new MyView(model, controller);


    Of course, if you add the model reference to the view, you really probably remove the observer functionality from the model entirely, and just have the model call Ui.requestUpdate() to notify the active view that the model changed. That does simplify things a bit, but it doesn't really line up with the 'normal' MVC configuration anymore.

    Travis