Week 02

zdroje, lokalizácia, ikona aplikácie, obrázky, prehrávanie médií, manifest

Úvod

  • http://sli.do#ki-smart - anketa a otázky pre prednášajúceho
  • https://join.slack.com/t/ki-smart/signup - slack pre rýchlu komunikáciu
  • hackaton - zatiaľ nie sú žiadne ďalšie informácie
  • coderfest - už budúci utorok, zaregistrujte sa čo najskôr!
  • 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í
  • enki - šikovná aplikácia pre vaše každonné nešportové workouty - programátorské
  • prvý šprint - budúci týždeň cviko bude

Torch Application - Continues

  • dnes budeme pokračovať vo vývoji aplikácie, ktorú sme začali vyvíjať minulý týždeň - baterku
  • (slide)

Zmena textu tlačidla a trieda View

  • Ak si spomenieme na ukážkové správanie aplikácie, po kliknutí na tlačidlo by malo dôjsť k zmene jeho textu. Potrebujeme teda získať referenciu na objekt tlačidla, aby sme vedeli aktualizovať jeho text.

  • Ak sa pozrieme na deklaráciu metódy toggle(), má jeden parameter typu View. Tento parameter je práve referenciou na objekt, nad ktorým k volaniu metódy došlo, čo je v našom prípade práve tlačidlo.

  • (slide) Trieda View je však veľmi význačná, nakoľko akýkoľvek prvok nachádzajúci sa v rozložení aktivity je potomkom tejto triedy. To znamená, že aj samotné tlačidlo, ktoré je inštanciou triedy Button je potomkom triedy View.

  • Pred samotnou zmenou textu teda najprv potrebujeme parameter metódy view pretypovať a to buď priamo v deklarácii metódy alebo vytvoriť novú lokálnu premennú metódy, ktorá vznikne pretypovaním parametra:

    public void toggle(View view){
        Button button = (Button)view;
    
        this.isOn = !this.isOn;
        if(this.isOn == true){
            button.setText("Turn Off");
        }else{
            button.setText("Turn On");
        }
    }

Zmena textu elementu <TextView>

  • My však budeme chcieť zmeniť obsah textu priamo v aplikácii. Aby sme získali objekt reprezentujúci element <TextView> zo súboru s vzhľadom aktivity, použijeme metódu findViewById() (slide). Táto metóda má jeden parameter, ktorým je identifikátor daného elementu (v Androidovom API je každý element potomkom triedy View, ale o tom neskôr). Identifikátor sa nachádza v atribúte elementu android:id, pričom v kóde je reprezentovaný konštantou pomocou triedy R. K objektu elementu <TextView> sa teda v kóde dostaneme nasledovne:

    TextView label = (TextView) findViewById(R.id.label);
  • Obsah elementu je možné zmeniť pomocou metódy objektu s názvom setText(). Metóda toggle() bude po uvedených úpravách vyzerať nasledovne:

    public void toggle(View button){
        TextView label = (TextView) findViewById(R.id.label);
    
        if(this.isOn == true){
            this.isOn = false;
            label.setText("Off");
        }else{
            this.isOn = true;
            label.setText("On");
        }
    }

Resources

  • Je najvyšší čas nahradiť textový popisok obrázkom, ktorý bude reprezentovať stav baterky. Preto sa najprv pozrieme na organizáciu projektu a povieme si niečo o zdrojoch aplikácie (slide).

  • používajú sa na zabezpečenie špecifických konfigurácií, ako napr. rozličné jazykové verzie, rozlíšenia alebo orientácia aplikácie

  • nachádzajú sa v priečinku /res (slide):

    • drawable/ - Bitmap files (.png, .9.png, .jpg, .gif)
    • layout/ - XML files that define a user interface layout
    • mipmap/ - Drawable files for different launcher icon densities.
    • values/ - XML files that contain simple values, such as strings, integers, and colors.
    • raw/ - Arbitrary files to save in their raw form.
  • Once you provide a resource in your application, you can apply it by referencing its resource ID. All resource IDs are defined in your project’s R class, which is automatically generated.

  • A resource ID is always composed of (slide):

    • The resource type: Each resource is grouped into a “type,” such as string, drawable, and layout.
    • The resource name, which is either: the filename, excluding the extension; or the value in the XML android:name attribute, if the resource is a simple value (such as a string).
  • Trieda Resources umožňuje pristupovať ku zdrojom vašej aplikácie.

