piątek, 3 stycznia 2020

Java - Android - Aplikacja pogodowa

W tym poście chciałbym opisać krótką aplikację pozwalającą na odczytanie pogody z serwisu OpenWeatherMap.


Aby pobrać pogodę dla danego miasta należy wysłać odpowiedni string, w którym zostanie wpisana nazwa miasta oraz API użytkownika.

Pobranie i obsłużenie komunikatu od serwera wykonywane jest za pomocą AsyncTask.

Całość wykonywana jest przez cztery elementy:

  • onPreExecute;
  • doInBackground;
  • onProgressUpdate;
  • onPostExecute;

Jako pierwsze wywołana jest funkcji onPreExecute. Wywołane na samym początku zanim rozpocznie się obsługa zdarzenia. Wykorzystywana do ustawienia widoczności poszczególnych elementów na ekranie:

  1. protected void onPreExecute() {
  2.     super.onPreExecute();
  3.     SetMainWindowVisibility_StartSettings();
  4. }
  5. private void SetMainWindowVisibility_StartSettings()
  6. {
  7.     findViewById(R.id.loader).setVisibility(View.VISIBLE);
  8.     findViewById(R.id.mainContainer).setVisibility(View.GONE);
  9.     findViewById(R.id.errorText).setVisibility(View.GONE);
  10. }


Wywołanie super.onPreExecute nadpisuje domyślne wystąpienie metody na tą wykorzystaną w funkcji.

Wykonanie metody onProgressUpdate zostawiłem bez nadpisywania funkcji. Wykorzystywana jest on podczas wykonywania metody doInBackground w celu np. wyświetlenia paska postępu. W tym przypadku pobranie danych z serwisu jest bardzo szybkie i nie ma potrzeby wykonywania takich funkcji.

Ostatnim krokiem jest wywołanie metody onPostExecute. Tutaj następuje obrobienie otrzymanych danych wraz z wyświetleniem ich na ekranie.

  1. protected void onPostExecute(String result) {
  2.     try {
  3.         JSONObject jsonObj = new JSONObject(result);
  4.         if(CheckIfLoadingErrorOccurs(jsonObj.getLong("cod"))) {
  5.             //If error when receiving data it loads previous window
  6.             SetMainWindowVisibility_ProperData();
  7.             return;
  8.         }
  9.         JSONObject main = jsonObj.getJSONObject("main");
  10.         JSONObject sys = jsonObj.getJSONObject("sys");
  11.         JSONObject wind = jsonObj.getJSONObject("wind");
  12.         JSONObject coords = jsonObj.getJSONObject("coord");
  13.         JSONObject clouds = jsonObj.getJSONObject("clouds");
  14.         JSONObject weather = jsonObj.getJSONArray("weather").getJSONObject(0);
  15.         addressTxt.setText(jsonObj.getString("name") + ", " + sys.getString("country"));
  16.         updated_atTxt.setText("Date: " + new SimpleDateFormat("dd/MM/yyyy hh:mm a"Locale.ENGLISH).format(new Date(jsonObj.getLong("dt") * 1000)));
  17.         statusTxt.setText(weather.getString("description").toUpperCase());
  18.         tempTxt.setText(main.getString("temp") + "°C");
  19.         temp_minTxt.setText("Min: " + main.getString("temp_min") + "°C");
  20.         temp_maxTxt.setText("Max: " + main.getString("temp_max") + "°C");
  21.         feelsLikeTxt_Val.setText("Temp Fells Like: " + main.getString("feels_like") + "°C");
  22.         sunriseTxt_Val.setText("Sunrise: " + new SimpleDateFormat("hh:mm a"Locale.ENGLISH).format(new Date(sys.getLong("sunrise") * 1000)));
  23.         sunsetTxt_Val.setText("Sunset: " + new SimpleDateFormat("hh:mm a"Locale.ENGLISH).format(new Date(sys.getLong("sunset") * 1000)));
  24.         windTxt_Val.setText("Wind: " + wind.getString("speed"));
  25.         pressureTxt_Val.setText(main.getString("pressure") + " hPa");
  26.         humidityTxt_Val.setText(main.getString("humidity") + "%");
  27.         latTxt_Val.setText("Latitude: " + coords.getString("lat"));
  28.         lonTxt_Val.setText("Longitude: " + coords.getString("lon"));
  29.         cloudinessTxt_Val.setText("Cloudiness: " + clouds.getLong("all") + "%");
  30.         SetMainWindowVisibility_ProperData();
  31.     }
  32.     catch (JSONException e)
  33.     {
  34.         SetMainWindowVisibility_Error();
  35.     }
  36. }

