Week 03

AlertDialog, zmena orientácie aktivity, životný cyklus aktivity, verzia SDK

Úvod

  • (slide) http://sli.do#ki-smart - anketa a otázky pre prednášajúceho

  • (slide) https://join.slack.com/t/ki-smart/signup - slack pre rýchlu komunikáciu

  • (slide) hackaton
    • registrácia na at&t hackathon je otvorená
    • o téme T-Systems hackathonu sa dá hlasovať
    • Časť 118 CZ Podcastu je venovaná firemným hackatonom. Trvá niečo vyše 30 minút a zúčastnení sa rozprávajú o tom, ako hackatony fungujú vo firemnom prostredí, čo im prinášajú a či je to vlastne celé dobré ;)
  • (slide) coderfest - už budúci utorok, zaregistrujte sa čo najskôr!

  • (slide) oss víkend
    • o Linuxe, ale nie len o ňom
    • ak ste sa stretli s niečím (nie niekým) zaujímavým, príďte o tom porozprávať - je to skúsenosť prísť porozprávať pred nie veľmi veľkú skupinu ľudí
  • (slide) prvý šprint
    • webová reprezentácia šprintu pribudne zajtra a bude pribúdať priebežne - agile
    • user stories
    • diskutujte!

Torch Application - Part III

  • (slide)

  • Zatiaľ sme spravili len kostru aplikácie, aj keď sa na obrazovke zariadenia zobrazuje všetko potrebné. Zariadenie však nesvieti. A keď zariadenie nesvieti, nebude ani žiadny profit. Dnes sa teda pozrieme na to, ako naše zariadenie rozsvietiť. A samozrejme - kopec vecí okolo.

Adding Dialog

  • Minule sme pri zistení, že zariadenie neobsahuje blesk, zavreli aktivitu a o vzniknutej situácii sme používateľa informovali zobrazením Toast-u. Sú však samozrejme aj iné sp��soby, ako je možné používateľa oboznámiť. (slide) Jednou z nich je aj vytvoriť dialógové okno, ktoré zmizne až po tom, čo na ňom používateľ stlačí príslušné tlačidlo.

  • zobrazenie dialógového okna s potrebnou informáciou môže vyzerať napr. takto:

    
        AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
        
        builder.setTitle("Error")
               .setMessage("Sorry, your device doesn't support flash light!")
               .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int which) {
                        // closing the application
                        finish();
                    }
               });
        AlertDialog dialog = builder.create();
        dialog.show();
        

    Upozornenie

    Pozor však na správne usporiadanie kódu! Po zobrazení dialógu nezabudnite ukončiť metódu onCreate() zavolaním príkazu return. V opačnom prípade dôjde k pokusu o použitie zdroja - kamery.

Changing orientation