i18n and l10n

  • (slide)

  • i18n - internationalization

  • l10n - localization

  • ak chceme lokalizovať aplikáciu do iného jazyka, ako je angličtina, priečinok so zdrojmi pre iný jazyk musí byť pomenovaný s postfixom reprezentujúcim kód daného jazyka

  • súbor s prekladom bude teda vyzerať nasledovne:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string name="app_name">Baterka</string>
        <string name="turn_on">Zasvietiť</string>
        <string name="turn_off">Zhasnúť</string>
    </resources>
  • následne treba upraviť všetky texty v aplikácii, kde sa uvedené reťazce nenachádzajú, resp. kde sa nachádzajú v surovom tvare na reťazce zo zdrojov (R.string.turn_on v kóde, resp. @string/turn_on v súbore návrhu)

Obrázky a <ImageView>

  • Ak chceme v aplikácii používať obrázky a chceme, aby sa stali súčasťou aplikácie, musíme ich nakopírovať do priečinku /res/drawable.

  • Pre projekt budeme potrebovať tieto obrázky:

    bulb_on bulb_off

  • Následne aktualizujeme kód. Pre nastavenie obrázku objektu typu ImageView použijeme metódu setImageResource(), kde parametrom je identifikátor zdroja obrázku. Ak sme obrázky úspešne pridali do projektu, Android Studio rozpozná všetky obrázkové zdroje. Keď teda budeme vkladať parametre do metódy setImageResource(), bude nám priamo posúvať dva zdroje: R.drawable.bulb_on a R.drawable.bulb_off:

    public void toggle(View view){
        Button button = (Button) view;
        ImageView image = (ImageView) findViewById(R.id.image);
    
        this.isOn = !this.isOn;
        if(this.isOn == true){
            image.setImageResource(R.drawable.bulb_on);
            button.setText(R.string.turn_off);
        }else{
            image.setImageResource(R.drawable.bulb_off);
            button.setText(R.string.turn_on);
        }
    }

Ikona Aplikácie

  • Ikona aplikácie zatiaľ vyzerá dosť trápne. A pokiaľ by sme chceli aplikáciu naozaj predávať, chcelo by to niečo atraktívnejšie a sexi. Pozrieme sa teda na to, ako tento nedostatok našej aplikácie opraviť.

  • Keď sme hovorili o zdrojoch (resources) aplikácie, hovorili sme aj o tom, že priečinok /res/mipmap/ obsahuje ikony, ktoré reprezentujú práve spúšťač aplikácie.

  • Aby sme sa veľmi nenamakali, môžeme pre tvorbu ikony využiť rozličné nástroje. Jedným z nich je aj on-line nástroj Android Asset Studio, ktorý obsahuje niekoľko nástrojov, pričom jedným z nich je práve nástroj na tvorbu ikon spúšťačov. Zrejme po vzore tohto online nástroja vzniklo Asset Studio, ktoré je súčasťou aj Android Studia.

  • My si teda jednoduchú ikonu aplikácie vytvoríme. Ako podklad použijeme nasledujúcu ikonu:

    launcher icon
  • Nástroj Android Asset Studio nám následne umožní náš výsledok stiahnuť vo forme .zip balíčku, ktorý obsahuje ikony v niekoľkých rozlíšeniach, resp. formátoch:

    • xxxhdpi (extra-extra-extra-high) ~ 640dpi
    • xxhdpi (extra-extra-high) ~ 480dpi
    • xhdpi (extra-high) ~ 320dpi
    • hdpi (high) ~ 240dpi
    • mdpi (medium) ~ 160dpi
    • ldpi (low) ~ 120dpi
  • Miesto, kde definujeme, ktorá ikona sa v skutočnosti použije ako ikona aplikácie, sa nachádza v manifeste aplikácie (v súbore AndroidManifest.xml). Konkrétne sa jedná o atribút elementu <application> v tvare:

    android:icon="@mipmap/ic_launcher"

