Animation - Persmission Required error

Hello everybody!

I'm trying to do the Goal Screen with animation on my Watch Face.

Here is the code containing animation:

function drawGoal(dc) {
var bitmap = new Bitmap({
:rezId => Rez.Drawables.smile,
:locX => dc.getWidth() / 2,
:locY => 50
});

bitmap.draw(dc);

Ui.animate(bitmap, :locY, Ui.ANIM_TYPE_EASE_IN, 10, 100, 5, null);
}


Everything is fine here. I see my bitmap if i do not call animate function. But if i do, i always get error Permission required.

Failed invoking <symbol>
Permission Required
in drawGoal
in onUpdate
Connection Finished


Can someone help me? Why i need permission here? Is Animation permission even exists?

Thanks.
  • Hello, Thank you. Btw, i tried solution with sleepingMode.

    Yes, but you did not do it correctly. You omitted the most important parts of my code.

    If you look at your code, you'll see that every time drawGoal() is called and sleeping is false, you...

    • allocate a new Ui.Text and associate it with the local variable text.
    • draw the temporary Ui.Text once.
    • associate the temporary Ui.Text with an animation.
    • let your reference to the Ui.Text go out of scope.


    This is a problem for several reasons.

    • the animate() call creates an object that keeps a reference to the drawable you pass in (your temporary Ui.Text) so the memory will not be released until the animation finishes.
    • the animator is the only thing that has a reference to that Ui.Text so nothing can draw it.


    To make matters worse, you are doing this as fast as the application allows, without releasing any of those resources. The end result is that your application runs out of some resource (probably timers used internally by the animate() call), and then it fails. You can verify this by putting a Sys.println("x") above the animate() call in your code. You'll see it gets called several times before failing.

    If you looked carefully at the code I provided, it uses the member variable _bitmap to know that there is an active animation. It uses this to avoid creating a new Ui.Text and associating it with a new animation. Again, here is a completely functioning version of your code after I've fixed it.

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

    class MyView extends Ui.WatchFace {

    var sleeping = false;
    var text;

    function initialize() {
    WatchFace.initialize();
    }

    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_BLACK, Gfx.COLOR_BLACK);
    dc.clear();

    drawGoal(dc);
    }

    function drawGoal(dc) {

    // if `text' is non-null, an animation is running.
    if (text != null) {
    text.draw(dc);
    }
    else if (!sleeping) {
    var startY = dc.getHeight();
    var endY = dc.getHeight() / 2 + 20;

    text = new Ui.Text({
    :font => Gfx.FONT_LARGE,
    :color => Gfx.COLOR_WHITE,
    :backgroundColor => Gfx.COLOR_BLACK,
    :text => "IT'S A GOAL!",
    :locX => dc.getWidth() / 2,
    :locY => startY,
    :justification => Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER
    });

    text.draw(dc);

    Ui.animate(text, :locY, Ui.ANIM_TYPE_EASE_IN, startY, endY, 3.0, method(:onAnimationComplete));
    }
    }

    function onAnimationComplete() {
    // set text back to null so we can run the animation again.
    text = null;
    }

    function onEnterSleep() {
    sleeping = true;
    Ui.requestUpdate();
    }

    function onExitSleep() {
    sleeping = false;
    Ui.requestUpdate();
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    var view = new MyView();

    return [view];
    }
    }


    All that said, I still think that you're going about this completely wrong. If you want to create a watch face to display a goal, you should use the functionality provided for you (using getGoalView()). The following is a working sample. You can use the Settings > Trigger Goal menu item to trigger a goal achievement to test this...

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

    // this is just a regular old watch face. it is super simple
    class MyWatchFace extends Ui.WatchFace
    {
    hidden var _sleeping;

    function initialize() {
    WatchFace.initialize();
    _sleeping = true;
    }

    hidden var _bitmap;

    function onEnterSleep() {
    _sleeping = true;
    Ui.requestUpdate();
    }

    function onExitSleep() {
    _sleeping = false;
    Ui.requestUpdate();
    }

    function onUpdate(dc) {
    var fmt;
    if (_sleeping) {
    fmt = "$1$:$2$";
    }
    else {
    fmt = "$1$:$2$:$3$";
    }

    var clockTime = Sys.getClockTime();
    clockTime = Lang.format(fmt, [
    (clockTime.hour + 11) % 12 + 1,
    clockTime.min.format("%02d"),
    clockTime.sec.format("%02d")
    ]);

    dc.setColor(Gfx.COLOR_WHITE, Gfx.COLOR_DK_BLUE);
    dc.clear();

    var cx = dc.getWidth() / 2;
    var cy = dc.getHeight() / 2;

    dc.drawText(cx, cy, Gfx.FONT_SMALL, clockTime,
    Gfx.TEXT_JUSTIFY_CENTER | Gfx.TEXT_JUSTIFY_VCENTER);
    }
    }

    // this is the goal view, and is based on your code and Jim's code from above
    class MyGoalView extends Ui.View
    {
    hidden static const goalNames = [ "Steps", "Floors", "Active Minutes" ];
    hidden var goalType;

    function initialize(goalType) {
    View.initialize();
    self.goalType = goalType;
    }

    hidden var _text;

    function onLayout(dc) {
    _text = new Ui.Text({
    :locX => dc.getWidth() / 2,
    :locY => dc.getHeight() / 2,
    :text => goalNames[goalType],
    :color => Gfx.COLOR_WHITE,
    :backgroundColor => Gfx.COLOR_TRANSPARENT,
    :font => Gfx.FONT_LARGE,
    :justification => Gfx.TEXT_JUSTIFY_CENTER
    });

    Ui.animate(_text, :locY, Ui.ANIM_TYPE_EASE_IN_OUT,
    dc.getHeight(),
    dc.getHeight() / 2,
    3.0,
    method(:onAnimationComplete));
    }

    function onUpdate(dc) {
    dc.setColor(Gfx.COLOR_RED, Gfx.COLOR_RED);
    dc.clear();

    _text.draw(dc);
    }

    function onAnimationComplete() {
    }
    }

    class MyApp extends App.AppBase
    {
    function initialize() {
    AppBase.initialize();
    }

    function getInitialView() {
    return [ new MyWatchFace() ];
    }

    function getGoalView(goalType) {
    return [ new MyGoalView(goalType) ];
    }
    }
  • Yes, but you did not do it correctly. You omitted the most important parts of my code.


    Thank you for great reply!

    You also omitted my words :) I already using getGoalView() and goal triggers normally in simulator without animation. Than you for your time anyway. Now i understand how animation works here!