cursus Android Studio les 3

In de derde les introduceren we Toast, ImageView, WebView, het MVC design pattern, ListView, ArrayAdapter en OnClickListener. Om dit allemaal te demonstreren maken we LijstApp die een lijst van posts laat zien vanuit een database met posts. Daarnaast introduceren we Logcat om LijstApp te debuggen.

De tweede les was veel uitleg over Android, hoe je het een en ander met Android Studio kunt maken en waar je alles in de documentatie van Google kunt vinden. Bij de derde les veronderstellen dat je zelf de relevante informatie zoekt, dat je experimenteert met Android Studio en dat je veel test op een AVD of via een USB-kabel. Bijna alles uit de tweede les herhalen we of gaan we dieper op in.

De LijstApp krijgt drie verschillende schermen. Op het eerste scherm staat een lijst van posts, die je kunt aanklikken. Het tweede scherm laat de post zien met een titel, een tekst, eventueel een plaatje en eventueel een link naar een website voor nog meer informatie. Je kan door middel van een upvote-Button en een downvote-Button aangeven hoe belangrijk je de post vindt. Het derde scherm verschijnt als je op de link naar een website klikt. Om het eenvoudig te houden maken we nog geen verbinding met een externe database, maar doen alsof er wel een is. Dat doen we met behulp van een voorbeeld database in Java.


Oefening 5: Ontwerp en test de eerste twee schermen voor LijstApp

Voorlopig beperken we ons dus tot 1 post. In int downvotes, upvotes tellen we hoe populair die post is. Hoe het tweede scherm er verder uitziet werken we later uit. De uitwerking van het eerste scherm met de lijst van (meer dan 1) posts volgt nog later in deze les.


Van LineairLayout naar ConstraintLayout

Tot nu toe gebruikten we LineairLayout met android:orientation="vertical", omdat dat de meest eenvoudige layout van Android is. Alle onderdelen: EditText, ViewText en Button stonden gewoon onder elkaar.

Maar om de downvote-Button, de TextView en de upvote-Button van oefening 5 naast elkaar te krijgen, moest je een LineairLayout met android:orientation="horizontal" genest invoegen. Verder moest je alles omlaag halen met android:layout_gravity="bottom" en breedtes instellen met android:layout_width="wrap_content". Kortom veel gedoe en veel uitzoekwerk.

activity_post.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=".PostActivity">

    <TextView
        android:id="@+id/titel"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Post zonder titel"
        android:textSize="24sp"
        android:textStyle="bold" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal">

        <Button
            android:id="@+id/downvoteKnop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="downvote"
            android:onClick="downvotePost" />

        <TextView
            android:id="@+id/downvotesUpvotes"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="0 / 0"
            android:textSize="24sp"
            android:textStyle="bold"/>

        <Button
            android:id="@+id/upvoteKnop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="upvote"
            android:onClick="upvotePost"/>

    </LinearLayout>
</LinearLayout>

        

Omdat LineairLayout niet altijd handig is, kent Android ook FrameLayout, RelativeLayout, GridLayout en ConstraintLayout. Deze layouts staan ongeveer in volgorde van een steeds steilere leercurve en dan noemen we niet eens alle widgets, views en bijbehorende instellingen en mogelijkheden. Bovendien is alles voortdurend aan verandering onderhevig, omdat Google concurreert met Apple iPhone. Alles moet steeds mooier, met animaties, swipes en steeds nieuwe hardware. Het is zelfs voor professionele Android ontwikkelaars niet bij te houden. Hoe moeten wij dan kiezen? Gelukkig is Google heel duidelijk:

For better performance and tooling support, you should instead build your layout with ConstraintLayout.

Dus we moeten de ConstraintLayout en de Android Studio Design-tool bestuderen om echt mooie schermen te ontwerpen! Voor goede uitleg kan je je aanmelden op de HVA Lynda portal. Zoek daarna op David Gassner of klik op het plaatje. Deze Lynda cursus duurt 3 uur.

In deze cursus blijven we echter zo eenvoudig mogelijke layouts gebruiken, want we willen ons beperken tot hoofdzaken van Android.

Activity life cycle en Bundle nader bekeken

PostActivity.java

package nl.hva.pdiepen.lijstapp;

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