Activity Lifecycle

  • S našou aplikáciou však máme stále jeden veľký problém - po zmene orientácie dôjde k resetnutiu stavu aktivity (spustí sa nanovo). To nie je veľmi dobré, nakoľko môžeme stratiť veľmi dôležité údaje (napr. výsledok športovej činnosti po 30min, stav rozohratej hry, …).

  • Problém by bolo možné vyriešiť zakázaním zmeny orientácie aplikácie. Toto je možné zabezpečiť pridaním nasledujúceho atribútu pre potrebnú aktivitu v súbore s manifestom aplikácie:

  • Aby sme lepšie porozumeli tomu, prečo je to vlastne tak, pozrime sa na životný cyklus aktivity (slide). Aktivita sa teda môže nachádzať v jednom z týchto stavov:

    • starting - V tomto momente dochádza k spusteniu aktivity a teda jej vytvoreniu ako objektu. Volajú sa postupne metódy onCreate(), onStart() a onResume()
    • running - Stav, v ktorom aplikácia vykonáva svoju činnosť v tzv. UI vlákne - je viditeľná a aktívna.
    • paused - Aplikácia stratila fokus, napr. je prekrytá inou nie plneobrazovkovou priesvitnou aktivitou. Aplikácia je teda čiastočne viditeľná a drží svoj stav. Pri prechode do tohto stavu volá metódu onPause().
    • stopped - Ak je aktivita kompletne prekrytá inou aktivitou, je v tomto stave. Stále síce obsahuje svoje členské premenné, ale nie je používateľovi viditeľná.
    • destroyed - Stav, kedy sa aktivita ukončuje (buď s povolením používateľa alebo automaticky). Ak sa znova zobrazí používateľovi, aktivita je vytvorená nanovo.
  • Basically, whenever Android destroys and recreates your Activity for orientation change, it calls onSaveInstanceState() before destroying and calls onCreate() after recreating. Whatever you save in the bundle in onSaveInstanceState, you can get back from the onCreate() parameter.

  • Vytvoríme teda metódu onSaveInstanceState() a uložíme v nej stav baterky:

    
        @Override
        protected void onSaveInstanceState(Bundle outState) {
            super.onSaveInstanceState(outState);
            outState.putBoolean("state", this.isOn);
        }
        
  • A následne upravíme aj metódu onCreate(), v ktorej v prípade, že bol stav aktivity uložený, tak si ho odpamätáme:

    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        
            if(savedInstanceState != null){
                this.isOn = savedInstanceState.getBoolean("state");
                if(this.isOn == true){
                    ImageView image = (ImageView) findViewById(R.id.image);
                    Button button = (Button) findViewById(R.id.button);
        
                    image.setImageResource(R.drawable.bulb_on);
                    button.setText(R.string.turn_off);
                }
            }else{
                this.isOn = false;
            }
        }
        
  • Tento fragment kódu sa nám čiastočne prekrýva s metódou toggle(), takže môžeme urobiť menší refaktoring:

    • vytvoríme súkromnú metódu render(), ktorá len vyrenderuje aktivitu na základe aktuálneho stavu (na základe hodnoty členskej premennej isOn, a
    • aktualizujeme metódu toggle(), z ktorej presunieme všetok fragment kódu týkajúci sa renderovania do metódy render().
  • Metóda toggle() teda bude vyzerať nasledovne:

    
        public void toggle(View view){
            player.start();
            this.isOn = !this.isOn;
            render();
        }
        
  • Metóda render() bude po refaktoringu vyzerať nasledovne:

    
        private void render(){
            Button button = (Button) findViewById(R.id.button);
            ImageView image = (ImageView) findViewById(R.id.image);
            Camera.Parameters params = this.camera.getParameters();
        
            if(this.isOn == true) {
                image.setImageResource(R.drawable.bulb_on);
                button.setText(R.string.turn_off);
        
                params.setFlashMode(Camera.Parameters.FLASH_MODE_TORCH);
                this.camera.setParameters(params);
                this.camera.startPreview();
            }else{
                image.setImageResource(R.drawable.bulb_off);
                button.setText(R.string.turn_on);
        
                params.setFlashMode(Camera.Parameters.FLASH_MODE_OFF);
                this.camera.setParameters(params);
                this.camera.stopPreview();
            }
        }
        
  • Následne metóda onCreate() bude vyzerať takto:

Obtaining and Realeasing Camera

  • Ako sme spomínali, kameru je možné získať pomocou volania:

    
        Camera camera = Camera.open();
        
  • Akonáhle otočím jej orientáciu, zosype sa opäť s výnimkou java.lang.RuntimeException a s hláškou: “Fail to connect to camera service”.

  • Ak si spomenieme na životný cyklus aktivity (slide), pri otočení zariadenia dochádza k ukončeniu a znovuvytvoreniu aktivity. To znamená, že ak chceme, aby novovytvorená aktivita mala prístup ku kamere a jej blesku, potrebueje toto zariadenie pri ukončení aplikácie uvoľniť. Ináč ho nebude môcť používať nie len aktivita našej aplikácie, ale ani žiadne iné aplikácie.

  • Kameru teda uvoľníme volaním:

    
        camera.release();
        
  • Je však potrebné a dôležité kameru uvoľniť resp. získať na správnom mieste, resp. v správnom čase. Môžeme sa pozrieť znovu na životný cyklus aktivity a nájsť spoločne správne miesto (metódu), v ktorej kameru uvoľníme a rovnako tak správne miesto (metódu), v ktorej kameru získame.

  • Za vhodné miesto pre uvoľnenie kamery sa javí metóda onPause(). Jej kód môže vyzerať nasledovne:

    
        @Override
        protected void onPause() {
            super.onPause();
        
            if (this.camera != null) {
                this.camera.release();
            }
        }
        
  • Podobne - vhodné miesto pre získanie objektu kamery sa javí metóda onResume(), ktorej kód následne môže vyzerať takto:

    
        @Override
        protected void onResume() {
            super.onResume();
        
            this.camera = Camera.open();
            this.params = this.camera.getParameters();
        
            update();
        }
        
  • Ak sme postupovali správne, baterka pri otočení zariadenia zhasne a následne sa rozsvieti znova pri znovuvytvorení aktivity.

Conclusion

  • Až teraz môžeme povedať, že je aplikácia hotová. Bolo by ešte možné vytvoriť niekoľko úprav (napr. zrušiť orientáciu na šírku, aby aplikácia zbytočne neblikala), ale tu sa už dá hovoriť o profite.

Additional Resources

  • slides

  • Android Design Tips

  • Supporting Multiple Screens

  • Camera API - The Android framework includes support for various cameras and camera features available on devices, allowing you to capture pictures and videos in your applications.

  • finish() - Call this when your activity is done and should be closed.

  • dialogs - A dialog is a small window that prompts the user to make a decision or enter additional information.