how to update a bitmap after background event?

Hi,

I've only started using ConnectIQ for a few days so apologies if I've missed anything. I would like to update a bitmap after a background event has run.  I based my background event on this code as well as some other examples I found online. This is my Background.mc file:

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

(:background)
class BgbgServiceDelegate extends Toybox.System.ServiceDelegate {
	
	var APIKEY = "my key";
	var lat = 38.8551;
	var lon = -94.8001;
	
	function initialize() {
		Sys.ServiceDelegate.initialize();
		inBackground = true;
	}
	
    function onTemporalEvent() {
	    	var UNITS = (Sys.getDeviceSettings().temperatureUnits==Sys.UNIT_STATUTE) ? "imperial" : "metric";
			var url = "https://api.openweathermap.org/data/2.5/weather";
	    	var param = {
	    		"lat" => lat.toFloat(),
	    		"lon" => lon.toFloat(),
	    		"appid" =>APIKEY,
	    		"units" => UNITS};
			var options = {
				:methods => Comm.HTTP_REQUEST_METHOD_GET,
				:headers => {"Content-Type" => Communications.REQUEST_CONTENT_TYPE_URL_ENCODED},
				:responseType => Comm.HTTP_RESPONSE_CONTENT_TYPE_JSON
			};
   	
			Comm.makeWebRequest(
	    	   	url,
	    	   	param,
	    	   	options,
	    	   	method(:receiveWeather));
    }
    
    function receiveWeather(responseCode,data) {
    	var ret = (responseCode == 200) ? data :responseCode;
    	
		Background.exit(ret);
	}
}

Then, in the onBackgroundData() function of the App.mc class, I get my variables like so (I'm aware I haven't yet checked if they are null from an error code):

...

function onBackgroundData(data) {

    	var weather = data.get("weather")[0].get("icon");
    	var temperature = data.get("main").get("temp");
    	var weatherCodeValue = weatherCodes.get(weather);

        App.getApp().setProperty("weatherCode", weatherCodeValue);
        App.getApp().setProperty("temperature", temperature);
        Ui.requestUpdate();
    }    

And I assume I can obtain these variables using the App.getApp().getProperty() function. Based on a post in the same thread I linked earlier, it seems the initialize() function runs each time a background event has run, so what I did was put this code there:

class TestView extends Ui.WatchFace {

// based on the value of weatherCode
var icons = {
	          "Snow" => Rez.Drawables.hud_snow,
	          "Sun"  => Rez.Drawables.hud_sunny,
	          "Few Clouds" => Rez.Drawables.hud_partlycloudy,
	          ...
	        }; 
	        
... }

    function initialize() {
        WatchFace.initialize();
        
        temperature = App.getApp().getProperty("temperature");
    	weatherCode = App.getApp().getProperty("weatherCode");
    
  		if (!weatherCode.equals(null))
  		{
  			weatherHUD = null; 
  			weatherHUD = Ui.loadResource(icons.get(weatherCode));
  			Ui.requestUpdate();
  		}
  		
...

function onUpdate(dc as Dc) as Void {

... 
if (!weatherCode.equals(null)) { dc.drawBitmap(x_pos, y_pos, weatherHUD); }

...

if (!temperature.equals(null)) {
       		dc.drawText(x, y, Gfx.FONT_XTINY, Lang.format("$1$" + StringUtil.utf8ArrayToString([0xC2,0xB0]), [temperature.format("%d")]), Gfx.TEXT_JUSTIFY_LEFT);
        }
     
...

However, I don't believe this is the correct approach, as my app sometimes crashes when I simulate a background event with the simulator. What is the proper (and efficient) approach to do this?


Thanks.

Top Replies

All Replies

  • Based on a post in the same thread I linked earlier, it seems the initialize() function runs each time a background event has run, so what I did was put this code there:

    Well...the initialize method of your *App* class runs every time the background process runs, not after a background event finishes.

    It's because the foreground process and the background process share the same code for the App class (although they don't share any memory at runtime, except for what's passed to onBackgroundData).

    The initialize() method of your *test view* won't get called after (or before) a background event, because your test view isn't part of the background process. (The background process has the main app class, and everything annotated with :background)

    However, since onBackgroundData() runs in the foreground process, you can simply use global vars or App class member variables to pass your data to TestView. You're correctly calling Ui.requestUpdate(), so all that's left is:

    1) Copy relevant values from the data passed to onBackground(data) to global vars or App class members

    2) On TestView.onUpdate(), read the values updated in 1)

    A slightly different way of doing 1) would be to keep a reference to TestView in the App class, and update the relevant values there.

    Having said all of that, I'm not sure why your app is crashing when you simulate a background event (I haven't looked super closely at your code though.)

    And you may still want to use app properties to save things between different runs of your watchface. (i.e. When navigating away from the watchface and back to it.)

  • First thing to do is in onBackgroundData(), check what's seen

    if(data instanceof Number) {

    //error on makeWebRequest

    } else {

    //got data

    }

    You can avoid errors in the sim, but not on real devices.  If you got real data, you can save it to Storage, but can also save it so it's accessible by the view without reloading it from Storage.  Have a class variable in your AppBasde called "view" and set it in your getInitialView, then view.myData will make it accessible to the view (or just use a global that both your AppBase and view can access).

    In your code, you only seem to access the data in the view's initialize, but that runs when the main app starts, not after onBackgroundData occurs, and you want to make sure you have the newest data in onUpdate, but the way you have it, that doesn't happen.  Using "myData" means that onUpdate can see the newest data without reading Storage.

    next, you seem to be using .equal() for things that aren't strings.  when it comes to data from OWM, things like temperature will be a Number/Float and not a string,   Add some println() calls as well as instanceof checks to see what's going on.

  • Thanks. Your post and Jim's one below were informative. I wasn't sure if it was fine to load resources in the OnUpdate(), but that's what I've done and everything seems to be working fine. 

    Regarding the background crash: I believe this was a memory issue from loading the background data into the foreground. My app already uses a lot of memory, so I only extracted what I needed from the response instead of the entire thing before passing it to the foreground. This seems to have solved the issue.