czwartek, 11 stycznia 2018

[2] Xamarin - Aplikacja pogodowa

W tym poście chciałbym przedstawić sposób wykonania aplikacji pogodowej w Visual Studio przy użyciu Xamarina na Androida.

[Źrodlo: https://en.wikipedia.org/wiki/Xamarin]

Wstęp:


Tutaj podobnie jak w przypadku programu aplikacji pogodowej dla C# będę wykorzystywał serwis appweather.org. Dane będą pobierane na podstawie podanej nazwy miasta (Warszawa) bądź połączenia nazwy miasta ze skrótem danego kraju (Warszawa,pl).

Aplikacja w emulatorze prezentuje się następująco:


Teraz przejdę przez plik .xaml zawierający szablon projektu:

Deklaracja nagłówka z nazwą aplikacji:

  1. <Label
  2.    FontSize="20"
  3.    FontAttributes="Bold"
  4.    TextColor="Black"
  5.    XAlign="Center"
  6.    Text="Weather App"/>

Najpierw ustalana jest wielkość czcionki, pogrubienie, pozycjonowanie a na końcu sam tekst aplikacji.

Nazwa miasta wpisywana jest do kontrolki Entry.

  1. <Entry
  2.    Text="{Binding City, Mode=Default}"
  3.    HorizontalTextAlignment="Center"
  4.    FontSize="20"
  5.    TextColor="Black"
  6.    FontAttributes="Bold"
  7.    Placeholder="Write search city"/>
  8. <ActivityIndicator
  9.     IsRunning="{Binding CheckWhenTaskInitializing,Mode=TwoWay}"/>

Poniżej definicji pola tekstowego znajduje się ActivityIndicator, odpowiada on za uruchamianie pobierania informacji o pogodzie. 

Pod kontrolką będzie znajdowała się ikona z aktualną pogodą. Jej załadowanie wygląda następująco:

  1. <Image Source="{Binding WeatherIcon}"
  2.                Aspect="AspectFit"
  3.                WidthRequest="50"
  4.                HeightRequest="50"/>

Do tej kontrolki wprowadzane są dane do wyświetlenie, sposób dopasowania obrazka oraz jego rozmiar.

Teraz część zawierająca kontrolki do wyświetlania danych dla miasta, temperatury, wilgotności itp:

  1. <StackLayout Spacing="10" x:Name="Country" Orientation="Horizontal"  BackgroundColor="Aqua">
  2.     <StackLayout HorizontalOptions="StartAndExpand">
  3.         <Label
  4.             FontSize="16"
  5.            TextColor="Black"
  6.            FontAttributes="Bold"
  7.            Text="Country:"/>
  8.     </StackLayout>
  9.     <StackLayout HorizontalOptions="EndAndExpand">
  10.         <Label
  11.             FontSize="16"
  12.            TextColor="Black"
  13.            FontAttributes="Bold"
  14.            Text="{Binding JsonWeatherReport.Sys.Country}"/>
  15.     </StackLayout>
  16. </StackLayout>
  17. <StackLayout Spacing="10" x:Name="Position" Orientation="Horizontal" BackgroundColor="Aqua">
  18.     <StackLayout HorizontalOptions="StartAndExpand">
  19.         <Label
  20.             FontSize="16"
  21.            TextColor="Black"
  22.            FontAttributes="Bold"
  23.            Text="Position:"/>
  24.     </StackLayout>
  25.     <StackLayout HorizontalOptions="EndAndExpand">
  26.         <Label
  27.             FontSize="16"
  28.            TextColor="Black"
  29.            FontAttributes="Bold"  
  30.             Text="{Binding PositionString}"/>
  31.     </StackLayout>
  32. </StackLayout>

Całość została przygotowana w oparciu o StackLayout. Każda linia zawiera po lewej stronie opis, po prawie znajduje się odczytany parametr. Dane do kontrolek przypisywane są ze zmiennej sformatowanej z dodatkowymi znakami, bądź bezpośrednio z odebranego JSON'a.

Kod Programu:


Na początku funkcja dekodująca dane JSON otrzymane z strony z aplikacją pogodową:

  1. public class JsonWeatherReport
  2. {
  3.     [JsonProperty("name")]
  4.     public string Name { get; set; }
  5.     [JsonProperty("coord")]
  6.     public Coord Coord { get; set; }
  7.     [JsonProperty("weather")]
  8.     public List<Weather> Weather { get; set; }
  9.    
  10.     [JsonProperty("base")]
  11.     public string Base { get; set; }
  12.    
  13.     [JsonProperty("main")]
  14.     public Main Main { get; set; }
  15.     [JsonProperty("visibility")]
  16.     public int Visibility { get; set; }
  17.     [JsonProperty("wind")]
  18.     public Wind Wind { get; set; }
  19.     [JsonProperty("clouds")]
  20.     public Clouds Clouds { get; set; }
  21.     [JsonProperty("dt")]
  22.     public int Dt { get; set; }
  23.     [JsonProperty("sys")]
  24.     public Sys Sys { get; set; }
  25.     [JsonProperty("id")]
  26.     public int Id { get; set; }
  27.    
  28.     [JsonProperty("cod")]
  29.     public int Cod { get; set; }
  30. }

Powyżej znajduje się główna klasa zawierająca dane do wpisywania do klas pomocniczych przechowujących pogrupowane wartości zgodne z formatem JSON zwracanym przez stronę.

Za przesłanie zapytania odpowiada następująca funkcja:

  1. public class GetWeatherApp<T>
  2. {
  3.     private const string OPENWEATHERAPI = "http://api.openweathermap.org/data/2.5/weather?q=";
  4.     private const string KEY = "f0f932e4dca4cb03bb603c7ce35e5f4a";
  5.     private const string APPIDStart = "&APPID=";
  6.  
  7.     HttpClient httpClient = new HttpClient();
  8.  
  9.     public async Task<T> getWeatherJson(string city)
  10.     {
  11.         string url = OPENWEATHERAPI + city + APPIDStart + KEY;
  12.  
  13.         try
  14.         {
  15.             var jsonData = await httpClient.GetStringAsync(url);
  16.             var weatherData = JsonConvert.DeserializeObject<T>(jsonData);
  17.  
  18.             return weatherData;
  19.         }
  20.   catch(ArgumentNullException e) { }
  21.         catch(HttpRequestException e)
  22.         {
  23.             //ex: no internet connection, DNS error
  24.         }
  25.  
  26.         return (T)Convert.ChangeType(""typeof(T)) ;
  27.     }
  28. }

Na samym początku deklarowane są stałe z adresem strony oraz kluczem. Po nich tworzony jest obiekt klasy HttpClient. W deklaracji zadania asynchronicznego tworzony jest adres url do przesłania. Następnie w bloku try, wysyłany jest adres ip, po czym następuje przetworzenie obiektu oraz jego zwrócenie. Gdy nastąpi błąd zwracany jest pusty element. 

Ustawienie danych z informacjami które potrzebują edycji aby je przedstawić użytkownikowi wygląda następująco:

  1. public JsonWeatherReport JsonWeatherReport
  2. {
  3.     get { return jsonWeatherReport; }
  4.     set
  5.     {
  6.         jsonWeatherReport = value;
  7.         WeatherIcon = "http://openweathermap.org/img/w/" + jsonWeatherReport.Weather[0].Icon + ".png";
  8.         OnPropertyChanged();
  9.  
  10.         TemperatureString = string.Format("{0}°K / {1}°C", jsonWeatherReport.Main.Temp(Math.Truncate((jsonWeatherReport.Main.Temp - 273) *100) / 100));
  11.         HumidityString = string.Format("{0}%", jsonWeatherReport.Main.Humidity);
  12.         PressureString = string.Format("{0}hPa", jsonWeatherReport.Main.Pressure);
  13.         PositionString = string.Format("Long: {0} - Lon: {1}", jsonWeatherReport.Coord.Lon, jsonWeatherReport.Coord.Lat);
  14.         SunriseString = string.Format("{0}", UnixTimeStampToDateTime(jsonWeatherReport.Sys.Sunrise));
  15.         SunsetString = string.Format("{0}", UnixTimeStampToDateTime(jsonWeatherReport.Sys.Sunset));
  16.     }
  17. }

Pozostałe informacje są pobierane bezpośrednio z JSON'a.

W celu weryfikacji czy dane miasto zostało już wprowadzone ustawiana jest flaga. Gdy jest ona ustawiana wywoływane jest przesłanie zapytania do serwisu. 

  1. public class WeatherGetData
  2. {
  3.     GetWeatherApp<JsonWeatherReport> getWeatherApp = new GetWeatherApp<JsonWeatherReport>();
  4.     public async Task<JsonWeatherReport> GetWeatherDetails(string cityName)
  5.     {
  6.         var getWeatherDetails = await getWeatherApp.getWeatherJson(cityName);
  7.         return getWeatherDetails;
  8.     }
  9. }
  10.  
  11. private async Task getWeatherAsyn()
  12. {
  13.     try
  14.     {
  15.         CheckIfTaskInitializing = true;
  16.         JsonWeatherReport = await weatherGetCityData.GetWeatherDetails(cityName);
  17.     }
  18.     finally
  19.     {
  20.         CheckIfTaskInitializing = false;
  21.     }
  22. }

Cały projekt można pobrać pod tym linkiem.