Week 08

explicitný a implicitný Intent, intent filter, JSON, HttpURLConnection

Úvod

  • http://sli.do#ki-smart - anketa a otázky pre prednášajúceho
  • hackaton - (slide) - posledný novembrový víkend 25.-27.nov.2017 na tému Hack your life!
  • kultúra - (slide) - v kine úsmev bude budúci týždeň 31.okt.2017 Halloweenska párty spojená s premietaním kultového filmu Shining

Mr. Iľko

  • Dnes nás čaká nová aplik��cia na predpoveď počasia. Pracovný názov aplikácie bude Mr. Iľko.
  • Aby sme sa na začiatku nezdržiavali, mám rovno pripravenú kostru projektu, ktorú je možné stiahnuť z tejto linky. Tento projekt zahrňuje:
    • dve aktivity - SearchActivity pre vyhľadávanie a DetailActivity pre zobrazenie výsledkov Obr. 1: MrIlko SearchActivity (vľavo) a DetailActivity (vpravo)
    • niekoľko pripravených zdrojov (najmä obrázkových)
    • upravené konfiguračné súbory pre použitie knižnice ButterKnife

Intents

  • O zámeroch sme už hovorili pri baterke, kde sme ich používali na spustenie príslušnej služby. Podobným spôsobom ich použijeme aj tentokrát, keď budeme chcieť z jednej aktivity spustiť inú aktivitu. To však tiež nebude všetko, čo sa dnes o zámeroch naučíme.
  • Existujú však dva typy zámerov:
    • explicitný - špecifikuje komponent (obyčajne triedu), ktorý má byť spustený
    • implicitný - nešpecifikuje komponent, ktorý má byť spustený, ale deklaruje akciu, ktorá má byť vykonaná (komponentom inej aplikácie)
  • Ukážme si teda použitie najprv explicitného Intent-u, kedy po kliknutí na tlačidlo Search dôjde k prechodu z aktivity SearchActivity do aktivity DetailActivity.

Explicit Intent

  • Najprv teda vytvoríme metódu search(), po zavolaní ktorej dôjde k vytvoreniu explicitného intentu. Do tohto intentu pridáme aj extra údaj, ktorým bude zadané mesto pre vyhľadávanie. Následne vytvorený intent odovzdáme metóde startActivity(), ktorá zabezpečí zobrazenie aktivity DetailActivity a doručenie pripraveného intent-u priamo do nej.
  • Predtým však vytvoríme príslušnú členskú premennú, ktorá bude referenciou na objekt typu EditText. Samozrejme priamo použijeme knižnicu ButterKnife, ktorej binding inicializujeme v metóde onCreate() aktivity: ```java @BindView(R.id.edittext_search) EditText etInput;

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_search); ButterKnife.bind(this); } ```
  • Následne už vytvoríme samotnú metódu onSearchClicked(): java @OnClick(R.id.button_search) public void onSearchClicked(){ Intent intent = new Intent(this, DetailActivity.class); intent.putExtra("city", etInput.getText().toString()); startActivity(intent); }

Receiving Intent

Implicit Intent

Launcher Activity

  • Aktuálne máme k dispozícii dve aktivity - ktorá sa bude spúšťať ako prvá?
  • To je zadefinované v manifeste projektu pomocou tzv. (slide) intent filtru aktivitiy.

Intent Filter

  • An intent filter is an expression in an app’s manifest file that specifies the type of intents that the component would like to receive. For instance, by declaring an intent filter for an activity, you make it possible for other apps to directly start your activity with a certain kind of intent. A zasa naopak, ak žiadny intent filter pre aktivitu nezadeklarujete, potom túto aktivitu môžete spustiť len pomocou explicitného intentu.

Downloading Data

  • Aby aplikácia začala konečne prinášať hodnotu, potrebujeme stiahnuť živé údaje zo serveru poskytovateľa pomocou jeho REST API. Adresa pre požiadavku je v tvare (slide): http://api.openweathermap.org/data/2.5/weather?units=metric&q=CITY&APPID=KEY kde:
    • CITY predstavuje mesto, o ktorom chceme získať informácie o počasí, a
    • KEY predstavuje používateľský kľúč pre prístup k informáciám z tohto serveru. Kľúč je možé získať po zaregistrovaní sa na serveri služby OpenWeatherMap.
  • Jednoducho si môžeme otestovať toto spojenie aj pomocou príkazu curl z OS Linux (slide):

  • Odpoveď zo servera dostaneme vo formáte JSON a našou úlohou bude získať tieto údaje v rámci našej Android aplikácie.

Class HttpURLConnection

Exception android.os.NetworkOnMainThreadException

  • K tejto výnimke dôjde vtedy, ak sa aplikácia snaží vykonávať sieťové operácie v hlavnom vlákne.
  • Po spustení aplikácie totiž systém vytvorí vlákno, v ktorom je aplikácia vykonávaná. Je označovaná ako “main”, teda hlavné vlákno. Toto vlákno zodpovedá za úlohy súvisiace s používateľským rozhraním. Ak by sme v hlavnom vlákne spustili úlohu, ktorej vykonanie trvá príliš dlho, aplikácia sa bude javiť ako nefunkčná aj napriek tomu, že vlastne len čaká na dokončenie tejto dlhotrvajúcej úlohy.
  • Dlhotrvajúca úloha nemusí vždy trvať dlho, ale jej dĺžka môže byť neurčitá. Medzi takéto úlohy môžeme zaradiť napríklad sieťové operácie alebo databázové operácie.
  • Dlhotrvajúcu úlohu si môžeme vyskúšať veľmi jednoducho - stačí, ak po stlačení tlačidla Search spustíme toto:

  • Po kliknutí na tlačidlo sa aplikácia zahryzne a nereaguje na žiadne ďalšie podnety, resp. vstupy. Z tohto stavu nás vyseká už len Android sám tým, že sa ponúkne nereagujúcu aktivitu vypnúť.
  • V prípade, že potrebujeme spustiť nejakú dlhotrvajúcu operáciu, ktorá nemusí byť nutne sieťová, musíme to urobiť v samostatnom vlákne. Na možnosti, ktoré ponúka Android pre takéto úlohy sa následne pozrieme.

Threading in Android

  • Čo je to vlákno? Definícia z wikipédie. (slide)
  • Ilustrácia o spolubývajúcich (viaceré vlákna) z jednej izby (proces) čítajúcich jedny skriptá (zdieľaná pamäť) - v jednom čase môže ku skriptám (zdieľaným údajom) pristupovať len jeden z nich. (Stack Overflow)
  • V Android-e máme niekoľko možností, ako môžeme niektorú (dlhotrvajúcu) úlohu vykonávať v samostatnom vlákne, resp. na pozadí.
  • Jedno z nich je vytvoriť štandardné Javové vlákno (slide). Nie je to však odporúčaný postup, nakoľko tu vzniká niekoľko problémov, ako napr.: synchronizácia s hlavnou triedou, ak z vlákna budete chcieť vrátiť údaje späť UI.
  • Android poskytuje alternatívny prístup ku práci s vláknami cez android.os.Handler alebo AsyncTask.
  • Trieda Handler sa používa ako jednoduchý komunikačný kanál medzi vláknami. Ani ona nám však nepomôže, ak chceme v rámci vlákna spracovávať sieťovú komunikáciu. Tu musíme použiť triedu AsyncTask.

