cursus Android Studio les 2

In de tweede les geven we een rondleiding langs de Android stack, maken we BerichtApp en testen die op een Android Virtual Device.

De eerste les was niet bedoeld om meteen alles uit te leggen, maar vooral om zo snel mogelijk te beginnen. In de tweede les proberen we juist wel alles uit te leggen. Helaas is Android immens groot en voortdurend aan verandering onderhevig. Daarom moeten we ons beperken en de informatie doceren. De BerichtApp is vooral bedoeld om de essentie van Android te demonstreren en om uit te leggen waar je alles kunt vinden in Android Studio en in de documentatie van Google en andere bronnen.

Voorkennis

Stack en GUI

De applicaties die je voor Programming en OOP1 hebt gemaakt, hadden een stack van twee lagen (jouw Java-code en de JVM) en nog geen GUI. Voor input en output gebruikte je een CLI met Scanner(System.in) en System.out.println().

In het boek van Liang komt een GUI pas aan de orde in hoofdstuk 14 na de hoofdstukken over OOP. Het is gebruikelijk om met OOP een GUI te implementeren als een aparte laag van de stack. Het idee is dat je de GUI-laag zodanig programmeert dat die onafhankelijk is van de rest van de applicatie. Dit heeft als voordeel dat je de GUI-laag die is gemaakt met AWT of Swing kunt vervangen door JavaFX of iets wat nog mooier is.

Bovendien is jouw Java-applicatie onafhankelijk van de hardware en het operating system waarop die draait. Jouw Java-applicatie draait namelijk op de Java Virtual Machine en er zijn verschillende JVM's voor Windows, Apple, Linux, enz. De JVM-laag zit tussen jouw Java-applicatie en het operating system.

Bij een webapplicatie zijn er meer lagen: HTML voor de structuur van de webpagina, CSS voor de opmaak, JavaScript om de DOM te manipuleren (allemaal frontend in de browser) en allerlei lagen op de server (backend).

Bij een Android app is alles weer een beetje anders: in plaats van HTML en CSS manipuleren we XML-bestanden met Java en tussen Java en het Linux operating system zit geen JVM, maar ART (of Dalvik bij oudere versies van Android).

De GUI van een Android app maak je dus met XML op soortgelijke wijze als je een GUI maakt voor een webapplicatie met HTML en CSS. En je programmeert een Android app in Java zoals je een webapplicatie programmeert met JavaScript.

Android Stack

Google heeft Android opgezet als open source platform en operating system voor mobiele telefoons, tabletcomputers, TV's en nog veel meer apparaten gebaseerd op de Linux kernel en het Java-programmeerplatform. Of zoals Google het formuleert:

Android unites the world! Use the open source Android operating system to power your device.

Android bestaat inmiddels al meer dan 10 jaar en is tegenwoordig het meest gebruikte operating system. Android draait op meer dan 2 miljard apparaten!

Iedereen kan echt meedoen. De source van Android zelf kan je downloaden van source.android.com en op developer.android.com vind je alle informatie die je als ontwikkelaar van Android apps nodig kunt hebben.

Het plaatje rechts is een vereenvoudigd overzicht van de Android stack. De lagen bestaan uit verschillende onderdelen. De laag waar wij het meeste mee te maken krijgen, is de groene laag met het Java API Framework. Klik op het plaatje voor een meer compleet overzicht met uitleg.

Componenten van een Android app

Als je een Android app maakt, moet je gebruik maken van Java classes die Google heeft ontworpen. De belangrijkste zijn: Activity, Intent, Service, Broadcast Receiver en Content Provider. Google heeft de hele Android stack zodanig ontworpen dat deze classes kunnen functioneren. Deze componenten zijn het fundament van Android sinds de allereerste versie van Android.

Het is dus essentieel dat je deze componenten begrijpt en kunt toepassen. In deze les maken we de BerichtApp en beperken we ons tot het toepassen van Activity en Intent. Klik op het plaatje voor een meer complete uitleg.

Activity

Hieronder staat een deel van de Javadoc van Activity. Klik voor de volledige Javadoc. Rechtsboven zie je dat Activity onderdeel is van de Android library sinds versie 1 (en zelfs al eerder).

