0

I am creating a UWP app, simply put I have an async void GetWeather function that reads JSON from an API and creates an object. Calling the Forecast.getWeather() function I get the error " object reference required from non-static method". I have done my research but have not found a solution to this with a void method as I don't want to return an object just yet. Also if this is not possible (and maybe a better idea) how would I return the Object so it can be used on many different pages throughout the app or would the object values still be accessible if created in the void method?

Forecast.cs

    class Forecast
{
    public async void GetWeather()
    {
        var uri = new Uri("MY API URI HERE");
        using (HttpClient client = new HttpClient())
        {
            using (HttpResponseMessage response = await client.GetAsync(uri))
            {
                using (IHttpContent content = response.Content)
                {
                     var json = await content.ReadAsStringAsync();

                    var result = JsonConvert.DeserializeObject<RootObject>(json);
                    Debug.WriteLine("In async method");
                }
            }
        }
    }
}

MainPage

 public sealed partial class MainPage : Page
{
    public MainPage()
    {
        this.InitializeComponent();

        Forecast.GetWeather();

    }
}

Weather.cs

namespace WeatherForecast
{

public class Main
{
    public double temp { get; set; }
    public double temp_min { get; set; }
    public double temp_max { get; set; }
    public double pressure { get; set; }
    public double sea_level { get; set; }
    public double grnd_level { get; set; }
    public int humidity { get; set; }
    public double temp_kf { get; set; }
}

public class Weather
{
    public int id { get; set; }
    public string main { get; set; }
    public string description { get; set; }
    public string icon { get; set; }
}

public class Clouds
{
    public int all { get; set; }
}

public class Wind
{
    public double speed { get; set; }
    public double deg { get; set; }
}

public class Snow
{
    public double __invalid_name__3h { get; set; }
}

public class Sys
{
    public string pod { get; set; }
}

public class List
{
    public int dt { get; set; }
    public Main main { get; set; }
    public List<Weather> weather { get; set; }
    public Clouds clouds { get; set; }
    public Wind wind { get; set; }
    public Snow snow { get; set; }
    public Sys sys { get; set; }
    public string dt_txt { get; set; }
}

public class Coord
{
    public double lat { get; set; }
    public double lon { get; set; }
}

public class City
{
    public int id { get; set; }
    public string name { get; set; }
    public Coord coord { get; set; }
    public string country { get; set; }
}

public class RootObject
{
    public string cod { get; set; }
    public double message { get; set; }
    public int cnt { get; set; }
    public List<List> list { get; set; }
    public City city { get; set; }
}
}
MidnightP
  • 105
  • 1
  • 4
  • 12
  • Appreciate the speedy response @John but that gives an error on the `)` that _A new expression requires (),[], or {} after type_ – MidnightP Feb 22 '18 at 02:22
  • oops typo. `(new Forecast()).GetWeather();` – John Woo Feb 22 '18 at 02:22
  • Perfect thanks that has saved me greatly,also as I am new to UWP would my `result` object created in forecast.cs be available on different pages of my app or is the scope to withen forecast.cs ? – MidnightP Feb 22 '18 at 02:26

1 Answers1

2

The problem is that your method is not static, so you need to create an instance of the Forecast class to access it. You can read more on this in this SO question and also here.

The quick solution also would be to make your Forecast class static (as long as you don't want to have multiple different instances of it):

static class Forecast
{
    public static async void GetWeather()
    {
        var uri = new Uri("MY API URI HERE");
        using (HttpClient client = new HttpClient())
        {
            using (HttpResponseMessage response = await client.GetAsync(uri))
            {
                using (IHttpContent content = response.Content)
                {
                     var json = await content.ReadAsStringAsync();

                    var result = JsonConvert.DeserializeObject<RootObject>(json);
                    Debug.WriteLine("In async method");
                }
            }
        }
    }
}

But now we come upon a bigger problem. Your method is async and is async void. This is something you should do only when absolutely necessary as async void methods are so-called fire-and-forget. They start, but when they reach the first true asynchronous call, they will execute on their own and if something bad happens inside the method (like an exception) you will never know about it until the symptoms start appearing elsewhere in a hard-to-debug way. Also you never know when the result is actually ready for you to use.

The best solution is then Task return type. This presents a promise that the result will be available when the method execution is finished.

static class Forecast
{
    public static RootObject Result {get; private set;}

    public static async Task GetWeatherAsync()
    {
        var uri = new Uri("MY API URI HERE");
        using (HttpClient client = new HttpClient())
        {
            using (HttpResponseMessage response = await client.GetAsync(uri))
            {
                using (IHttpContent content = response.Content)
                {
                    var json = await content.ReadAsStringAsync();

                    Result = JsonConvert.DeserializeObject<RootObject>(json);

                    Debug.WriteLine("In async method");
                }
            }
        }
    }
}

You can see the method is no longer void but it still does not return the RootObject directly (as you requested, otherwise you could use Task<RootObject> to return it). I also added a Result property so that the result is accessible when the execution is finished. Because the class is static, the property is accessible from anywhere after the GetWeatherAsync method call finished.

Now how can you use this? Instead of the constructor you can call the method in OnNavigatedTo handler:

public override async void OnNavigatedTo(NavigationEventArgs e)
{
    base.OnNavigatedTo( e );
    try
    {
       await Forecast.GetWeatherAsync();
       //after await finishes, Result is ready

       //do something with Forecast.Result
    }
    catch
    {
       //handle any exceptions
    }
}

You may notice that I violated the rule that async methods should not be void. However, in this case, we are dealing with event handler which has a fixed notation so you have no other choice. However, I have added a try-catch block so that we can handle any exceptions if they happen.

I recommend reading more on async-await in documentation as it will help you understand the concept much better. I also suggest you to check out more detailed info on instance vs. static to better understand the distinction and when to use each of them.

Martin Zikmund
  • 38,440
  • 7
  • 70
  • 91