public class PostActivity extends AppCompatActivity {
    private int downvotes, upvotes;

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

    public void upvotePost(View view) {
        upvotes++;
        setDownvotesUpvotes("Dat was een upvote!");
    }

    public void downvotePost(View view) {
        downvotes++;
        setDownvotesUpvotes("Dat was een downvote!");
    }

    public void setDownvotesUpvotes(String toastTekst) {
        Toast.makeText(this, toastTekst, Toast.LENGTH_SHORT).show();
        TextView id = (TextView) findViewById(R.id.downvotesUpvotes);
        String t = "-" + downvotes + " +" + upvotes;
        id.setText(t);
    }
}
        

Bovenstaande code zou alles moeten doen wat in oefening 5 werd gevraagd. Dat is niet het geval, want als je het scherm draait, gaat het mis. Opeens staan downvotes en upvotes weer op nul. Wat is hier mis gegaan?

In de praktijk betekent dat laaste dat de gebruiker, die bezig is met jouw app, de telefoon opneemt, even naar WhatsApp gaat of zoals hier het scherm draait. Android heeft op elk moment vele activities van meer apps draaien, maar die zijn dus niet allemaal even actief. Ze verkeren in verschillende states van de activity lifecycle. De gebruiker en Android bepalen wat er gebeurt. De ontwikkelaar moet daarop anticiperen door middel van lifecycle callback methods.

Tot nu toe hadden we alleen onCreate toegepast. Achter de schermen heeft Android jouw activity gestart in ART en heeft de layout voor jouw activity klaar gezet en de resources erbij gehaald. In de documentatie van Google heet dat inflate. Pas nadat dit alles is gedaan, geeft Android jou controle in onCreate.

Als de gebruiker het scherm heeft gedraaid, moet de layout totaal anders worden en zijn misschien andere resources nodig. Android regelt dat allemaal, maar begint gewoon opnieuw en doet alsof jouw activity nooit iets heeft gedaan. Je krijgt gewoon weer een callback in onCreate en downvotes en upvotes zijn nul.

In de code zie je dat onCreate een Bundle meekrijgt van Android. Een Bundle is een Java class die Android gebruikt om allerlei informatie over layout en resources vast te leggen. Met super.onCreate verwerken we die Bundle voor gebruik in onze Activity.

Wij kunnen ons probleem met downvotes en upvotes oplossen door onze informatie in vorm van key / value pairs ook vast te leggen in de Bundle. Android gebruikt de method onSaveInstanceState om de informatie over layout en resources vast te leggen. Wij gebruiken daarom ook de method onSaveInstanceState om onze informatie: downvotes en upvotes vast te leggen. Vervolgens kunnen we in onCreate onze informatie ook weer ophalen.

        
PostActivity.java
    ...
    final String DOWNVOTE = "down";
    final String UPVOTE = "up";

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

        if (savedInstanceState == null) {
            setDownvotesUpvotes("We gaan beginnen");
        } else {        
            downvotes = savedInstanceState.getInt(DOWNVOTE);
            upvotes = savedInstanceState.getInt(UPVOTE);
            setDownvotesUpvotes("We gaan verder");
        }
    }

    @Override
    protected void onSaveInstanceState(Bundle savedInstanceState) {
        super.onSaveInstanceState(savedInstanceState);
        savedInstanceState.putInt("down", downvotes);
        savedInstanceState.putInt("up", upvotes);
    }
    ...
        

Uiteraard moeten we dit nog even testen. Merk op dat in het tweede plaatje een Toast verschijnt.

Verschil tussen Bundle en Intent

Het doorgeven van informatie in de vorm van key / value pairs via een Bundle lijkt op het doorgeven van informatie via een Intent.

Omdenken

De oplossing voor het probleem met downvotes en upvotes vloeit dus voort uit de manier hoe Android omgaat met activities. De workaround is: de variables meegeven aan de Bundle en ze daarna weer uit de Bundle halen. Een totaal andere aanpak is: zorgen dat de class PostActivity geen global variables heeft.

Indien je zorgt dat er geen global variables in de Activity zijn (als je dus zorgt dat variables uitsluitend local in de methods bestaan), kan je ze niet kwijtraken als Android de Activity opnieuw opstart.

