Fenix 5X widget disappears

Hello,
I have problem that looks similar https://ciq.forums.garmin.com/developer/connect-iq/f/discussion/8045/fenix-5x-crashes-when-accessing-data-stored-in-a-watch-face in my application but not possible to find the cause. I tried to put try catch everywhere but nothing in CIQ.log. The application updates data in background, it updates and I store them in properties. I am a little confused and I can not find the solution. After a few minutes the widget disappears but I can also see the update a few times. No crash in the simulator. For now I'm testing only fenix 5x. Would you have a track?

SDK=3.0.10

GbaliseApp.mc

using Toybox.Application as App;
using Toybox.Background;
using Toybox.System as Sys;
using Toybox.Time.Gregorian as Time;
using Toybox.Communications as Comm;
using Toybox.WatchUi as Ui;

(:background)
class GbaliseApp extends App.AppBase {
	private var mView;
	private var mBg;

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

	// onStart() is called on application start up
	function onStart(state) {

	}

	// onStop() is called when your application is exiting
	function onStop(state) {
	}

	// Return the initial view of your application here
	function getInitialView() {
		//register for temporal events if they are supported
		if(Toybox.System has :ServiceDelegate) {
			Background.registerForTemporalEvent(new Time.Duration(5 * 60));
		}/* else {
			Sys.println("****background not available on this device****");
		}*/
		mView = new GbaliseView();
		//return [ new GbaliseView() ];
		return [ mView ];
	}

	function onBackgroundData(data) {
		var clockTime = Sys.getClockTime();
		var timeString = Lang.format("$1$:$2$:$3$", [clockTime.hour, clockTime.min.format("%02d"),clockTime.sec.format("%02d")]);
		try {
			App.getApp().Properties.setValue("bgdtupd",timeString);
		} catch(e) {
			Sys.println("Error when saving propertie bgdtupd: "+e.getErrorMessage());
		}
		saveData(data);
		//mView.requestUpdate();
		Ui.requestUpdate();
		return null;
	}

	function getServiceDelegate(){
		mBg = new GbaliseBgServiceDelegate();
		//return [new GbaliseBgServiceDelegate()];
		return [ mBg ];
	}