Intent

Hieronder staat een deel van de Javadoc van Intent. Klik voor de volledige Javadoc. Rechtsboven zie je dat Intent onderdeel is van de Android library sinds versie 1 (en zelfs al eerder).

Stap 1: Configureer project BerichtApp

We gaan Activity en Intent toepassen in de BerichtApp. Hieronder volgt weer een stap voor stap beschrijving precies zoals bij les 1. In deze les leggen we meer uit en verwijzen we naar extra informatie over alles wat we tegenkomen. Bovendien demonstreren we hoe je BerichtApp kunt testen op een Android Virtual Device

Dit is al eerder aan de orde geweest in les 1 stap 3. De opties This project will support instant apps en Use AndroidX artifacts moet je niet niet aanvinken, want die gaan we niet gebruiken. Raadpleeg eventueel de documentatie van Android Studio voor meer uitleg over deze opties.

Als je op Help me choose klikt, verschijnt het volgende window. Op Distribution dashboard staat hoe Google aan deze cijfers komt.

Hier zie je ook een samenvatting wat er nieuw was in Android KitKat en als je op andere versies doorklikt, krijg je die informatie van een andere versie. Opmerkelijk is dat Android Studio 3.3.1 van 8/2/2019 achterloopt. Sinds 6/8/2018 bestaat Android Pie. De Wikipedia heeft een uitgebreider overzicht van de versies.

Stap 2: Configureer een Android Virtual Device

In les 1 stap 8 hadden we door middel van upload via een USB-kabel onze EersteApp op een mobiele telefoon getest. In les 2 testen we BerichtApp op een Android Virtual Device oftewel AVD.

Omdat Android Studio nu een complete BerichtApp heeft gegenereerd, is dit een handig moment om deze te testen op een of meer AVD's. Dat kan door gewoon op Run app te klikken. De eerste keer kan de build van BerichtApp, het opstarten van de emulator en het starten van BerichtApp op de AVD totaal 15 minuten duren. Als de AVD eenmaal draait, zal de cyclus BerichtApp aanpassen en Run app (Shift-F10) steeds vlotter gaan. Met Apply Changes (Ctrl-F10) kan het vaak nog sneller. Raadpleeg eventueel de documentatie van Android Studio voor meer uitleg.

De USB-kabel is niet aangesloten, want we gaan op een AVD testen. Klik op Create New Virtual Device.

Selecteer een apparaat. Klik op Next.

Klik op Next.

Klik op Finish.

Stap 3: Test BerichtApp op een Android Virtual Device

Indien er een AVD draait, kan je die steeds opnieuw gebruiken. Eventueel kan je nog een AVD maken voor een tabletcomputer en dan kan je afwisseld op beide AVD's testen.

Stap 4: Ontwerp eerste scherm voor BerichtApp

BerichtApp krijgt twee activities. Met de eerste Activity kan de gebruiker een bericht intoetsen en op een knop drukken om het bericht te versturen. Dan moet BerichtApp een tweede Activity starten die het bericht ontvangt.

We beginnen dus met het ontwerpen van het eerste scherm. Je kunt dat afwisselend doen in tabblad Design en tabblad Text. Dit is al eerder aan de orde geweest in les 1 stap 4.

De uitgebreide uitleg over het designtool staat in de documentatie van Android Studio. De uitgebreide uitleg over de XML en de bijbehorende Java staat in Layouts.

Voor de BerichtApp gaan we de layout zo simpel mogelijk maken. Daarom beperken we ons tot een LineairLayout met een TextView, een Button en een EditView.

De ID's editText1, button1 en textView1 zijn gegenereerd door Android Studio. Betere ID's zijn berichtTekst, zendKnop en verzondenTekst. Verder zie je in het Component Tree window drie warnings (gele driehoekjes): Hardcoded string ..., should use @string resource. Dat gaan we allemaal verbeteren in de volgende stap.

Maar eerst testen we of alles werkt op de AVD. Klik op Run app. Als het goed is gegaan kan je op de knop klikken en een bericht intoetsen.

Stap 5: Maak String resources voor BerichtApp

Resource name: berichtTekstHint is android:text="@string/berichtTekstHint" in activity_main.xml en komt in plaats van een hardcoded String.