Playing Media

  • Aplikácia je už síce funkčná, ale môžeme spestriť jej používanie pridaním zvuku pre stlačenie tlačidla. S tým nám pomôže trieda MediaPlayer.

  • Samotné zvukové súbory je potrebné uložiť do priečinku /res/raw/. V kóde budú následne dostupné ako R.raw.file.

  • Zapnutie a vypnutie baterky bude reprezentované týmto zvukom (možno pre niektorých aj pomerne známym, pokiaľ ste s dr. Freemanom strávili s pajserom v jednej ruke a baterkou v druhej množstvo nezabudnuteľných chvíľ).

  • Ak chcem daný zvuk následne prehrať, použijem tento fragment kódu:

    MediaPlayer player = MediaPlayer.create(this, R.raw.sound);
    player.start();
  • Kvôli správnemu načasovaniu “šťuknutia” je vhodné spustiť prehrávač ešte predtým, ako dôjde k zmene obrázkov, textov a zasvieteniu blesku. Ideálnym miestom sa javí metóda toggle(), ktorá po implementovaní prehrávača bude vyzerať takto:

    public void toggle(View view) {
        MediaPlayer player = MediaPlayer.create(this, R.raw.flashlight_on);
        player.start();
    
        this.isOn = !this.isOn;
        update();
    }

Smart Device

  • Otázka na štátniciach od profesora Návrata pri téme súvisiacej s inteligentnou domácnosťou (smart home): “A čo je na tom vlastne to inteligentné?”
  • Podobne je to aj s nami - doteraz sme robili aplikáciu na zariadení, ktoré má prívlastok smart. V čom je však výnimočné toto chytré alebo inteligenté zariadenie v porovnaní s bežnými počítačmi?
  • Definícia chytrého zariadenia podľa wikipédie: “A smart device is an electronic device, generally connected to other devices or networks via different wireless protocols such as Bluetooth, NFC, WiFi, 3G, etc., that can operate to some extent interactively and autonomously.”
  • Definícia chytrého telefónu podľa wikipédie: “A smartphone or smart phone is a mobile phone with an advanced mobile operating system which combines features of a personal computer operating system with other features useful for mobile or handheld use. They typically combine the features of a cell phone with those of other popular mobile devices, such as personal digital assistant (PDA), media player and GPS navigation unit.”
  • Naša aplikácia zatiaľ nie je nijak výnimočná a nijak sa nelíši od bežných aplikácií spustených na hlúpom zariadení. Rozšírime teda jej funkcionalitu tým, že do nej integrujeme použitie blesku fotoaparátu tak, aby fungoval ako baterka.

Package Manager

  • Ak teda chceme pracovať s bleskom, musíme získať objekt kamery. Nie všetky zariadenia sú však s kamerou vybavené, preto by bolo dobré pred získaním tohto objektu overiť, či sa kamera v systéme vôbec nachádza. Nemusíme sa však nakoniec pýtať priamo na kameru, ale stačí sa opýtať na to, či máme k dispozícii len blesk.

  • Na to, aby sme overili, či je zariadenie obsahuje potrebnú vlastnosť, použijeme triedu PackageManager, ktorú je možné získať priamo z aktivity volaním metódy getPackageManager().

  • Správca balíčkov plní podobnú funkcionalitu ako je správca balíčkov v linuxovom systéme. Pomocou neho je napr. možné získať zoznam nainštalovaných balíčkov alebo aplikácií v systéme.

  • My sa však pomocou neho budeme snažiť zistiť, či naše zariadenie podporuje požadovanú vlastnosť, resp. funkcionalitu. To docielime volaním metódy hasSystemFeature() inštancie triedy PackageManager.

  • (slide) Požadovaná konštanta pre overenie, či je alebo nie je zariadenie vybavené bleskom, sa volá PackageManager.FEATURE_CAMERA_FLASH. Konštanty pre overenie ostatných vlastností môžete nájsť na stránke s dokumentáciou tejto triedy.

  • Následne vieme vytvoriť kód pre overenie existencie požadovanej vlastnosti v zariadení:

    PackageManager pm = getPackageManager();
    if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) == false){
        Log.e(TAG, "This device doesn't support Camera Flash.");
        Toast.makeText(this, 
            "This device doesn't support Camera Flash.", 
            Toast.LENGTH_LONG).show();
        finish();
        return;
    }
  • Triedu Log aj Toast už poznáme, ale neznámou je v tomto kóde metóda finish().