Obsługa JSON'a musi być obudowana blokiem try{} catch{}. Pobrane dane są zapisywane bezpośrednio do kontrolek.

Na samym początku po pobraniu danych sprawdzony zostaje kod odpowiedzi ze strony. Tak aby nie rozpoczynać obrabiania danych gdy wystąpi błąd wynikający np. z wpisania błędnej nazwy miasta.

  1. @org.jetbrains.annotations.Contract(value = "null -> false", pure = true)
  2. private boolean CheckIfLoadingErrorOccurs(Long pageLoadingStatus)
  3. {
  4.     if(pageLoadingStatus == 404) {
  5.         return true;
  6.     }
  7.     return false;
  8. }

Po pobraniu danych z serwera w postaci formatu JSON otrzymuje się następujące informacje:

  1. {"coord":{
  2.     "lon":19.94,
  3.     "lat":50.06},
  4. "weather":[
  5.     {"id":803,
  6.     "main":"Clouds",
  7.     "description":"broken clouds",
  8.     "icon":"04n"}],
  9. "base":"stations",
  10. "main":{
  11.         "temp":1.2,
  12.         "feels_like":-3.91,
  13.         "temp_min":0,
  14.         "temp_max":2.22,
  15.         "pressure":1026,
  16.         "humidity":80},
  17. "visibility":10000,
  18. "wind":{
  19.         "speed":4.1,
  20.         "deg":320},
  21. "clouds":{"all":75},
  22. "dt":1577484733,
  23. "sys":{
  24.     "type":1,
  25.     "id":1701,
  26.     "country":"PL",
  27.     "sunrise":1577428713,
  28.     "sunset":1577457804},
  29. "timezone":3600,
  30. "id":3094802,
  31. "name":"Krakow",
  32. "cod":200}

Dane są obrabiane w funkcji asynchronicznej. Obsługa formatu JSON wykonywana jest przez interfejs JsonObject. 

W drugim kroku (natychmiast po zakończeniu onPreExecute) wykonuje pobranie danych w formacie JSON. Aby to osiągnąć należy przesłać nazwę miasta, skrót od nazwy kraju, oraz klucz dostępu wygenerowany po utworzeniu konta na stronie:

  1. protected String doInBackground(String... args) {
  2.     String receiveDataString = HttpRequest.excuteGet
  3.            ("https://api.openweathermap.org/data/2.5/weather?q=" +
  4.                  CITY_NAME + "," + COUNTRY_SHORT +
  5.                  "&units=metric&appid=" + API);
  6.     return receiveDataString;
  7. }

Deklarowane wartości ustawiłem jako domyślne:

  1. String CITY_NAME = "gdansk";
  2. String COUNTRY_SHORT = "pl";
  3. final String API = "API_KEY";

Zdefiniowałem je w taki sposób aby zaraz po uruchomieniu możliwe było odczytanie pogody dla interesującego mnie miejsca. W celu zmiany miasta i/lub kraju wykorzystałem dwie kontrolki EditTxt oraz przycisk. Po jego wciśnięciu następuje zapisanie nowych wartości do zmiennych oraz wywołanie zdarzenia asynchronicznego do pobrania danych z serwera:

  1. public void loadDataBtnClick(View view)
  2. {
  3.     CITY_NAME = city_EditTxt.getText().toString();
  4.     COUNTRY_SHORT = country_EditTxt.getText().toString();
  5.     new getWeatherAsyncTask().execute();
  6. }

Layout całej aplikacji prezentuje się w następujący sposób:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.    android:orientation="vertical"
  4.    android:layout_width="match_parent"
  5.    android:layout_height="match_parent"
  6.    android:padding="5dp"
  7.    android:background="@drawable/image">
  8.     <RelativeLayout
  9.        android:id="@+id/mainContainer"
  10.        android:layout_width="match_parent"
  11.        android:layout_height="match_parent"
  12.        android:visibility="visible">
  13.         <LinearLayout
  14.            android:id="@+id/addressContainer"
  15.            android:layout_width="match_parent"
  16.            android:layout_height="wrap_content"
  17.            android:gravity="center"
  18.            android:orientation="vertical">
  19.             <LinearLayout
  20.                android:layout_width="match_parent"
  21.                android:layout_height="wrap_content"
  22.                android:gravity="center"
  23.                android:orientation="horizontal">
  24.                 <EditText
  25.                    android:id="@+id/cityEditTxt_Control"
  26.                    android:layout_width="wrap_content"
  27.                    android:layout_height="wrap_content"
  28.                    android:hint="city"
  29.                    android:textColor="#ffffff"
  30.                    android:textColorHint="#808080"
  31.                    android:tooltipText="Write city name" />
  32.                 <Space
  33.                    android:layout_width="50dp"
  34.                    android:layout_height="wrap_content" />
  35.                 <EditText
  36.                    android:id="@+id/countryEditTxt_Control"
  37.                    android:layout_width="wrap_content"
  38.                    android:layout_height="wrap_content"
  39.                    android:hint="pl"
  40.                    android:textColor="#ffffff"
  41.                    android:textColorHint="#808080"
  42.                    android:tooltipText="Write country shortcut" />
  43.             </LinearLayout>
  44.             <Button
  45.                android:id="@+id/loadDataBtnClick"
  46.                android:layout_width="match_parent"
  47.                android:layout_height="wrap_content"
  48.                android:background="@android:color/transparent"
  49.                android:onClick="loadDataBtnClick"
  50.                android:text="Load Data For City"
  51.                android:textColor="#ffffff" />
  52.             <Space
  53.                android:layout_width="wrap_content"
  54.                android:layout_height="2dp" />
  55.             <TextView
  56.                android:id="@+id/address"
  57.                android:layout_width="wrap_content"
  58.                android:layout_height="wrap_content"
  59.                android:text="CITY, CUNTRY_SHORT"
  60.                android:textColor="#ffffff"
  61.                android:textSize="24dp" />
  62.             <Space
  63.                android:layout_width="wrap_content"
  64.                android:layout_height="2dp" />
  65.             <TextView
  66.                android:id="@+id/updated_at"
  67.                android:layout_width="wrap_content"
  68.                android:layout_height="wrap_content"
  69.                android:text="20 April 2012, 20:08 PM"
  70.                android:textColor="#ffffff"
  71.                android:textSize="14dp" />
  72.             <Space
  73.                android:layout_width="wrap_content"
  74.                android:layout_height="2dp" />
  75.         </LinearLayout>
  76.         <LinearLayout
  77.            android:id="@+id/overviewContainer"
  78.            android:layout_width="match_parent"
  79.            android:layout_height="wrap_content"
  80.            android:layout_alignParentBottom="@+id/addressContainer"
  81.            android:layout_centerVertical="true"
  82.            android:orientation="vertical">
  83.             <Space
  84.                android:layout_width="wrap_content"
  85.                android:layout_height="60dp" />
  86.             <LinearLayout
  87.                android:layout_width="match_parent"
  88.                android:layout_height="wrap_content"
  89.                android:gravity="center"
  90.                android:layout_alignParentBottom="@+id/addressContainer"
  91.                android:orientation="horizontal">
  92.                 <TextView
  93.                    android:id="@+id/temp"
  94.                    android:layout_width="wrap_content"
  95.                    android:layout_height="wrap_content"
  96.                    android:layout_gravity="center"
  97.                    android:fontFamily="sans-serif-thin"
  98.                    android:text="29°C"
  99.                    android:textColor="#ffffff"
  100.                    android:textSize="90dp"
  101.                    android:textStyle="bold" />
  102.             </LinearLayout>
  103.             <LinearLayout
  104.                android:layout_width="match_parent"
  105.                android:layout_height="wrap_content"
  106.                android:layout_alignParentBottom="@+id/overviewContainer"
  107.                android:gravity="center"
  108.                android:orientation="horizontal">
  109.                 <TextView
  110.                    android:id="@+id/temp_min"
  111.                    android:layout_width="wrap_content"
  112.                    android:layout_height="wrap_content"
  113.                    android:text="Min Temp: 05:05 AM"
  114.                    android:textColor="#ffffff" />
  115.                 <Space
  116.                    android:layout_width="50dp"
  117.                    android:layout_height="wrap_content" />
  118.                 <TextView
  119.                    android:id="@+id/temp_max"
  120.                    android:layout_width="wrap_content"
  121.                    android:layout_height="wrap_content"
  122.                    android:text="Max Temp: 05:05 PM"
  123.                    android:textColor="#ffffff" />
  124.             </LinearLayout>
  125.             <Space
  126.                android:layout_width="wrap_content"
  127.                android:layout_height="20dp" />
  128.             <TextView
  129.                android:id="@+id/status"
  130.                android:layout_width="wrap_content"
  131.                android:layout_height="wrap_content"
  132.                android:layout_gravity="center"
  133.                android:text="Clear Sky"
  134.                android:textColor="#ffffff"
  135.                android:textSize="18dp" />
  136.             <TextView
  137.                android:id="@+id/lonTxtView"
  138.                android:layout_width="wrap_content"
  139.                android:layout_height="wrap_content"
  140.                android:layout_gravity="center"
  141.                android:text="Longitude: "
  142.                android:textColor="#ffffff"
  143.                android:textSize="22dp" />
  144.             <TextView
  145.                android:id="@+id/latTxtView"
  146.                android:layout_width="wrap_content"
  147.                android:layout_height="wrap_content"
  148.                android:layout_gravity="center"
  149.                android:text="Latitude: "
  150.                android:textColor="#ffffff"
  151.                android:textSize="22dp" />
  152.             <TextView
  153.                android:id="@+id/sunriseTxtView"
  154.                android:layout_width="wrap_content"
  155.                android:layout_height="wrap_content"
  156.                android:layout_gravity="center"
  157.                android:text="Sunrise: "
  158.                android:textColor="#ffffff"
  159.                android:textSize="22dp" />
  160.             <TextView
  161.                android:id="@+id/sunsetTxtView"
  162.                android:layout_width="wrap_content"
  163.                android:layout_height="wrap_content"
  164.                android:layout_gravity="center"
  165.                android:text="Sunset: "
  166.                android:textColor="#ffffff"
  167.                android:textSize="22dp" />
  168.             <TextView
  169.                android:id="@+id/feelsLikeTxtView"
  170.                android:layout_width="wrap_content"
  171.                android:layout_height="wrap_content"
  172.                android:layout_gravity="center"
  173.                android:text="Fells Like: "
  174.                android:textColor="#ffffff"
  175.                android:textSize="22dp" />
  176.             <TextView
  177.                android:id="@+id/windTxtView"
  178.                android:layout_width="wrap_content"
  179.                android:layout_height="wrap_content"
  180.                android:layout_gravity="center"
  181.                android:text="Wind: "
  182.                android:textColor="#ffffff"
  183.                android:textSize="22dp" />
  184.             <TextView
  185.                android:id="@+id/cloudinessTxtView"
  186.                android:layout_width="wrap_content"
  187.                android:layout_height="wrap_content"
  188.                android:layout_gravity="center"
  189.                android:text="Cloudiness: "
  190.                android:textColor="#ffffff"
  191.                android:textSize="22dp" />
  192.             <TextView
  193.                android:id="@+id/pressureTxtView"
  194.                android:layout_width="wrap_content"
  195.                android:layout_height="wrap_content"
  196.                android:layout_gravity="center"
  197.                android:text="Pressure: "
  198.                android:textColor="#ffffff"
  199.                android:textSize="22dp" />
  200.             <TextView
  201.                android:id="@+id/humidityTxtView"
  202.                android:layout_width="wrap_content"
  203.                android:layout_height="wrap_content"
  204.                android:layout_gravity="center"
  205.                android:text="Humidity: "
  206.                android:textColor="#ffffff"
  207.                android:textSize="22dp" />
  208.         </LinearLayout>
  209.     </RelativeLayout>
  210.     <ProgressBar android:id="@+id/loader"
  211.        android:layout_width="wrap_content"
  212.        android:layout_height="wrap_content"
  213.        android:layout_centerInParent="true"
  214.        android:visibility="gone"/>
  215.     <TextView android:id="@+id/errorText"
  216.        android:layout_width="wrap_content"
  217.        android:layout_height="wrap_content"
  218.        android:layout_centerInParent="true"
  219.        android:visibility="gone"
  220.        android:text="Loading Error"/>
  221. </RelativeLayout>

Poniżej screen aplikacji: