Week 09

kontrola pripojenia, broadcast, BroadcastReceiver, Activity Manager, ListView

Mr. Iľko - The State of the Art

  • Program na zisťovanie počasia na základe zadania.
  • Pomocou explicitných zámerov vieme prechádzať medzi aktivitami a pomocou implicitných zámerov vieme spúšťať doplnkovú funkcionalitu.
  • Údaje z externej služby sme sťahovali pomocou inštancie triedy HttpURLConnection.
  • S výsledkom, ktorý sme dostali vo forme JSON objektu sme spracovali pomocou tried JSONObject a JSONArray.

Internet Connection Unavailable

  • (slide) Čo ak sa stane, že máme aplikáciu, ktorá vyžaduje sieťovú komunikáciu, ale sieť nie je dostupná? Ako sa dá takémuto problému predísť? Na to sa teraz pozrieme.

  • Skontrolovať pripojenie je možné pomocou nasledovného fragmentu kódu (slide):

    private boolean isNetworkAvailable() {
        ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);     
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();     
        return activeNetworkInfo != null && activeNetworkInfo.isConnected(); 
    }
  • Metóda getActiveNetworkInfo() môže vrátiť aj hodnotu null, ak nie je k dispozícii žiadne aktívne sieťové rozhranie (napr. zariadenie je v tzv. Airplane režime).

  • Poprípade je možné overiť každé jedno dostupné sieťové rozhranie zvlášť, či má alebo nemá pripojenie do internetu:

    private boolean hasNetworkConnection() {
        boolean hasConnectedWifi = false;
        boolean hasConnectedMobile = false;
        ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo[] netInfo = cm.getAllNetworkInfo();
        for (NetworkInfo ni : netInfo) {
            if (ni.getTypeName().equalsIgnoreCase("WIFI"))
                if (ni.isConnected())
                    hasConnectedWifi = true;
                if (ni.getTypeName().equalsIgnoreCase("MOBILE"))
                    if (ni.isConnected())
                        hasConnectedMobile = true;     
        }     
        return hasConnectedWifi || hasConnectedMobile; 
    }
  • Aby to však bolo možné, potrebujeme najskôr správne povolenie: xml <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

  • Otázkou však stále zostáva aj umiestnenie daného kódu - kde budeme zisťovať, či je pripojenie k internetu dostupné alebo nie?

  • Pravdepodobne by to overenie malo prebehnúť pred samotným spojením a vytvorením AsyncTask-u, niekde po kliknutí na tlačidlo Search.

Broadcast Receivers

  • Pozor však na to, že: Note that having an active network interface doesn’t guarantee that a particular networked service is available. Network issues, server downtime, low signal, captive portals, content filters and the like can all prevent your app from reaching a server. For instance you can’t tell for sure if your app can reach Twitter until you receive a valid response from the Twitter service.

  • Toto môžete rovnako zažiť napr. aj u nás v škole - ak sa so svojou aplikáciou budete snažiť pripojiť do internetu alebo vo všeobecnosti na špecifickú službu, ktorá nebeží na vybraných portoch na wifi sieti TUNET-guest, máte smolu a pokiaľ nie ste na túto situáciu pripravení, vaša aplikácia zhavaruje…

  • Tento prístup bude síce fungovať tak, ako má, ale nie vždy je vyhovujúci. Občas totiž potrebujeme zareagovať okamžite, ak dôjde k zmene stavu sieťového pripojenia. Akým spôsobom je teda možné byť upozornený automaticky na to, že sieťové spojenie sa stratilo alebo sa naopak obnovilo?

  • Za týmto účelom existuje mechanizmus tzv. Broadcast Receiver-ov (slide).

  • A broadcast receiver (short receiver) is an Android component which allows you to register for system or application events. All registered receivers for an event are notified by the Android runtime once this event happens.

  • Broadcast receiver v Androide predstavuje klasickú implementáciu mechanizmu publish-subscribe (slide), resp. implementáciu návrhového vzoru pozorovateľ (observer). Ako tento mechanizmus pracuje? Príklad - Shrek 2 (slide) vs nočák.

  • Aj každý z nás je v princípe nejakým typom BR - počúvame na meno, na špecifické pokriky/oznamy.

  • Príkladom takej udalosti môže byť systémová udalosť ACTION_BOOT_COMPLETED, ktorá nastane len raz a to vtedy, keď systém Android ukončil proces bootovania (štartovania) systému.

Vytvorenie a zaregistrovanie Broadcast Receivera

  • Vlastný BR sa vytvára ako nová trieda, ktorá je potomkom triedy BroadcastReceiver.

  • Jednoduchý BR teda môže vyzerať nasledovne:

    public class HelloReceiver extends BroadcastReceiver {     
        private static final String TAG = "HelloReceiver";      
    
        @Override     
        public void onReceive(Context context, Intent intent) {
            String name = intent.getStringExtra("name");
            Log.i(TAG, "Hello " + name);
        } 
    }
  • Broadcast receiver je následne potrebné zaregistrovať v súbore AndroidManifest.xml. Zaregistrovať BR je však možné aj dynamicky priamo v kóde. O tom však neskôr.

  • To, pre akú udalosť sa BR registruje, definuje element <intent-filter> a kompletný tvar registrácie BR v manifeste potom bude vyzerať nasledovne: xml <receiver android:name=".HelloReceiver" android:enabled="true" android:exported="true" > <intent-filter> <action android:name="sk.tuke.smart.mrilko.SAY_HELLO"/> </intent-filter> </receiver>

  • Ak dôjde k registrovanej udalosti, systém Android zavolá metódu onReceive() tejto triedy.

  • Pre otestovanie broadcastu môžeme využiť nástroj Android Monitor z príkazového riadku (slide):

    $ adb shell 
    # am broadcast -a sk.tuke.smart.mrilko.SAY_HELLO --es name juraj
  • Ak chceme poslať broadcast do systému z programu, vytvoríme za týmo účelom nový Intent:

    Intent intent = new Intent(); 
    intent.setAction("sk.tuke.smart.mrilko.SAY_HELLO"); 
    intent.putExtra("name", "juraj"); 
    sendBroadcast(intent);