	function saveData(data) {
		if(data!=null) {
			try {
				if(App.getApp().Properties.getValue("dtupd").find(data[0]["date"])==null) {
					App.getApp().Properties.setValue("newdata",true);
				}
			} catch(e) {
				Sys.println(e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("dtupd",data[0]["date"].toString());
			} catch(e) {
				Sys.println("Error when saving propertie dtupd: "+e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("vmin",data[0]["vitesseVentMin"].toNumber());
			} catch(e) {
				Sys.println("Error when saving propertie vmin "+e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("vmoy",data[0]["vitesseVentMoy"].toNumber());
			} catch(e) {
				Sys.println("Error when saving propertie vmoy "+e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("vmax",data[0]["vitesseVentMax"].toNumber());
			} catch(e) {
				Sys.println("Error when saving propertie vmax "+e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("vdirmoy",data[0]["directVentMoy"].toNumber());
			} catch(e) {
				Sys.println("Error when saving propertie vdirmoy "+e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("vdir",data[0]["directVentInst"].toNumber());
			} catch(e) {
				Sys.println("Error when saving propertie vdir "+e.getErrorMessage());
			}
			try {
				App.getApp().Properties.setValue("temp",data[0]["temperature"].toNumber());
			} catch(e) {
				Sys.println("Error when saving propertie temp "+e.getErrorMessage());
			}
		}

	}
}

GbaliseBg.mc

using Toybox.Background;
using Toybox.System as Sys;
using Toybox.Communications as Comm;
using Toybox.Application as App;

const URL = "https://data.ffvl.fr/php/historique_relevesmeteo.php";

// The Service Delegate is the main entry point for background processes
// our onTemporalEvent() method will get run each time our periodic event
// is triggered by the system.

(:background)
class GbaliseBgServiceDelegate extends Sys.ServiceDelegate {

	function initialize() {
		Sys.ServiceDelegate.initialize();
	}

	function onStart() {
		fetchData();
	}

	function onTemporalEvent() {
		fetchData();
	}

	function fetchData() {
		var idbalise;
		var heures;
		idbalise = App.getApp().getProperty("idbalise");
		heures = App.getApp().getProperty("heures");
		if (idbalise != null && heures != null && isPhoneConnected()) {
			var params = {
				"idbalise" => idbalise.toString(),
				"heures" => heures.toString()
			};
			var options = {
				:method => Comm.HTTP_REQUEST_METHOD_GET,
				:headers => {"Content-Type" => Comm.REQUEST_CONTENT_TYPE_JSON},
				:responseType => Comm.HTTP_RESPONSE_CONTENT_TYPE_JSON
       		};
       		try {
				Comm.makeWebRequest(URL, params, options, method(:onResponse));
			} catch (e) {
				Sys.println("makeWebRequest exception: "+e.getErrorMessage());
			}
		}
	}

	function onResponse(responseCode, data) {
		if(responseCode != 200) {
			try {
			Background.exit(null);
			} catch (e) {
				Sys.println("Background exit exception: "+e.getErrorMessage());
			}
		} else {
			try {
				Background.exit(data);
			} catch (e) {
				Sys.println("Background exit exception: "+e.getErrorMessage());
			}
		}
	}

	function isPhoneConnected() {
		var con = false;
		try {
			//con = System.getDeviceSettings().phoneConnected;
			con = Sys.getDeviceSettings().connectionAvailable;
		} catch(e) {
			Sys.println("Error System.getDeviceSettings().connectionAvailable: "+e.getErrorMessage());
			con = false;
		}
		return con;
	}
}

GbaliseView.mc

using Toybox.WatchUi as Ui;
using Toybox.Application as App;
using Toybox.Graphics as Gfx;
//using Toybox.Time.Gregorian as Time;
using Toybox.System as Sys;
using Toybox.Timer;
using Toybox.Attention;

const idlerefresh = 5000;

class GbaliseView extends Ui.View {
	private var _screenSize = new[2];
	private var size_max;
	private var updTimer;

	private var bgdtupd;
	private var dtupd;
	private var vmin;
	private var vmax;
	private var vmoy;
	private var vdirmoy;
	private var vdir;
	private var temp;
	private var newdata=true;
	private var attvdir=true;
	private var attvmax=true;
	private var vibeDataVdir;
	private var vibeDataVmax;

	private var vsmax; // Vent max spot
	private var vsmin; // Vent min spot
	private var dmax; // Direction vent max spot
	private var dmin; // Direction vent min spot
	private var ddmax;
    private var ddir;
    private var startAngle=0;
	private var endAngle=0;
	// Cache references to drawables immediately after layout, to avoid expensive findDrawableById() calls in onUpdate();
	private var mDrawables = {};

	function initialize() {
		getData();
		updTimer = new Timer.Timer();
		vibeDataVdir = [
	        new Attention.VibeProfile(100, 1000),
	        new Attention.VibeProfile(0, 1000),
	        new Attention.VibeProfile(100, 1000),
	        new Attention.VibeProfile(0, 1000)
		];
		vibeDataVmax = [
	        new Attention.VibeProfile(100, 4000),
	        new Attention.VibeProfile(0, 4000),
	        new Attention.VibeProfile(100, 4000),
	        new Attention.VibeProfile(0, 4000)
	    ];
		View.initialize();
	}

	// Load your resources here
	function onLayout(dc) {
		setLayout(Rez.Layouts.Gbalise(dc));
		_screenSize[0] = dc.getWidth();
		_screenSize[1] = dc.getHeight();
		size_max = _screenSize[0] > _screenSize[1] ? _screenSize[1] : _screenSize[0];
		cacheDrawables();
    	updTimer.start(method(:timerCallback), idlerefresh, true);
	}

	function cacheDrawables() {
		try {
			mDrawables["vmoy"] = View.findDrawableById("vmoy");
			mDrawables["vmin"] = View.findDrawableById("vmin");
			mDrawables["vmax"] = View.findDrawableById("vmax");
			mDrawables["vdirmoy"] = View.findDrawableById("vdirmoy");
			mDrawables["temp"] = View.findDrawableById("temp");
			mDrawables["bgdtupd"] = View.findDrawableById("bgdtupd");
			mDrawables["dtupd"] = View.findDrawableById("dtupd");
		} catch(e) {
			Sys.println("cacheDrawables() error:"+e.getErrorMessage());
		}
	}

	function timerCallback() {
		try {
    		Ui.requestUpdate();
    	} catch(e) {
			Sys.println("Ui.requestUpdate() error:"+e.getErrorMessage());
		}
	}

	// Called when this View is brought to the foreground. Restore
	// the state of this View and prepare it to be shown. This includes
	// loading resources into memory.
	function onShow() {
		updTimer.start(method(:timerCallback), idlerefresh, true);
		Ui.requestUpdate();
	}

	// Update the view
	function onUpdate(dc) {
		// Call the parent onUpdate function to redraw the layout
		try {
			View.onUpdate(dc);
		} catch(e) {
			Sys.println("View.onUpdate(dc) error:"+e.getErrorMessage());
		}
		dc.clear();
		drawData(dc);
	}

	// Called when this View is removed from the screen. Save the
	// state of this View here. This includes freeing resources from
	// memory.
	function onHide() {
		updTimer.stop();
		Sys.println("onHide()");
	}

	function getData() {
		try {
			bgdtupd = App.getApp().Properties.getValue("bgdtupd");
		} catch(e) {
			bgdtupd = "Waiting for data";
		}
		if(bgdtupd == null) {
			bgdtupd = "Waiting for data";
		}

		try {
			dtupd = App.getApp().Properties.getValue("dtupd");
		} catch(e) {
			dtupd = "Waiting for data";
		}
		if(dtupd == null) {
			dtupd = "Waiting for data";
		}

		try {
			vmax = App.getApp().Properties.getValue("vmax");
		} catch(e) {
			vmax = -1;
		}
		if(vmax == null) {
			vmax = -1;
		}

		try {
			vmoy = App.getApp().Properties.getValue("vmoy");
		} catch(e) {
			vmoy = -1;
		}
		if(vmoy == null) {
			vmoy = -1;
		}

		try {
			vmin = App.getApp().Properties.getValue("vmin");
		} catch(e) {
			vmin = -1;
		}
		if(vmin == null) {
			vmin = -1;
		}

		try {
			vdirmoy = App.getApp().Properties.getValue("vdirmoy");
		} catch(e) {
			vdirmoy = -1;
		}
		if(vdirmoy == null) {
			vdirmoy = -1;
		}

		try {
			vdir = App.getApp().Properties.getValue("vdir");
		} catch(e) {
			vdir = -1;
		}
		if(vdir == null) {
			vdir = -1;
		}

		try {
			temp = App.getApp().Properties.getValue("temp");
		} catch(e) {
			temp = -1;
		}
		if(temp == null) {
			temp = -1;
		}

		try {
			dmin = App.getApp().Properties.getValue("dmin");
		} catch(e) {
			dmin = 0;
		}
		if(dmin == null) {
			dmin = 0;
		}

		try {
			dmax = App.getApp().Properties.getValue("dmax");
		} catch(e) {
			dmax = 360;
		}
		if(dmax == null) {
			dmax = 360;
		}

		try {
			vsmin = App.getApp().Properties.getValue("vsmin");
		} catch(e) {
			vsmin = 0;
		}
		if(vsmin == null) {
			vsmin = 0;
		}

		try {
			vsmax = App.getApp().Properties.getValue("vsmax");
		} catch(e) {
			vsmax = 200;
		}
		if(vsmax == null) {
			vsmax = 200;
		}

		try {
			newdata = App.getApp().Properties.getValue("newdata");
		} catch(e) {
			newdata = true;
		}
		if(newdata == null) {
			newdata = true;
		}

		try {
			attvdir = App.getApp().Properties.getValue("attvdir");
		} catch(e) {
			attvdir = true;
		}
		if(attvdir == null) {
			attvdir = true;
		}

		try {
			attvmax = App.getApp().Properties.getValue("attvmax");
		} catch(e) {
			attvmax = true;
		}
		if(attvmax == null) {
			attvmax = true;
		}
	}

	function drawData(dc) {
		getData();
		var linecolor = Gfx.COLOR_BLUE;
		try {
			mDrawables["bgdtupd"].setText( bgdtupd );
		} catch(e) {
			Sys.println(e.getErrorMessage());
		}
		try {
        	mDrawables["dtupd"].setText( dtupd );
        } catch(e) {
			Sys.println(e.getErrorMessage());
		}
		try {
        	drawLineOrientation(dc, _screenSize[0] / 2, _screenSize[1] / 2, size_max, dmin, Gfx.COLOR_BLACK, 1.1);
        	drawLineOrientation(dc, _screenSize[0] / 2, _screenSize[1] / 2, size_max, dmax, Gfx.COLOR_BLACK, 1.1);
        } catch(e) {
			Sys.println("drawLineOrientation dmin/dmax error:" + e.getErrorMessage());
		}

		startAngle = degToArc(dmin);
	    endAngle = degToArc(dmax);
    	dc.setColor(Gfx.COLOR_GREEN, Gfx.COLOR_TRANSPARENT);
        dc.drawArc( _screenSize[0] / 2, _screenSize[1] / 2, _screenSize[0] / 2 - 8, Gfx.ARC_CLOCKWISE, startAngle, endAngle);
        dc.drawArc( _screenSize[0] / 2, _screenSize[1] / 2, _screenSize[0] / 2 - 7, Gfx.ARC_CLOCKWISE, startAngle, endAngle);
        dc.drawArc( _screenSize[0] / 2, _screenSize[1] / 2, _screenSize[0] / 2 - 6, Gfx.ARC_CLOCKWISE, startAngle, endAngle);

        dc.setColor(Gfx.COLOR_RED, Gfx.COLOR_TRANSPARENT);
        dc.drawArc(_screenSize[0] / 2, _screenSize[1] / 2, _screenSize[0] / 2 - 8, Gfx.ARC_CLOCKWISE, endAngle, startAngle);
        dc.drawArc(_screenSize[0] / 2, _screenSize[1] / 2, _screenSize[0] / 2 - 7, Gfx.ARC_CLOCKWISE, endAngle, startAngle);
        dc.drawArc(_screenSize[0] / 2, _screenSize[1] / 2, _screenSize[0] / 2 - 6, Gfx.ARC_CLOCKWISE, endAngle, startAngle);
	    if(vdirmoy!=-1) {
	    	// Repporte l'intervale avec dmin= 0°
        	ddir = vdirmoy - dmin;
        	if(ddir < 0) {
        		ddir = ddir + 360;
        	}
        	ddmax = dmax - dmin;
        	if(ddmax < 0) {
        		ddmax = ddmax + 360;
        	}
        	if(ddir >= 0 and ddir<= ddmax) {
        		linecolor = Gfx.COLOR_GREEN;
        	} else {
        		linecolor = Gfx.COLOR_RED;
        	}
        	drawLineOrientationMoy(dc, _screenSize[0] / 2, _screenSize[1] / 2, size_max, vdirmoy, linecolor);
        	try {
        		mDrawables["vdirmoy"].setText( vdirmoy.toString()+"°" );
        	} catch(e) {
				Sys.println(e.getErrorMessage());
			}
        }
        if(vdir!=-1) {
        	// Repporte l'intervale avec dmin= 0°
        	ddir = vdir - dmin;
        	if(ddir < 0) {
        		ddir = ddir + 360;
        	}
        	ddmax = dmax - dmin;
        	if(ddmax < 0) {
        		ddmax = ddmax + 360;
        	}
        	if(ddir>=0 and ddir<=ddmax) {
        		linecolor = Gfx.COLOR_GREEN;
        	} else {
        		linecolor = Gfx.COLOR_RED;
        		if(attvdir==true and newdata==true) {
		        	if (Attention has :backlight) {
		    			Attention.backlight(true);
					}
					if (Attention has :playTone) {
						Attention.playTone(Attention.TONE_LOUD_BEEP);
					}
					if (Attention has :vibrate) {
				    	Attention.vibrate(vibeDataVdir);
					}
				}
        	}
        	drawLineOrientation(dc, _screenSize[0] / 2, _screenSize[1] / 2, size_max, vdir, linecolor, 1.3);
        }
        if(vmin!=-1) {
	        if (vmin>=vsmin) {
	        	mDrawables["vmin"].setColor(Gfx.COLOR_GREEN);
	        } else if (vmin<vsmin) {
	        	mDrawables["vmin"].setColor(Gfx.COLOR_RED);
	        }
	        try {
	        	mDrawables["vmin"].setText( "Min:" + vmin.toString() + " Km/h");
	        } catch(e) {
				Sys.println(e.getErrorMessage());
			}
	    }
	    if(vmoy!=-1) {
	        if(vmoy>=vsmin and vmoy<=vsmax) {
	        	mDrawables["vmoy"].setColor(Gfx.COLOR_GREEN);
	        } else if (vmax>vsmax) {
	        	mDrawables["vmoy"].setColor(Gfx.COLOR_RED);
	        } else if (vmin<vsmin) {
	        	mDrawables["vmoy"].setColor(Gfx.COLOR_BLUE);
	        }
	        try {
	        	mDrawables["vmoy"].setText( "Moy:" + vmoy.toString() + " Km/h");
	        } catch(e) {
				Sys.println(e.getErrorMessage());
			}
	    }
	    if(vmax!=-1) {
	    	if (vmax>vsmax) {
	        	mDrawables["vmax"].setColor(Gfx.COLOR_RED);
	        	if(attvmax==true and newdata==true) {
		        	if (Attention has :backlight) {
		    			Attention.backlight(true);
					}
					if (Attention has :playTone) {
						Attention.playTone(Attention.TONE_ALERT_HI);
					}
					if (Attention has :vibrate) {
				    	Attention.vibrate(vibeDataVmax);
					}
				}
	        } else if (vmax<=vsmax) {
	        	mDrawables["vmax"].setColor(Gfx.COLOR_GREEN);
	        }
	        try {
        		mDrawables["vmax"].setText( "Max:" + vmax.toString() + " Km/h");
        	} catch(e) {
				Sys.println(e.getErrorMessage());
			}
        }
        if(temp!=-1) {
        	try {
        		mDrawables["temp"].setText( temp.toString() + " °C" );
        	} catch(e) {
				Sys.println(e.getErrorMessage());
			}
        }
        if(newdata==true) {
        	try {
        		App.getApp().Properties.setValue("newdata",false);
        	} catch(e) {
				Sys.println(e.getErrorMessage());
			}
        }
	}

	function drawLineOrientationMoy(dc, center_x, center_y, size, orientation, color){
		var radius=size/2;
		dc.setColor(color, Gfx.COLOR_TRANSPARENT);
		var ore = Math.toRadians(-orientation);
		var ore1 = Math.toRadians(-orientation-2);
		var ore2 = Math.toRadians(-orientation+2);

		var xy1 = pol2Cart(center_x, center_y, ore1, radius);
		var xy2 = pol2Cart(center_x, center_y, ore2, radius);
		var xy3 = pol2Cart(center_x, center_y, ore, radius/1.3);
		dc.fillPolygon([xy1, xy2, xy3]);
	}

	function drawLineOrientation(dc, center_x, center_y, size, orientation, color, sizel){
		var radius=size/2;
		dc.setColor(color, Gfx.COLOR_TRANSPARENT);
		//dc.setColor(Gfx.COLOR_BLUE, Gfx.COLOR_TRANSPARENT);
		var ore = Math.toRadians(-orientation);

		var xy1 = pol2Cart(center_x, center_y, ore, radius);
		var xy2 = pol2Cart(center_x, center_y, ore, radius/sizel);
		dc.drawLine(xy1[0], xy1[1], xy2[0], xy2[1]);
	}

	function pol2Cart(center_x, center_y, radian, radius) {
		var x = center_x - radius * Math.sin(radian);
		var y = center_y - radius * Math.cos(radian);

		return [Math.ceil(x), Math.ceil(y)];
	}

	function degToArc(deg) {
		var res = 0;
		if (deg < 90) {
			res = 90 - deg;
		} else {
			res = 90 - deg + 360;
		}
		return res;
	}
}

properties.xml

<resources>
  <properties>
    <property id="newdata" type="boolean">true</property>
    <property id="attvmax" type="boolean">true</property>
    <property id="attvdir" type="boolean">true</property>
    <property id="idbalise" type="number">158</property>
    <property id="heures" type="number">1</property>
    <property id="dmin" type="number">315</property>
    <property id="dmax" type="number">45</property>
    <property id="vsmin" type="number">5</property>
    <property id="vsmax" type="number">30</property>
    <property id="dtupd" type="string">Waiting for data</property>
    <property id="bgdtupd" type="string">Waiting for data</property>
    <property id="vmin" type="number">-1</property>
    <property id="vmax" type="number">-1</property>
    <property id="vmoy" type="number">-1</property>
    <property id="vdirmoy" type="number">-1</property>
    <property id="vdir" type="number">-1</property>
    <property id="temp" type="number">-1</property>
  </properties>
</resources>

settings.xml

<resources>
	<settings>
    	<setting propertyKey="@Properties.idbalise" title="@Strings.idbalise_title">
      		<settingConfig type="numeric" required="true" />
    	</setting>
    	<setting propertyKey="@Properties.heures" title="@Strings.heures_title">
      		<settingConfig type="numeric" required="true" min="1" max="72" />
    	</setting>
    	<setting propertyKey="@Properties.dmin" title="@Strings.dmin_title">
      		<settingConfig type="numeric" required="false" min="0" max="360" />
    	</setting>
    	<setting propertyKey="@Properties.dmax" title="@Strings.dmax_title">
      		<settingConfig type="numeric" required="false" min="0" max="360" />
    	</setting>
    	<setting propertyKey="@Properties.vsmin" title="@Strings.vsmin_title">
      		<settingConfig type="numeric" required="false" min="0" max="300" />
    	</setting>
    	<setting propertyKey="@Properties.vsmax" title="@Strings.vsmax_title">
      		<settingConfig type="numeric" required="false" min="0" max="300" />
    	</setting>
    	<setting propertyKey="@Properties.attvmax" title="@Strings.attvmax_title">
      		<settingConfig type="boolean" />
    	</setting>
    	<setting propertyKey="@Properties.attvdir" title="@Strings.attvdir_title">
      		<settingConfig type="boolean" />
    	</setting>
	</settings>
</resources>

  • Ok, a couple things here.

    1) In general, for stuff like this you don't want to use properties Unless it's something you want available in app settings.  For local stuff, use Storage.

    2) There are a bunch of case like this:

    App.getApp().Properties.setValue("bgdtupd",timeString);

    and instead you might want to use

    Application.Properties.setValue("bgdtupd",timeString);

    The App.getApp().setProperty() is the older style call, and you kind of mixed the two together.  I see a bit of code that tells me this may have started with a sample I posted, and there I used the old style.

    3) onStart() isn't needed in the ServiceDelegate.  onStart() in the AppBase is used.

    Norally, the main app of a widget times out in a couple minutes, and it will go away (you don't see this in the sim), but the background will keep running every 5 minutes in your case.  Is that what you're seeing?  The next time the widget runs, onBackgroundData will be called right away with the latest data.

    Are you expecting the widget to just keep running and not time out?

  • ok thank you very much for all your answers, I understand better my mistakes. Indeed I take some examples (very useful).
    Yes, ideally I would like the widget to remain in the foreground. Is it possible I did not find the method in the DOC API SDK and if I understand correctly it's better that I make a WatchFace for that

  • Widgets, by design have a short time before they time out and return to the watchface.  About 2 minutes is the max if there is no user interaction.  They are meant to be "quick views" of things.

    If you want something that keeps running, you can do a device-app instead of a widget, and in a widget or device-app, you can also do comm in the main app and don't need a background process for that.  With a background process in them, you can be requesting the data while the widget or device-app isn't running, and even queue it up in the background for the next time the main app runs. (by default, you get the data from the last time the background ran.