what is the 'right' way to start position

Building on the PositionSample and the ObjectStore example, what is the 'right way' to start the gps (with Position.LOCATION_CONTINUOUS), and then pass the object returned to pages to display in a multi-page application?

I expected pos2 = infoPos to assign pos2 to the object pos. What I'm finding here is that pos2 gets assigned to null rather than pointing to the object pos. What is the right way to do this?

using Toybox.Application as App;
using Toybox.Position as Position;
using Toybox.WatchUi as Ui;



class myApp extends App.AppBase
{

var pos = null;

//! onStart() is called on application start up
function onStart() {
Position.enableLocationEvents(Position.LOCATION_CONTINUOUS, method(:onPosition));
}

//! onStop() is called when your application is exiting
function onStop() {
Position.enableLocationEvents(Position.LOCATION_DISABLE, method(:onPosition));
}

function onPosition(info) {
pos=info;
Ui.requestUpdate();
}

// Initial View
function getInitialView()
{


return [new startupView(pos), new startupViewDelegate()];
}

// For this app all that needs to be done is trigger a Ui refresh
// since the settings are only used in onUpdate().
function onSettingsChanged()
{
Ui.requestUpdate();
}
}


using Toybox.WatchUi as Ui;
using Toybox.Graphics as Gfx;
using Toybox.Application as App;