Resource name: berichtTekstHint en Resource value: Bericht intoetsen vormen samen een name / value pair in de String resources.

activity_main.xml        
       
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/berichtTekst"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:ems="10"
        android:inputType="textAutoComplete"
        android:text="@string/berichtTekstHint"
        android:textSize="24sp"
        android:textStyle="bold" />

    <Button
        android:id="@+id/zendKnop"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/zendKnop"
        android:onClick="verzendBericht"
        android:textSize="24sp"
        android:textStyle="bold" />

    <TextView
        android:id="@+id/verzondenTekst"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/berichtNietVerzonden"
        android:textSize="24sp"
        android:textStyle="bold" />
</LinearLayout>
        

De android:id's heb je nodig om met Java-code iets met de GUI te kunnen doen.

Merk op dat voor berichtTekst textAutoComplete is ingesteld. Dat is een van de vele standaard mogelijkheden van EditText.

Merk op dat verzendBericht is ingesteld als de method die wordt aangeroepen als de gebruiker op de knop drukt. Dit is een van de vele standaard mogelijkheden van Button. De bijbehorende Javacode schrijven we in de volgende stap.

De @strings zijn name / value pairs in strings.xml. Het plaatje rechts laat zien dat je die kunt vinden in res/values/strings.xml.

Je mag een String hardcoded in de layout of de Java-code laten staan, maar Android Studio geeft dan een warning. Het is beter om ze als String resources te scheiden van de layout en de Java-code. De layout en Java-code vinden via @string/ Resource name de bijbehorende Resource value.

Deze werkwijze heeft als voordeel dat Android in staat is om met verschillende talen te werken, want alles staat bij elkaar in strings.xml. Als je voor Frans, Duits en Engels verschillende strings.xml-bestanden maakt, gebruikt Android de automatisch de juiste taal. In de documentatie van Google heet dat Localize your app.

Dit geldt overigens voor veel meer instellingen in Android. Android ondersteunt verschillende apparaten, verschillende kleuren, verschillende talen, enz. Daarom moeten app-bouwers zorgen voor alternatieve resources.

strings.xml 

<resources>
    <string name="app_name">BerichtApp</string>
    <string name="berichtTekstHint">Bericht intoetsen</string>
    <string name="zendKnop">Verzend bericht</string>
    <string name="berichtNietVerzonden">Bericht nog niet verzonden</string>
    <string name="berichtVerzonden">Bericht verzonden</string>    
</resources>
        

Stap 6: Schrijf Java-code voor eerste scherm van BerichtApp

MainActivity.java

package nl.hva.pdiepen.berichtapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void verzendBericht(View view) {
        TextView verzondenTekst = (TextView) findViewById(R.id.verzondenTekst);
        String t = getString(R.string.berichtVerzonden);
        verzondenTekst.setText(t);
    }
}
        

Dit is al eerder aan de orde geweest in les 1 stap 5. Alleen de gele code moet je toevoegen. De rest is gegenereerd door Android Studio. Hieronder volgt een summier uitleg van bovenstaande Java-code en we laten zien dat Android Studio achter de schermen nog meer code heeft gegenereerd.

AppCompatActivity

Merk op dat class MainActivity extends AppCompActivity en niet Activity. Volgens de Javadoc hieronder is AppCompActivity een subclass van Activity (en van FragmentActivity). Dat betekent dat MainActivity dus ook de attributes en methods erft van Activity. AppCompActivity (en FragmentActivity) kan dus meer dan de orginele Activity en is pas in versie 25 aan Android toegevoegd. Klik voor de volledige Javadoc.

Lifecycle callback methods

Je kunt als ontwikkelaar een Activity iets laten doen als Android je controle geeft. Dan doe je door middel van een @Override van een Activity lifecycle callback method. Android roept zo'n method aan op specifieke momenten: onCreate, onStart, onResume, onPause, onStop en onDestroy.

In deze les beperken we ons tot onCreate.

Bundle

Android legt per scherm vast wat de gebruiker doet en wat er eerder is gebeurd. In de documentatie van Google heet dat Saving UI states. Een Bundle is een Java class om deze informatie vast te leggen. Android geeft een Bundle mee bij de aanroep van een callback method zoals onCreate. Met super.onCreate verwerken we die Bundle voor gebruik in onze Activity.

R.java

Elke Activity heeft een eigen GUI met een schermlayout. De GUI bestaat uit XML-code, plaatjes, filmpjes, muziek, enz., de resources. Alle resources van een app hebben in Android een uniek nummer. Achter de schermen genereert Android Studio R.java met public final class R en inner classes voor de (hexadecimal) nummers van de resources.

Android Studio heeft R.java verstopt in een folder die normaliter niet zichtbaar is. Klik in het Project paneel op het driehoekje wijzig Android in Project. Met Project zie je namelijk alle files en folders. Met Android zie je slechts een deel. Vervolgens vind je heel diep onder de build folder R.java. Zie het plaatje rechts. Als je op R.java klikt, waarschuwt Android Studio: Files under the build folder are generated and should not be edited. Dat is dus de reden waarom ze achter de schermen verstopt zijn.

Voor de ontwikkelaar van een app is R.java zeer belangrijk, want met setContentView(R.layout.activity_main) in onCreate koppel je MainActivity.java aan activity_main.xml, het eerste scherm van de BerichtApp. Met findViewById(R.id.verzondenTekst) in verzendBericht refereer je aan een TextView in de LineairLayout. Met getString(R.string.berichtVerzonden) in verzendBericht refereer je aan een String resource in de string.xml. En zo voort.

Hieronder staat een uittreksel uit de ruim 11000 regels van R.java.

R.java

/* AUTO-GENERATED FILE. DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found. It
 * should not be modified by hand.
 */

package nl.hva.pdiepen.berichtapp;

public final class R {
    public static final class anim {
        ...
    }
    public static final class id {
        public static final int berichtTekst=0x7f070020;
        public static final int verzondenTekst=0x7f070091;
        public static final int zendKnop=0x7f070095;
        ...
    }
    public static final class layout {
        public static final int activity_main=0x7f09001c;
        ...
    }
    public static final class string {
        public static final int app_name=0x7f0b0027;
        public static final int berichtNietVerzonden=0x7f0b0028;
        public static final int berichtTekstHint=0x7f0b0029;
        public static final int berichtVerzonden=0x7f0b002a;
        public static final int zendKnop=0x7f0b002d;
        ...
    }
    ...
}
        

Test eerste scherm BerichtApp

De Java-code voor het eerste scherm van BerichtApp is geschreven. De BerichtApp stuurt nog niet echt een bericht, maar meldt alleen dat het bericht is verzonden. Dit moeten we natuurlijk wel even testen.


Oefening 2

Maak een extra TextView vorigeBericht en laat de method verzendBericht de berichtTekst in vorigeBericht zetten en @string/berichtTekstHint weer in berichtTekst zetten. Hint: de tegenhanger van setText() is getText.toString().


Oefening 3

Maak nog een knop om de klinkers te verwijderen en een extra TextView voor het resultaat. Doe iets met de grootte van de letters. Bedenk iets voor berichten met meer regels en voor het sluiten van het toetsenbord.


Stap 7: Maak tweede Activity van BerichtApp

Na veel uitleg over de eerste Activity van BerichtApp en een intermezzo met 2 oefeningen, gaan we nu verder met de tweede Activity. We maken OntvangBerichtActivity die het bericht uit MainActivity ontvangt en eventueel met een druk op een knop doorstuurt naar een andere app. Dit alles om te demonstreren hoe activities binnen Android andere activities starten en informatie uitwisselen door middel van intents. Zie de inleiding.


Oefening 4: Ontwerp tweede scherm voor BerichtApp

Maak een verticale LineairLayout voor OntvangBerichtActivity met TextView, Button en TextView. De eerste TextView is voor het te ontvangen bericht. De Button is om het bericht naar een andere app door te sturen. Zorg dat Button een android:onClick method berichtDoorsturen krijgt. De tweede TextView is om te melden dat het bericht wel of niet is doorgestuurd.


Stap 8: Start de tweede Activity van BerichtApp