Class AsyncTask

  • AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.
  • Trieda AsyncTask je abstraktná. Ak ju chceme použiť, musíme z nej vytvoriť podtriedu (slide). Používa generiká a premenlivý počet parametrov.
  • Význam jednotlivých parametrov je nasledovný:
    • Params - typ parametrov odoslaných úlohe pri spustení.
    • Progress - the type of the progress units published during the background computation.
    • Result - the type of the result of the background computation. Not all types are always used by an asynchronous task. To mark a type as unused, simply use the type Void.
  • When an asynchronous task is executed, the task goes through 4 steps:
    1. onPreExecute() - invoked on the UI thread before the task is executed. This step is normally used to setup the task, for instance by showing a progress bar in the user interface.
    2. doInBackground(Params...) - invoked on the background thread immediately after onPreExecute() finishes executing. This step is used to perform background computation that can take a long time. The parameters of the asynchronous task are passed to this step. The result of the computation must be returned by this step and will be passed back to the last step. This step can also use publishProgress(Progress...) to publish one or more units of progress. These values are published on the UI thread, in the onProgressUpdate(Progress...) step.
    3. onProgressUpdate(Progress...) - invoked on the UI thread after a call to publishProgress(Progress...). The timing of the execution is undefined. This method is used to display any form of progress in the user interface while the background computation is still executing. For instance, it can be used to animate a progress bar or show logs in a text field.
    4. onPostExecute(Result) - invoked on the UI thread after the background computation finishes. The result of the background computation is passed to this step as a parameter.
  • Vzhľadom na uvedené teda vytvoríme vlastnú triedu s názvom ForecastTask, ktorá bude potomkom triedy AsyncTask

Class ForecastTask

  • Začneme identifikáciou generických typov:
    • Akého typu budú vstupné údaje úlohy? Čo bude vstupom tejto úlohy? (názov mesta, takže String)
    • Akého typu bude výstup z úlohy? Čo táto úloha vráti? (z webu providera príde JSON objekt serializovaný ako reťazec, ktorý v Jave vieme reprezentovať triedou JSONObject. Tento údajový typ však nie je vhodný, nakoľko ho nemôžeme zabaliť do intentu. Preto bude nakoniec lepšie použiť na prenos údajový typ String)
    • Budeme potrebovať nejakým spôsobom reprezentovať priebeh operácie? (max. tak točiacim sa kolieskom, takže tento typ zostane prázdny (Void))
  • Prázdna trieda bude vyzerať teda nasledovne:

  • Následne vytvoríme konštruktor tridy, ktorému predáme referenciu na aktivitu, v ktorej bola inštancia objektu AsyncTask vytvorená:

  • Nakoniec vytvoríme metódu doInBackground(), ktorá bude spustená v samostatnom vlákne a zabezpečí stiahnutie údajov o počasí zo služby OpenWeatherMap: ```java @Override protected String doInBackground(String… cities) { this.city = cities[0];

      try {
              URL url = new URL("http://api.openweathermap.org/data/2.5/weather?units=metric&APPID=3718d7f90e7b081ca8f46aa4305c05ea&q=" + city);
              HttpURLConnection connection = (HttpURLConnection) url.openConnection();
              connection.connect();
        
              Log.i(TAG, String.format("Connecting to %s", url.toString()));
              Log.i(TAG, String.format("HTTP Status Code: %d", connection.getResponseCode()));
        
              if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                  return null;
              }
        
              BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
              StringBuilder stringBuilder = new StringBuilder();
        
              String line;
              while ((line = bufferedReader.readLine()) != null) {
                  stringBuilder.append(line + 'n');
              }
        
              Log.i(TAG, String.format("GET: %s", stringBuilder.toString()));
        
              return new stringBuilder.toString();
          } catch (MalformedURLException e) {
              e.printStackTrace();
          } catch (IOException e) {
              e.printStackTrace();
          }
        
          return null;

    } ```

Processing Downloaded Data

Progress Bar

Complete Source code

Celkový kód bude vyzerať nasledovne:

Presentation of data

  • Následne už treba iba rozbaliť prijatý intent v metóde onCreate() a pomocou získaných hodnôt naplniť aktivitu. Nižšie sa nachádza celkový výpis triedy DetailViewActivity:
  • Ak ste postupovali správne, načítané údaje sa vám zobrazia: Obr. 2: Zobrazenie načítaných údajov

Implicit Intent

Additional Resources

  • slides - prezentácia z tohto týždňa
  • Intents and Intent Filters
  • JSON - Homepage of JSON (JavaScript Object Notation) - the lightweight data-interchange format.
  • Temps - A simple but smart weather app. (odkukaný layout pre Mr Iľka)
  • OpenWeatherMap REST API - dokumentácia API služby openweathermap.org
  • Threads - Android SDK dokumentácia ku vláknam
  • AsyncTask - AsyncTask enables proper and easy use of the UI thread. This class allows you to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers.