makeWebRequest into a variable

Hi!

I would need some help, I am quite new on this, I am trying to get some info from a .json file and save it in a variable. All the makeWebRequest seems to work as I print the information with :onreceive but then I do not know how to store it in a variable.

I get the information from the file using makeRequest() that call the onReceive function which saves the information into a public variable "Current_station" and prints the variable. Up to here all is ok.

But then I try to use the variable later, after calling makeRequest()  but seems empty. I provide you my code to see if you could help me. 

Please, note that I am totally new on this so I might say some things wrongly.

import Toybox.Application;
import Toybox.Lang;
import Toybox.WatchUi;
import Toybox.System;

class Velib_widgetApp extends Application.AppBase {

    var Current_station;

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

    // onStart() is called on application start up
    function onStart(state as Dictionary?) as Void {            
    }

    // set up the response callback function
         function onReceive(responseCode, data) {
             if (responseCode == 200) {
                 System.println("Request Successful");                   // print success
                 System.println(data);
                 Current_station = data["data"]["system_id"];
                 System.println(Current_station);
             }
             else {
                 System.println("Response: " + responseCode);            // print response code 
             }
         }

         function makeRequest() {
             var url = "https://velib-metropole-opendata.smoove.pro/opendata/Velib_Metropole/system_information.json";                         // set the url

             var params = {                                              // set the parameters
            
             };

             var options = {                                             // set the options
                 :method => Communications.HTTP_REQUEST_METHOD_GET,      // set HTTP method
                 :headers => {                                           // set headers
                         "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON},
                                                                         // set response type
                 :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
             };

            
             // Make the Communications.makeWebRequest() call
             Communications.makeWebRequest(url, params, options, method(:onReceive));
        }

    // onStop() is called when your application is exiting
    function onStop(state as Dictionary?) as Void {
    }

    // Return the initial view of your application here
    function getInitialView() as Array<Views or InputDelegates>? {
        makeRequest();
        System.println(Current_station);
        return [ new Velib_widgetView(Current_station) ] as Array<Views or InputDelegates>;
    }

}

  • So it works the first time but not the second?

    what is the responseCode in onRecieve?

  • When I print in the "onReceive" funtion it works the 3 printIn, System.println("Request Successful"), System.println(data) and System.println(Current_station). When I print in function getInitialView(), the line 59 after makeRequest(),  System.println(Current_station) prints "null". Seems that the variable Current_station here does not exist.

    The output is:

    null
    Request Successful
    {data=>{system_id=>Paris, language=>en, url=>, name=>Paris, timezone=>Europe/Paris}, lastUpdatedOther=>1636056532, ttl=>3600}
    Paris
  • makeWebRequest() returns immediately, without waiting for the request to finish (because it could take a long time, and you don't want to block your whole program). That's why it has a callback method to handle the response (onReceive).

    When you call makeRequest() in getInitialView(), it calls makeWebRequest(), which returns immediately, so Current_station won't have the value from the response yet. Since you're passing in Current_station to the view's constructor, it will never see the value from the response. (As your view and your app class have different variables to store the current station.)

    In this case it would work better to call makeWebRequest() from the view, which would have Current_station as its own member variable. That way the view can display the current station once the web request is completed.

  • Thanks for your reply. I understand that it is like an async call and that is why the variable is not ready.

    But if I call makeWebRequest() from the view, would I have the same problem? Maybe I didnt understand you. I dont know how to force the view to wait until I have the answer from makeWebRequest().

    Additionally I dont understand properly the difference between onLayout and onUpdate, when are they called and who calles them. (I read the garmin information, forum... and still not clear for me...)

    I give you the view code but is not working, gives null the watch face

    import Toybox.Graphics;
    import Toybox.WatchUi;
    import Toybox.System;
    
    class Velib_widgetView extends WatchUi.View {
        
        var Current_station;
    
        function initialize() {
            View.initialize();
            var velib_obj=new Velib_widgetApp();
            var aaa=velib_obj.makeRequest();
            System.println(aaa);
        }
    
        // Load your resources here
        function onLayout(dc as Dc) as Void {
            onUpdate(dc);
        }
    
        // 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() as Void {
        }
    
        // Update the view
        function onUpdate(dc as Dc) as Void {
            dc.setColor(Graphics.COLOR_TRANSPARENT, Graphics.COLOR_BLACK);
            dc.clear();
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
            var modeLabelFontHeight = dc.getFontHeight(Graphics.FONT_SMALL);
            var centerX = dc.getWidth() / 2;
            var centerY = dc.getHeight() / 2;
            var labelLocY = centerY - 10;
            var valueLocY = labelLocY + modeLabelFontHeight + 5;
            dc.drawText(centerX, (centerY - 50), Graphics.FONT_SMALL, Current_station, Graphics.TEXT_JUSTIFY_CENTER);
        }
    
        // Called when this View is removed from the screen. Save the
        // state of this View here. This includes freeing resources from
        // memory.
        function onHide() as Void {
        }
    
    }
    