MainActivity.java
    ...

    public void verzendBericht(View view) {
        TextView verzondenTekst = (TextView) findViewById(R.id.verzondenTekst);
        String t = getString(R.string.berichtVerzonden);
        verzondenTekst.setText(t);
        
        Intent intent = new Intent(this, OntvangBerichtActivity.class);
        startActivity(intent);
    }
    ...
        

Voeg de gele code toe, zodat Android OntvangBerichtActivity start. In de Intent geef je aan Android door wie de afzender is: this (dat is dus MainActivity) en welke Activity Android moet starten: OntvangBerichtActivity.class.

Voor stap 8 is het noodzakelijk dat je oefening 4 hebt gedaan! Klik op Run app om dit alles te testen.

De eerste twee plaatjes laten het eerste scherm van BerichtApp zien en het derde plaatje is van het tweede scherm. Als je op de terugtoets (het driehoekje) van de AVD klikt, ga je terug naar het eerste scherm en zie je ook het tweede plaatje. Omdat in de Intent ook een afzender stond, kan Android teruggaan naar het eerste scherm.

Stap 9: Stuur bericht naar OntvangerBerichtActivity

MainActivity.java
    ...

    public void verzendBericht(View view) {
        TextView verzondenTekst = (TextView) findViewById(R.id.verzondenTekst);
        String t = getString(R.string.berichtVerzonden);
        verzondenTekst.setText(t);
        
        Intent intent = new Intent(this, OntvangBerichtActivity.class);
        EditText bericht = (EditText) findViewById(R.id.berichtTekst);
        String inhoud = bericht.getText().toString();
        intent.putExtra(OntvangBerichtActivity.BERICHT, inhoud);
        startActivity(intent);
    }
    ...
        

In stap 9 hadden we alleen OntvangBerichtActivity gestart. In stap 10 geven we de inhoud van het bericht, dat de gebruiker heeft ingetoets, ook door via de Intent in de vorm van een name / value pair. De eerste parameter van putExtra is de name. De tweede parameter van putExtra is de value. De name moet iets zijn wat OntvangBerichtActivity moet herkennen. Daarom gebruiken we de constante BERICHT van de class OntvangBerichtActivity.

OntvangBerichtActivity.java

package nl.hva.pdiepen.berichtapp;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class OntvangBerichtActivity extends AppCompatActivity {

    public static final String BERICHT = "bericht";
    private String inhoudBericht;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_ontvang_bericht);

        Intent intent = getIntent();
        inhoudBericht = intent.getStringExtra(BERICHT);
        TextView ontvangenBerichtTekst = (TextView) findViewById(R.id.ontvangenBerichtTekst);
        ontvangenBerichtTekst.setText(inhoudBericht);
    }
    
    public void berichtDoorsturen(View view) {
    }
}
        

Uiteraard moet je ook in OntvangBerichtActivity wat Java-code schrijven om het bericht te verwerken.

Voor stap 9 is het noodzakelijk dat je oefening 4 hebt gedaan, zodat Android Studio ook al een android:onClick method heeft gemaakt.

Klik op Run app om dit allemaal te testen. Als het goed is gegaan, zal op het tweede scherm hetzelfde bericht staan als je op het eerste scherm hebt ingetoetst.

Stap 10: Stuur bericht door naar andere app

In stap 9 was de aandroid:onClick method nog leeg. Hier demonstreren we hoe je met BerichtApp nu eindelijk echt een bericht kan sturen!

OntvangBerichtActivity.java
    ...

    public void berichtDoorsturen(View view) {
        TextView doorgestuurdTekst = (TextView) findViewById(R.id.doorgestuurdTekst);
        String t = getString(R.string.berichtDoorgestuurd);
        doorgestuurdTekst.setText(t);

        Intent intent = new Intent(Intent.ACTION_SEND);
        intent.setType("text/plain");
        intent.putExtra(Intent.EXTRA_TEXT, inhoudBericht);
        startActivity(intent);
    }
    ...
        

De eerste twee plaatjes laten het tweede scherm van BerichtApp zien. Het derde en vierde plaatje zijn van een andere app. Als je op de terugtoets (het driehoekje) van de AVD klikt, ga je terug naar het tweede scherm van BerichtApp en zie je ook het tweede plaatje.

Samenvatting les 2