class startupView extends Ui.View
{

hidden var pos2;

function initialize(infoPos) {
pos2 = infoPos;

}

function onUpdate(dc)
{
var app = App.getApp();


dc.setColor(Gfx.COLOR_RED, Gfx.COLOR_BLACK);
dc.clear();
dc.drawText(3, 1, Gfx.FONT_SMALL, "startupView", Gfx.TEXT_JUSTIFY_LEFT);
dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_TRANSPARENT);
if (pos != null){
dc.drawText(3, 20, Gfx.FONT_SMALL, "position: " + pos2.position.toDegrees()[0].toString(), Gfx.TEXT_JUSTIFY_LEFT);
dc.drawText(3, 37, Gfx.FONT_SMALL, "position: " + pos2.position.toDegrees()[1].toString(), Gfx.TEXT_JUSTIFY_LEFT);
}


}
}
  • There are a few things here that are odd.

    Go back to the position sample as a start, and then in onUpdate(), you can replace the parts you don't want with the stuff you're using in your current code.
    dc.drawText(3, 20, Gfx.FONT_SMALL, "position: " +posnInfo.position.toDegrees()[0].toString(), Gfx.TEXT_JUSTIFY_LEFT);
    dc.drawText(3, 37, Gfx.FONT_SMALL, "position: " +posnInfo.position.toDegrees()[1].toString(), Gfx.TEXT_JUSTIFY_LEFT);


    In your version, you fire up GPS just fine, but onPosition() isn't actually doing anything when it is called with data, and in the sample, a function in the view (setPosition()) is called in onPosition() to pass the data to the view. Your function initialize(infoPos) is never being called with position data.

    There's a few other ways to get it to work, but going back to the sample might be the best way for now. Put in some Sys.println() calls in the code, and you can see what is getting called and when in the console window.

    For testing, you use the Simulation>fit data in the simulator. "Simulate data" is a repeating loop (~2 minutes) of heading south, then northeast, or you can use your own .fit from a recorded run for more complex data.
  • in the sample, a function in the view (setPosition()) is called in onPosition() to pass the data to the view.


    Yes, I saw and think I understood that example. If I had more than one view, position in each view would need to be updated by onPosition() how would this be accomplished?
  • One thing you could do is to save "info" in a global. Just move "var pos = null;" outside the class.

    Then when you want the position, just reference that global directly. onPosition just saves "info" as "pos" and where ever you need to use it, just use pos.
  • While using a global definitely does get the job done, it could be argued that it is bad design. If you were just going to access a global, you might as well call Position.getInfo() to access the position data whenever you want. No need to declare or use your own global. This reduces the testing burden for your view classes if this is something that you are into.

    Here is an example of something that I frequently use...

    class MyView extends Ui.View
    {
    hidden var mParent;

    function initialize(parent) {
    View.initialize();
    mParent = parent;
    }

    function onShow() {
    mParent.setView(self);
    }

    function onHide() {
    mParent.setView(null);
    }

    function onUpdate(dc) {
    // draw
    }

    function onSettingsChanged() {
    // read settings for this view
    }

    function onPosition(info) {
    // read position data
    }
    }

    class MyApp extends App.AppBase
    {
    hidden var mView; // the active view

    function setView(view) {
    mView = view;
    }

    function onStart() {
    Position.enableLocationEvents(Position.LOCATION_ENABLE, method(:onPosition));
    }

    function onStop() {
    Position.enableLocationEvents(Position.LOCATION_DISABLE, null);
    }

    function getInitialView() {
    mView = new MyView();
    return [ mView ];
    }

    function onSettingsChanged() {
    if (mView != null and mView has :onSettingsChanged) {
    mView.onSettingsChanged();
    Ui.requestUpdate();
    }
    }

    function onPosition(info) {
    if (mView != null and mView has :onPosition) {
    mView.onPosition(info);
    Ui.requestUpdate();
    }
    }
    }


    If you ever get around to testing your view, you can poke and prod it by calling the public functions. There is no need to make that global or write a test adapter for the position module so you can push values into your view.

    Travis
  • I expected pos2 = infoPos to assign pos2 to the object pos. What I'm finding here is that pos2 gets assigned to null rather than pointing to the object pos.


    I just realized that both Jim and I failed to explain why you're seeing the behavior you are. I think this kind of thing is important to understand, even if you don't need to know to solve this particular problem.

    Consider your code (snipped and formatted)...

    class myApp extends App.AppBase
    {
    var pos = null;

    function onPosition(info) {
    pos=info;
    }

    // Initial View
    function getInitialView()
    {
    return [new startupView(pos) ];
    }
    }

    class startupView extends Ui.View
    {
    hidden var pos2;

    function initialize(infoPos) {
    pos2 = infoPos;
    }
    }


    When myApp is initialized, the instance variable pos is a reference to null. When startupView.initialize() completes, you have something like this...

    +------+ +------+
    | pos |------------->| |
    +------+ | |
    | null |
    +------+ | |
    | pos2 |------------->| |
    +------+ +------+


    Both pos and pos2 pointers to null (or are references to a special null object if you want to think of it that way). When myApp.onPosition() is called, pos is updated to refer to the Position.Info object maintained by the Position module. Now you have something like this...

    +------+ +------+
    | pos |------------->| info |
    +------+ +------+

    +------+ +------+
    | pos2 |------------->| null |
    +------+ +------+


    This is why you never see pos2 updated. You may then think that the fix is to somehow force pos2 to refer to the info object later, after it is no longer assigned to null. The issue there is that nothing guarantees that the info parameter passed to onPosition() is always the same object. If the position module allocates a new object and assigns the appropriate state every time the callback is invoked, you'll be right back where you started (having a reference to stale data).

    As shown above, one solution is to have a reference to the active view in the app, and forward the information along as it comes in. I think this is a clean and elegant solution. Another option would be to add a level of indirection. This is a hack, but it is useful to understand. If you give the view a reference to an object that is shared with the app, you can update the shared object in one and see the update in the other. The following code uses a simple array for this, but you could easily use a small class to do the same.

    class myApp extends App.AppBase
    {
    var pos = [ null ];

    function onPosition(info) {
    pos[0] = info;
    }

    // Initial View
    function getInitialView()
    {
    return [new startupView(pos) ];
    }
    }

    class startupView extends Ui.View
    {
    hidden var pos2;

    function initialize(infoPos) {
    pos2 = infoPos;
    }

    // you can access the updated position data at pos2[0].
    }


    This results in the following object configuration...

    +------+
    | |
    | | +------+
    | pos |------------> | null |
    +------+ | | +------+
    | pos2 |------------->| |
    +------+ +------+


    Does that help?