  • One thing you can do is check Current_station for null.  (if(Current_station==null) and then in onUpdate, if it's not null, display it.

  • To be clear, it doesn't literally matter where you call makeWebRequest() from, but what actually matters is that the data returned from makeWebRequest() should be available to the view. So you can't pass Current_station from the app object to the view (at init time), as you did in the OP, because you're passing Current_station by value, when that value is initially null. That's why your original code doesn't work.

    The updated code you posted above doesn't work for a couple of reasons:

    - "var velib_obj=new Velib_widgetApp();" - you can't/shouldn't manually create a new instance of the app yourself. If you want to access to app from the view, you can either pass in a reference from the app to the view when the view is constructed, or you could use Application.getApp() (https://developer.garmin.com/connect-iq/api-docs/Toybox/Application.html#getApp-instance_function) and cast it to your app class.

    - "var aaa=velib_obj.makeRequest();" calling makeRequest() on the app presumably isn't going to update your Current_station value in your view, as you haven't written any code to do so.

    So you have roughly 2 choices:

    1) Have Current_station, makeWebRequest and onReceive all in the app class. The view class should access Current_station from the app somehow

    2) Have Current_station, makeWebRequest and onReceive all in the view class. This is the simplest for your case, IMO.

    Another thing I would do I initialize Current_station to "". That way you won't display "null" initially. Or you could do as Jim suggested and check for null (if Current_station is null, you don't call drawText). Either way would work.

    Here's an example:

    import Toybox.Graphics;
    import Toybox.WatchUi;
    import Toybox.System;
    
    class Velib_widgetView extends WatchUi.View {
        
        var Current_station = "";
    
        function initialize() {
            View.initialize();
            var velib_obj=new Velib_widgetApp();
            makeRequest();
    
        }
    
        function onReceive(responseCode, data) {
            if (responseCode == 200) {
                System.println("Request Successful");                   // print success
                System.println(data);
                Current_station = data["data"]["system_id"];
                System.println(Current_station);
            }
            else {
                System.println("Response: " + responseCode);            // print response code 
            }
        }
    
            function makeRequest() {
                var url = "https://velib-metropole-opendata.smoove.pro/opendata/Velib_Metropole/system_information.json";                         // set the url
    
                var params = {                                              // set the parameters
            
                };
    
                var options = {                                             // set the options
                    :method => Communications.HTTP_REQUEST_METHOD_GET,      // set HTTP method
                    :headers => {                                           // set headers
                            "Content-Type" => Communications.REQUEST_CONTENT_TYPE_JSON},
                                                                            // set response type
                    :responseType => Communications.HTTP_RESPONSE_CONTENT_TYPE_JSON,
                };
    
            
                // Make the Communications.makeWebRequest() call
                Communications.makeWebRequest(url, params, options, method(:onReceive));
        }
    
        // Load your resources here
        function onLayout(dc as Dc) as Void {
            onUpdate(dc);
        }
    
        // 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() as Void {
        }
    
        // Update the view
        function onUpdate(dc as Dc) as Void {
            dc.setColor(Graphics.COLOR_TRANSPARENT, Graphics.COLOR_BLACK);
            dc.clear();
            dc.setColor(Graphics.COLOR_WHITE, Graphics.COLOR_TRANSPARENT);
            var modeLabelFontHeight = dc.getFontHeight(Graphics.FONT_SMALL);
            var centerX = dc.getWidth() / 2;
            var centerY = dc.getHeight() / 2;
            var labelLocY = centerY - 10;
            var valueLocY = labelLocY + modeLabelFontHeight + 5;
            dc.drawText(centerX, (centerY - 50), Graphics.FONT_SMALL, Current_station, Graphics.TEXT_JUSTIFY_CENTER);
        }
    
        // Called when this View is removed from the screen. Save the
        // state of this View here. This includes freeing resources from
        // memory.
        function onHide() as Void {
        }
    
    }

  • Thanks for all your ideas! Finally I solved it creating another function, called from onReceive() that calls the requestUpdate() function.

    function updateLabels() {  
                //! update last feed time label
                var labelString = "Last feed: " + Current_station;
                View.findDrawableById("lblLastFeed").setText(labelString);  
                requestUpdate();    
             }
    Thanks!