Updating UI from Broadcast Receiver

  • Poďme ale späť k našej aplikácii. My chceme byť pripravení na moment, kedy dôjde k strate sieťového spojenia. A keď k tomu dôjde, budeme chcieť vhodným spôsobom zareagovať v používateľskom rozhraní aplikácie - napr. toastom s vhodnou správou a deaktivovaním tlačidla, aby naň nebolo možné kliknúť.

  • Prvá vec, ktorá nás bude zaujímať, je udalosť, pre ktorú potrebujeme vytvoriť BR. BR teda vytvoríme pre filtrovanie udalosti android.net.conn.CONNECTIVITY_CHANGE.

  • Druhá vec, ktorá je dôležitejšia, je, akým spôsobom bude BR vedieť aktualizovať používateľské rozhranie aplikácie v prípade, ak obdrží správu o zmene stavu spojenia. Je totiž dôležité si uvedomiť, že náš BR je registrovaný v celom systéme a naša aktivita, v ktorej chceme na zmenu reagovať, nemusí byť ani spustená.

  • BR sa dajú používať rozličnými spôsobmi a jedným z nich je definovanie BR ako tzv. vnútornej triedy (inner class). Tento prístup je veľmi vhodný pre náš prípad, pretože budeme chcieť mať celý BR pod kontrolou a teda:

    • budeme ho chcieť zapnúť, keď sa naša aktivita spustí
    • budeme ho chcieť vypnúť vždy vtedy, keď aktivita prestane byť viditeľná
    • budeme chcieť priamo z neho volať niektorú metódu aktivity
  • V tomto prípade sa teda broadcast receiver nestane súčasťou manifestu, ale zaregistrujeme ako aj odhlásime ho priamo v kóde.

  • Vytvoríme teda kostru vnútornej (anonymnej) triedy v triede SearchActivity, ktorá bude reprezentovať práve uvedený broadcast receiver:

    private BroadcastReceiver connChangeReceiver = new BroadcastReceiver() {     
        public static final String TAG = "ConnChangeReceiver";      
        @Override     
        public void onReceive(Context context, Intent intent) {         
            Log.i(TAG, "Connecvitivy has changed");
        } 
    };
  • Následne potrebujeme opäť zabezpečiť, aby bol tento broadcast receiver dostupný vždy vtedy, keď je aktivita viditeľná. To znamená, že ho potrebujeme zaregistrovať vždy vtedy, keď aktivita prechádza zo stavu neviditeľná do stavu viditeľná a zasa naopak - potrebujeme ho odhlásiť, keď prechádza zo stavu viditeľná do stavu neviditeľná.

  • BR zaregistrujeme v metóde onResume():

    @Override public void onResume() {
        super.onResume();
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        registerReceiver(this.connChangeReceiver, filter); 
    }
  • Broadcast receiver odhlásime v metóde onPause():

    @Override public void onPause() {
        super.onPause();
        unregisterReceiver(this.connChangeReceiver); 
    }
  • Následne môžeme túto implementáciu overiť vytvorením udalosti android.net.conn.CONNECTIVITY_CHANGE:

    am broadcast -a android.net.conn.CONNECTIVITY_CHANGE

    až na to, že emulátor sa reštartne :-(

  • Otestovať funkcionalitu môžeme buď prepnutím telefónu do režimu Airplaine ručne alebo z príkazového riadku. Zapnúť ho môžeme takto

    settings put global airplane_mode_on 1 am broadcast -a android.intent.action.AIRPLANE_MODE --ez state true  

    a vypnúť zasa takto

    settings put global airplane_mode_on 0 am broadcast -a android.intent.action.AIRPLANE_MODE --ez state false

    Funkcionalitu je však potrebné otestovať aj vtedy, ak aktivita nie je viditeľná.

  • Aktuálne už nezostáva nič iné, len poskladať kúsky dokopy - pomocou knižnice ButterKnife si bind-neme tlačidlo, ku ktorému následne môžeme pristupovať z vnútra metódy onReceive() anonymnej triedy BroadcastReceiver. Nad tlačidlom následne zavoláme metódu setEnabled(), pomocou ktorej zapneme, resp. vypneme tlačidlo na základe aktuálneho stavu pripojenia zariadenia do internetu. Výsledná implementácia bude vyzerať takto:

    // bind view
    @BindView(R.id.searchButton) Button button;
    
    // method in anonymous class BroadcastReceiver
    @Override
    public void onReceive(Context context, Intent intent) {
        Log.i(TAG, "Broadcast received");
        button.setEnabled(isNetworkAvailable());
        Toast.makeText(getApplicationContext(), "Connection has been changed", Toast.LENGTH_SHORT).show();
    }

Conclusion

  • Dnes sme si ukázali ďalší zo stavebných kameňov Android aplikácií - Broadcast Receiver.

  • Nabudúce sa pokúsime stiahnuť dáta pomocou knižnice Retrofit a ukážeme si, ako zobraziť údaje v zozname pomocou pohľadu ListView.

Additional Resources