Metóda finish()

  • Metóda aktivity. Metóda sa volá vtedy, keď aktivita skončila a má byť zatvorená, resp. ukončená. Nedôjde však k okamžitému ukončeniu aktivity, ako je to v prípade napr. volania metódy System.exit(). Metóda, ktorá túto metódu zavolala, sa najprv korektne ukončí.

Permissions

  • Ak momentálne aktualizujeme metódu onCreate() o uvedený fragment kódu, výsledok bude vyzerať nasledovne:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        // init camera
        PackageManager pm = getPackageManager();
        if(pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH) == false){
            Log.e(TAG, 
                "This device doesn't support Camera Flash.");
            Toast.makeText(this, 
                "This device doesn't support Camera Flash.", 
                Toast.LENGTH_LONG).show();
            finish();
            return;
        }
    }
  • Ak následne spustíme takto upravenú aplikáciu v emulátore, zobrazí sa nám na obrazovke Toast a v LogCate hláška: This device doesn't support Camera Flash. Preto bude ďalšie spúšťanie prebiehať na živom zariadení.

  • Následne sa už len stačí dostať ku objektu kamery prostredníctom volania:

    Camera camera = Camera.open();
  • Ideálne z nej rovno spraviť členskú premennú:

    private Camera camera;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        this.camera = camera;
    }
  • Ak ale aplikáciu s týmto kódom spustíme, zosype sa s výnimkou java.lang.RuntimeException: java java.lang.RuntimeException: Unable to start activity ComponentInfo{smart.tuke.sk.torch/smart.tuke.sk.torch.MainActivity}: java.lang.RuntimeException: Fail to connect to camera service

  • Problém, ktorý tu nastal, nesúvisí s tým, že by sme kameru s bleskom nemali, ale že k nim v rámci aplikácie nemáme prístup. Každá aplikácia totiž, ak chce využívať špeciálne vlastnosti, ako napr. prístup na internet, prístup k súborovému systému, prístup ku kontaktom alebo aj spomínanú kameru, musí ich mať explicitne povolené.

  • Všetky vlastnosti, ktoré chceme v rámci aplikácie používať, potrebujeme zadenifovať v manifeste aplikácie (v súbore AndroidManifest.xml) nad elementom <application>. V našom prípade to teda bude vyzerať takto:

    <uses-permission android:name="android.permission.CAMERA" />
    <application
        ...
  • Po aplikovaní tejto zmeny už bude aplikácia pracovať tak, ako má, aj keď zatiaľ bez samotného svetla. Iba ak žeby nie. V tom prípade je potrebné sa pozrieť na nastavenie verzie Target SDK v súbore build.gradle.

Let there be Light

  • aktualizujeme teda metódu toggle() nasledovne:

    public void toggle(View view){
        this.player.start();
    
        Button button = (Button) findViewById(R.id.button);
        ImageView image = (ImageView) findViewById(R.id.image);
        Camera.Parameters params = this.camera.getParameters();
    
        this.isOn = !this.isOn;
    
        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();
        }
    }

Conclusion

  • aplikáciu už máme takmer hotovú - dokáže pracovať s bleskom a svietiť
  • keď ju však otočím, zhasne a znova sa rozsvieti
  • keď ju minimalizujem, tak zhasne
  • keď ju otočím, tak okrem toho, že zhasne, aj zabudne, v akom stave bola
  • Nabudúce sa pokúsime všetky tieto problémy vyriešiť. Pozrieme sa na životný cyklus aktivity, pokúsime sa niečo spraviť s orientáciou na ležato a taktiež skúsime vyriešiť problém toho, že prestane svietiť, keď aplikáciu minimalizujem.

Additional Resources