How to make multiple API calls (to draw a line chart)

Hi all, 

Forgive me possibly simple question, but I am very new to this and just trying to learn (by doing). 

I want to make simple widget, that is showing the current wind speed, but also history of the wind speed as a chart (e.g., last 4-8hrs). After few painful evenings of digging documentation and this forum, I managed to display the wind speed, but have no idea how to proceed with chart. The major problem is that API returns it for given time, so to get a history I need to make several API calls, then parse each of it to get the speed, and (I guess) pass it to a variable so it can be drawn, e.g. as line using drawLine. I made makeRequest(delay) that takes delay as an argument to retrieve data from the past, but 

1) how to execute multiple API calls? 

2) how to pass the parse result into variable? 

and finally 3) how to draw it ('for' loop of drawLine? other better way?)

For some reason I am not able to attach the code here ("you have been blocked" message), so here it is on GitHub.

Thanks for any help :) 

A.

  • Drawing the data is the easy part.  Collecting the data is harder.  In your widget, you'll likely only get the data once.  (widgets on devices time out every 1-2 minutes).  Once you have the data, you want to save it to Application.Storage, so the next time the widget runs it starts with that data.

    But that still means that the data is only requested when the widget is run, and there could be a long time with no data.

    or when you request the data, is that data for the past 4 hours?

    You may want to look at a background service that requests the data, say every 10 minutes, and then pass an array of that data using Background.exit() and seen in the widget itself by way of onBackgroundData next time the widget runs. Then merge what you have in Storage with the new data, and save it back to Storage.  You may also want to include a time stamp with the reading so you can purge anything more than 4 hours old.  Also, a background may not run all the time, based on what else is going on with the device, and a timestamp can be used to detect gaps in the data.

  • Thanks for the comment! It seems like it will be much easier to deploy API that will do the stuff first, so all required data will be returned within one call. 

    But that still leaves me with the question on how to pass data from onReceive function to the variable? I am clearly missing something and can't find any relevant example. I have makeRequest (to make API call), then onReceive (where I do all parsing), but how to return value to the array that I can later use for drawing? 

  • The simplest way is have it in a global that can be accessed by both you AppBase and view.  A cleaner way would be to do the makeWebRequest in your view and not your AppBase

  • But how to return variable from onReceive function ("outside" of it)? 
    Lets say I want to print some_variable in the example below: 

    class appView extends Ui.View {

    hidden var _message;
    var some_variable;

    function initialize(message) {
    View.initialize();
    _message = message;
    System.println(message);
    }

    function onLayout(dc) {
    setLayout(Rez.Layouts.MainLayout(dc));
    View.findDrawableById("message").setText(_message);
    }

    function onShow() {
    makeRequest();
    System.println( some_variable );

    }

    function onUpdate(dc) {
    // Call the parent onUpdate function to redraw the layout
    View.onUpdate(dc);
    }

    function onHide() {
    }

    function onReceive(responseCode, data) {

    some_variable = 10;
    return(some_variable);
    }

    function makeRequest() {
    var url = "">api.data.gov.sg/.../wind-speed";
    var params = {
    "date_time" => "2021-06-22T15:00:00"
    };
    var options = {
    :method => Communications.HTTP_REQUEST_METHOD_GET,
    :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
    };
    var responseCallback = method(:onReceive);

    Communications.makeWebRequest(url, params, options, method(:onReceive));
    return(responseCallback);
    }
    }

  • Not sure why you are doing "return(responseCallback);" at the end.  When you do the makeWebResuqest, you pass the call back function for when data (or an error) is available.  You're onReceive() function will be called when that happens.

    if at the class level, you have

    var mydata=null;

    then in your onReceive() you have

    mydata=data;

    Then in onUpdate you just use "mydata" to reference that data.

     

  • Using System.println( mydata) within onUpdate() gives me null. But if I do System.println( mydata) within onReceive() function, everything is fine. Thats the whole problem...

  • function onReceive(responseCode, data) {

    some_variable = 10;
    return(some_variable);
    }

    In this code do

    mydata=data; (which you kind of do with "some_variable"

    In this case, data is a local to that functiion, and you're return statement doesn't actually pass anything to the caller.

    Also, "data" can be null if there was an error, so you want to check if "responceCode==200" to make sure there is data.

  • That is what I am doing (do I miss something?). There is no error in response. Data is printed corretly from within onResponse function. But I receive null if its called from onUpdate(). 

    class appView extends Ui.View {

    hidden var _message;
    var mydata;

    function initialize(message) {
    View.initialize();
    _message = message;
    System.println(message);
    }

    function onLayout(dc) {
    setLayout(Rez.Layouts.MainLayout(dc));
    View.findDrawableById("message").setText(_message);
    }

    function onShow() {
    makeRequest();


    }

    function onUpdate(dc) {
    // Call the parent onUpdate function to redraw the layout
    View.onUpdate(dc);
    System.println("onUpdate " + mydata);
    }

    function onHide() {
    }

    function onReceive(responseCode, data) {
    if (responseCode == 200) {
    System.println("onReceive " + data);
    mydata = data; // print success

    } else {
    System.println("Response: " + responseCode); // print response code
    }

    }

    function makeRequest() {
    var url = "">api.data.gov.sg/.../wind-speed";
    var params = {
    "date_time" => "2021-06-22T15:00:00"
    };
    var options = {
    :method => Communications.HTTP_REQUEST_METHOD_GET,
    :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON
    };
    var responseCallback = method(:onReceive);

    Communications.makeWebRequest(url, params, options, method(:onReceive));
    }
    }

  • The first time onUpdate is called, it could very likely be null.  In a widget or device app, you need to call WatchUi.requestUpdate() when you want onUpdate to be called.

  • It is working now. Thanks a lot  for your input and fast responses, extremely valuable. Without it I would never move forward with it :) Cheers!