Of indien je zorgt dat jouw informatie ook is opgeslagen in een datastrucuur die onderdeel is van de layout (TextView, EditView, enz.) gaat het automatisch goed, want Android slaat de layout altijd op in de Bundle.

In dit geval hadden we in onCreate de downvotes en upvotes uit de String kunnen peuteren, want die staan in de TextView met R.id.downvotesUpvotes. In andere gevallen is de workaround misschien de enige manier.

Resources nader bekeken

In les 2 stap 5 zijn de String resources aan de orde geweest. String resources staan in res/value/strings.xml. In Java gebruik je ze met bijvoorbeeld @string/app_name. Layouts zoals activity_main en activity_post staan in res/layout.

Voor het tweede scherm van LijstApp hebben we plaatjes nodig. Uiteindelijk moeten die plaatjes van een externe database komen, maar voorlopig slaan we ze op in res/drawable. In Java gebruik je ze met bijvoorbeeld @drawable/femke_halsema. Het bijbehoorde bestand is femke_halsema.jpg. Android zoekt zelf uit of het een png, jpg of gif plaatje is. Je moet als Android ontwikkelaar wel zorgen dat de naam van het bestand helemaal in kleine letters is, want anders doet Android alsof het plaatje niet bestaat en zie je niks.

De resources zijn dus onderdeel van de app. Ze worden door Android Studio samen ingepakt in een APK-bestand.


Oefening 6: Voltooi het tweede scherm van LijstApp

Het tweede scherm van LijstApp was nog niet af. In oefening 5 had je alleen de downvote-Button en upvote-Button in de layout gezet. In oefening 6 moet je de volgende views toevoegen:

Voor het plaatje kan je een van de bovenstaande plaatjes gebruiken of zelf iets opzoeken. Dat geldt ook voor de tekst en de website. Om een plaatje te laten zien moet je een ImageView in de layout zetten met bijvoorbeeld android:src="@drawable/femke_halsema".


Oefening 7: Maak het derde scherm van LijstApp

Bij de website-Button hoort de naarWebsite methode die de WebsiteActivity start en de url meegeeft. In les 2 stap 6 is dit eerder aan de orde geweest.

Maak de WebsiteActivity met in de layout een WebView. Om de webpagina te laten zien, moet je de methode loadUrl gebruiken.


AndroidManifest.xml

Tot nu toe hadden we nog niet naar AndroidManifest.xml gekeken, omdat Android Studio die voor ons genereert. Om een webpagina te laten zien moeten we echter een regel toevoegen. Je moet Android waarschuwen dat LijstApp het internet gaat raadplegen.

Verder specificeer je in AndroidManifest.xml welke activity de launch activity is. De launch activity hoeft dus niet MainActivity te heten, want Android kijkt toch in AndroidManifest.xml bij welke activity in het intent-filter LAUNCHER is gespecificeerd.

        
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="nl.hva.pdiepen.lijstapp">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".WebsiteActivity"></activity>
        <activity android:name=".PostActivity" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
        

Uitwerking oefening 6 en 7

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

        ((TextView) findViewById(R.id.titel)).setText("Femke Halsema");
        ((ImageView) findViewById(R.id.plaatje)).setImageResource(R.drawable.femke_halsema);
        ((TextView) findViewById(R.id.tekst)).setText(
                "Femke Halsema is sinds 2018 de eerste vrouwelijke burgemeester van Amsterdam. " +
                        "Daarvoor was ze van 1998 tot 2011 lid van de Tweede kamer voor GroenLinks.");
        ((Button) findViewById(R.id.website)).setText("Wikipedia");
        ((TextView) findViewById(R.id.url)).setText("https://nl.wikipedia.org/wiki/Femke_Halsema");

        if (savedInstanceState == null) {
            setDownvotesUpvotes("We gaan beginnen");
        } else {
            downvotes = savedInstanceState.getInt(DOWNVOTE);
            upvotes = savedInstanceState.getInt(UPVOTE);
            setDownvotesUpvotes("We gaan verder");
        }
    }

    ...

    public void naarWebsite(View view) {
        Intent intent = new Intent(this, WebsiteActivity.class);
        TextView id = (TextView) findViewById(R.id.url);
        String t = id.getText().toString();
        intent.putExtra(WebsiteActivity.URL, t);
        startActivity(intent);
    }
    
    ...
        
WebsiteActivity.java
package nl.hva.pdiepen.lijstapp;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.webkit.WebView;

public class WebsiteActivity extends AppCompatActivity {

    public static final String URL = "url";

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

        Intent intent = getIntent();
        String url = intent.getStringExtra(URL);
        WebView id = (WebView) findViewById(R.id.webPagina);
        id.getSettings().setJavaScriptEnabled(true);
        id.loadUrl(url);
    }
}        
        

Model View Controller

Voordat we verder gaan met het derde scherm: de lijst van posts gaan we een refactor doen van LijstApp. Refactor is als je je code gaat structureren, zodat het eenvoudiger wordt om nieuwe features toe te voegen. We gebruiken daarvoor de MVC architectuur. Dat betekent dat we de classes verdelen over 3 lagen: model, view en controller.

In de model-laag modeleren we de data en business logic onafhankelijk van hoe die zijn vastgelegd in een bestand, database of op het internet. In de praktijk komt er meestal nog Data Access Object laag (DAO) tussen model objecten en bestand / database / internet.

De view-laag van een Android app is de hiërarchie van views binnen een layout. Daarin staan alle objecten zoals LineairView, EditView, Button, ImageView, WebView, enz. zoals Android die inflate vanuit de XML-bestanden. De view-laag is dus wat de gebruiker ziet.

De activities van een Android app vormen de controller-laag. Daarin koppelen we model aan view. De controller-laag zit dus tussen de model-laag en de view-laag.

Elke Android app heeft dus al een view- en een controller-laag. Bij de refactor van de LijstApp verwijderen we code uit de PostActivity en verplaatsen die naar de Post class in de model-laag. Bovendien maken we de class LeesPostsDatabase.

Post.java
package nl.hva.pdiepen.lijstapp;

import java.util.ArrayList;

public class Post {
    private static ArrayList<Post> posts = new ArrayList<Post>();
    
    public static int getAantalPosts() {
        return posts.size();
    }

    public static Post getPost(int index) {
        return posts.get(index);
    }

    public static ArrayList<Post> getPosts() {
        return posts;
    }

    private String titel;
    private int plaatje; // @Drawable resource identifier
    private String tekst;
    private String website;
    private String url;
    private int downvotes;
    private int upvotes;    

    public Post(String titel, int plaatje, String tekst, String website, String url) {
        this.titel = titel;
        this.plaatje = plaatje;
        this.tekst = tekst;
        this.website = website;
        this.url = url;
        this.downvotes = 0;
        this.upvotes = 0;
        Post.posts.add(this);
    }
    
    public void downvote() {
        downvotes++;
    }

    public void upvote() {
        upvotes++;
    }    

    @Override
    public String toString() {
        return titel;
    }

    public String getTitel() {
        return titel;
    }

    public int getPlaatje() {
        return plaatje;
    }

    public String getTekst() {
        return tekst;
    }

    public String getWebsite() {
        return website;
    }

    public String getUrl() {
        return url;
    }
}
        

Hieronder staat slechts een deel van de database. Voor ons voorbeeld hebben we hem gevuld met de volgende items.

LeesPostsDatabase.java
package nl.hva.pdiepen.lijstapp;

public class LeesPostsDatabase {
    public LeesPostsDatabase() {
        ...

        new Post("Femke Halsema",
                R.drawable.femke_halsema,
                "Femke Halsema is sinds 2018 de eerste vrouwelijke burgemeester van Amsterdam. " +
                        "Daarvoor was ze van 1998 tot 2011 lid van de Tweede kamer voor GroenLinks.",
                "Wikipedia",
                "https://nl.wikipedia.org/wiki/Femke_Halsema");

        new Post("Eberhard van der Laan",
                R.drawable.eberhard_van_der_laan,
                "Eberhard van der Laan was van 2010 tot zijn overlijden in 2017 burgemeester van Amsterdam. " +
                        "Daarvoor was hij van 2008 tot 2010 minister voor Wonen, Wijken en Integratie in het kabinet-Balkenende IV.",
                "Wikipedia",
                "https://nl.wikipedia.org/wiki/Eberhard_van_der_Laan");

        new Post("Job Cohen",
                R.drawable.job_cohen,
                "Job Cohen was burgemeester van Amsterdam van 2001 tot 2010. " +
                        "Van 2010 tot 2012 lid was hij lid van de Tweede Kamer voor de PvdA",
                "Wikipedia",
                "https://nl.wikipedia.org/wiki/Job_Cohen");

        ...
    }
}
        

Lijst van posts op eerste scherm van LijstApp

In oefening 5 hadden we een layout gemaakt met een Button om PostActivity te starten en dan verscheen altijd Femke Halsema. Hier wijzigen we die layout zodanig dat de gebruiker uit een lijst kan kiezen wie of wat PostActivity laat zien. Daarvoor moeten we een aantal dingen aanpassen.

Layout met ListView

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" >

    <ListView
        android:id="@+id/postsLijst"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>
        

ArrayAdaptor

De ArrayAdaptor maakt van de posts in de ArrayList items voor de ListView. Je hoef alleen 3 parameters te specificeren:

Anonymous inner class

In de layouts die we tot nu toe hadden gemaakt, konden we bij een Button bijvoorbeeld android:onClick="upvotePost" specificeren. Vervolgens konden we in de methode upvotePost coderen wat er moet gebeuren als de gebruiker op die knop klikt. Achter de schermen heeft Android Studio een event listener aan de Button gekoppeld, die een @Override voor de methode onItemClick doet met daarin een aanroep van de methode upvotePost.

In een layout met ListView heeft Android Studio geen event listener gekoppeld. Toch wil je de PostActivity starten als de gebruiker een item aanklikt. Daarom moet je zelf een event listener koppelen aan elk item in de ListView. Bovendien moet je voor elk item doorgeven welk item het is en dat coderen in de @Override voor onItemClick. Dat doe je door de AdapterView.OnItemClickListener() interface te implementeren en een methode als parameter door te geven. Dat kan in Java met behulp van een anonymous inner class. Dat zie je in MainActivity tussen de haakjes die wordt afgesloten met });

Helaas kan het niet eenvoudiger. Gelukkig helpt Android Studio wel bij het intoetsen van deze ingewikkelde code. Er verschijnt een popup menu en dan moet je gewoon de eerste keuze selecteren.

MainActivity.java
package nl.hva.pdiepen.lijstapp;

import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ListView;

import java.util.ArrayList;

public class MainActivity extends AppCompatActivity {

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

        new LeesPostsDatabase();
        ArrayList<Post> allePosts = Post.getPosts();

        ArrayAdapter arrayAdapter = new ArrayAdapter(MainActivity.this,android.R.layout.simple_list_item_1,allePosts);

        ListView idLijst = (ListView) findViewById(R.id.postsLijst);
        idLijst.setAdapter(arrayAdapter);

        idLijst.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Intent intent = new Intent(MainActivity.this, PostActivity.class);
                intent.putExtra(PostActivity.POST_ID, id);
                startActivity(intent);
            }
        });
    }
}
        

POST_INDEX doorgeven via Intent

Indien de gebuiker op een item van de ListView in MainActivity klikt, zet OnItemClick een key / value pair in een Intent en start PostActivity. Hierin is POST_INDEX de key en index is de value, die verwijst naar de juiste Post in de ArrayList. Dat is de Post die PostActivity moet laten zien.

PostActivity.java        
    ...
    
public class PostActivity extends AppCompatActivity {
    public static final String POST_INDEX = "index" ;
    ...
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_post);

        Intent intent = getIntent();
        long index = intent.getLongExtra(POST_ID,0);
        Post p = Post.getPost((int) index);

        ((TextView) findViewById(R.id.titel)).setText(p.getTitel());
        ((ImageView) findViewById(R.id.plaatje)).setImageResource(p.getPlaatje());
        ((TextView) findViewById(R.id.tekst)).setText(p.getTekst());
        ((Button) findViewById(R.id.website)).setText(p.getWebsite());
        ((TextView) findViewById(R.id.url)).setText(p.getUrl());

        if (savedInstanceState == null) {
            setDownvotesUpvotes("We gaan beginnen");
        } else {
            downvotes = savedInstanceState.getInt(DOWNVOTE);
            upvotes = savedInstanceState.getInt(UPVOTE);
            setDownvotesUpvotes("We gaan verder");
        }
    }

    ...
        

Debuggen in Android Studio met Logcat

De LijstApp heeft nu alle onderdelen die we vooraf hadden bedacht, maar is nog niet perfect. We moeten nog het een en ander debuggen. En misschien liep je al eerder vast. Gelukkig heeft Android Studio allerlei tools om fouten uit LijstApp te halen.

De compiler geeft fouten en waarschuwingen. Die verschijnen in het Build window (met het hamertje). Sommige compiler fouten kunnen heel hardnekkig zijn, omdat Android Studio allerlei code genereert zoals XML-bestanden en R.java. Eventueel kan je alles helemaal opnieuw genereren met Build en dan Clean Project.

Als Build helemaal goed is gegaan en LijstApp draait op een AVD of via een USB-kabel op je telefoon, kan LijstApp stuk lopen op allerlei fouten die ART zal melden in Logcat (Alt-6). Zulke fouten verschijnen in rode teksten.

Als je niet meer begrijpt wat er gebeurt, kan je ook zelf meldingen maken in Logcat. Hieronder staat een voorbeeld. De methode Log.d heeft 2 parameter nodig: een tag en een melding.

MainActivity.java
    ...
public class MainActivity extends AppCompatActivity {
    private static final String WATSKEBURT = "watskeburt MainActivity";
    ...
    @Override
    protected void onStart() {
        super.onStart();
        Log.d(WATSKEBURT,"onStart");
    }
    ...
        

Oefening 8: Onderzoek de callback methods van de LijstApp activities

Hierboven staat het voorbeeld van Log in onStart van MainActivity. Schrijf soortgelijke code in de volgende callback methods van MainActivity, PostActivity en WebsiteActivity. Zorg dat de tag steeds WATSKEBURT is, maar met een andere tekst per Activity. Android Studio kan je helpen om deze code snel te schrijven. Klik op Code, Override Methods... (Ctrl-O) en selecteer:

Als je nu LijstApp test en naar Logcat (Alt-6) gaat, verschijnt zoiets als in onderstaande plaatje. Rechtsboven staat Show only selected application, maar het is heel veel. Je kunt door op het prullenbakje te klikken het window even wissen, maar er blijven steeds meldingen verschijnen.

Je kunt de meldingen filteren met de tag WATSKEBURT. Klik op het driehoekje rechtsboven en Edit Filter Configuratie. Daarna krijg je alleen meldingen met de tag WATSKEBEURT. Onderzoek welke callback methods langskomen als je LijstApp goed test. Wat gebeurt er als je van MainActivity naar PostActivity gaat? Wat gebeurt er als je teruggaat of naar WebActivity gaat, of als je het scherm draait, een andere app start, en zo voort? Maak eventueel een extra melding met Log.d om te laten zien wat je doorgeeft in de Intent of in de Bundle.


Oefening 9: Zoek de fout in LijstApp

Zoals gezegd is de LijstApp nog niet perfect. Ten eerste gaat er weer iets mis met de upvotes en downvotes, maar het oplossen van deze fout stellen we uit tot oefening 10. Ten tweede kan de lijst spontaan langer worden door een handeling van de gebruiker.

In oefening 9 moet je uitzoeken wanneer de lijst spontaan langer wordt. Dat kan je doen met behulp van Logcat en wat je eerder in deze les hebt geleerd. Beredeneer wat er precies misgaat. Vervolgens moet je de fout verhelpen. De oplossing is ergens een if toevoegen.


Oefening 10: Vervolmaak de LijstApp

Bij het afsplitsen van Post (Model) van PostActivity (Controller) heeft de Post class wel upvote en downvote methods gekregen, maar die worden niet aangeroepen vanuit PostActivity met als gevolg dat de gebruiker wel op de koppen kan drukken, maar dat het resultaat niet wordt opgeslagen. LijstApp heeft nog geen echte database, maar nu staan upvotes en downvotes weer op nul als de gebruiker teruggaat naar de lijst.

Vervolmaak de LijstApp zodanig dat het goed gaat met de upvotes en downvotes.

Is het nog nodig om downvotes en upvotes in de Bundle op te slaan?


Samenvatting les 3