Zelf mobiele apps bouwen (deel 6)

© PXimport

Zelf mobiele apps bouwen (deel 6)

Geplaatst: 17 november 2021 - 09:03

Aangepast: 25 november 2022 - 11:16

Gertjan Groen

In veel apps zal het nodig zijn om gegevens te bewaren. In dit hoofdstuk van de basiscursus Apps bouwen behandelen we daarom enkele methodes voor lokale opslag. Daarmee bedoelen we de opslag van gegevens op het toestel zelf.

Code downloaden

In de eerdere lessen hebben we voor het bewaren van gegevens binnen je app met variabelen gewerkt. Als de app wordt afgesloten, ben je al die gegevens kwijt, wat natuurlijk niet altijd gewenst is. Daarom zul je in een app regelmatig gegevens fysiek opslaan voor later gebruik. Voor lokale gegevensopslag zijn binnen Android meerdere mogelijkheden. Eenvoudig maar praktisch zijn de zogenoemde SharedPreferences, waarbij je gegevens in zogenoemde naam/waarde-paren (of key/value) kunt bewaren. De waarde kan daarbij bijvoorbeeld een boolean, getal of tekst zijn. Dat maakt het ideaal om inhoud van bepaalde variabelen te bewaren, zoals de highscore van een game. We laten zien hoe dat werkt en gebruiken we het als uitbreiding voor de dobbelsteen-app.

Databases spelen een belangrijke rol bij de opslag van gegevens in apps.

© PXimport

Kotlin Koans

Kotlin Koans is een studieprogramma dat je in IntelliJ IDEA kunt gebruiken.

© PXimport

SharedPreferences

01 Waarden bewaren

Met SharedPreferences biedt Android een relatief eenvoudige manier om gegevens voor je app op te slaan onder een naam. We gaan het gebruiken als uitbreiding voor onze dobbelsteen-app. Op de achtergrond wordt hiervoor een bestand gebruikt. Er zijn eenvoudige methodes om gegevens op te vragen of te bewaren. Gegevens kun je overal in je eigen app raadplegen, maar zijn niet beschikbaar voor andere apps. Daarom kun je het veilig gebruiken voor bijvoorbeeld het opslaan van inloggegevens. We demonstreren SharedPreferences binnen onze dobbelsteen-app. We gaan hiermee het aantal worpen en de som bewaren, waarmee je dan zelf het gemiddelde van alle worpen kunt berekenen.

02 Initialisatie SharedPreferences

Gebruik je SharedPreferences binnen een activity, dan kun je bij de initialisatie getPreferences gebruiken. Wil je met meerdere bestanden gaan werken of de gegevens in meerdere activity’s gebruiken, dan gebruik je getSharedPreferences in combinatie met een naam. Die naam kun je zien als een bestandsnaam. Deze laatste optie gebruiken we in ons voorbeeld en als naam kiezen we worpen. Bij de initialisatie geef je naast de naam ook de modus op, zoals MODE_PRIVATE, waarmee je bepaalt dat het bestand niet met andere apps kan worden gedeeld. Dat kan als je dat al zou willen tegenwoordig overigens ook niet meer via SharedPreferences. We gaan werken binnen de eerder geschreven functie werpDobbelsteen(). Eerst initialiseren we daarin onze SharedPreferences, die we beschikbaar maken onder de variabele sharedPref:

var sharedPref: SharedPreferences = getSharedPreferences("worpen", MODE_PRIVATE)

 

© PXimport

03 Waarden raadplegen

In de functie gaan we direct nadat we met de regel val dobbelsteenInt = (1..6).random() een willekeurig getal voor de worp hebben bepaald het huidige aantal worpen (aantal) ophalen. Hierbij geven we bij de aanroep aan dat 0 de standaardwaarde (‘default’) is. Dat is het geval als je nog geen worp hebt gedaan in de app. We tellen er daarna meteen één bij op, zodat we het nieuwe aantal worpen hebben:

var aantal = sharedPref.getInt("aantal", 0) + 1

Op dezelfde manier halen we de bewaarde som van alle worpen op in de variabele som met ook weer 0 als standaardwaarde. We tellen de dobbelsteenworp erbij op en krijgen hiermee de nieuwe som van alle worpen:

var som = sharedPref.getInt("som", 0) + dobbelsteenInt

We gaan de nieuwe waarden voor aantal en som meteen bewaren. Eerst moeten we daarvoor een zogeheten editor voor SharedPreferences definiëren:

val editor = sharedPref.edit()

Daarna schrijven we de twee waarden weg onder de namen aantal en som:

editor.putInt("aantal", aantal)

editor.putInt("som", som)

Als laatste ronden we af met:

editor.apply()

Vervolgens berekenen we zelf het gemiddelde over alle worpen als Double, waarna we met een trucje de waarde afronden op twee decimalen:

var gemiddelde = som.toDouble() / aantal

var gemiddeldeAfgerond = gemiddelde.toBigDecimal().setScale(2, RoundingMode.UP).toDouble()

Als laatste bereiden we alvast de melding voor die we straks via een Toast gaan geven:

var toastMsg: String = "Aantal worpen: " + aantal + " - gemiddelde: " + gemiddeldeAfgerond

Vervolgens laten we op het moment dat de worp wordt getoond ook de melding via Toast zien:

Handler(Looper.getMainLooper()).postDelayed({

textWorp.text = dobbelsteenInt.toString();

Toast.makeText(this, toastMsg, Toast.LENGTH_SHORT).show()

}, 500)

Opslag van SharedPreferences

SharedPreferences worden opgeslagen in een map met data voor je app.

© PXimport

04 Uiteindelijke functie

Is alles klaar, dan ziet de uiteindelijke functie werpDobbelsteen() eruit zoals hieronder.

private fun werpDobbelsteen() {

var sharedPref: SharedPreferences = getSharedPreferences("worpen", MODE_PRIVATE)

val textWorp: TextView = findViewById(R.id.text_worp)

textWorp.text = "..."

val dobbelsteenInt = (1..6).random()

var aantal = sharedPref.getInt("aantal", 0) + 1

var som = sharedPref.getInt("som", 0) + dobbelsteenInt

val editor = sharedPref.edit()

editor.putInt("aantal", aantal)

editor.putInt("som", som)

editor.apply()

var gemiddelde = som.toDouble() / aantal

val gemiddeldeAfgerond = gemiddelde.toBigDecimal().setScale(2, RoundingMode.UP).toDouble()

var toastMsg: String = "Aantal worpen: " + aantal + " - gemiddelde: " + gemiddeldeAfgerond

Handler(Looper.getMainLooper()).postDelayed({

textWorp.text = dobbelsteenInt.toString();

Toast.makeText(this, toastMsg, Toast.LENGTH_SHORT).show()

}, 500)

}

De uitgewerkte functie voor SharedPreferences.

© PXimport

Emulator als onderdeel van Android Studio

De emulator kun je via een instelling ook binnen Android Studio laten zien.

© PXimport

Klassen en objecten

Voor complexere gegevens zijn variabelen wat te beperkt en zul je al snel met objecten en klassen (classes in het Engels) willen werken. De termen spelen een belangrijke rol binnen Android. Enige basiskennis is dan ook nodig als je met Room gaat werken (wat verderop in de paragraaf ‘Databases’ aan de orde komt.

05 Sjabloon voor objecten

Bij het (object-georiënteerd) programmeren en zo ook bij Android kom je de begrippen klasse (of class) en object tegen. Een klasse kun je zien als een sjabloon waarmee je objecten kunt maken. Een klasse bevat eigenschappen in de vorm van ‘gewone’ variabelen. Voor een persoon zijn dat bijvoorbeeld een naam, geslacht en leeftijd. Een klasse kan ook functies of methodes bevatten: handelingen die kunnen worden uitgevoerd. Voor een persoon kun je denken aan eten, drinken, lopen of slapen. Een specifieke persoon kun je zien als object of instantie van deze klasse. Aan de hand van enkele voorbeelden zullen we dit verduidelijken.

06 Klasse en object maken

Voor de voorbeelden gebruiken we weer IntelliJ IDEA. We voeren onderstaande regels in binnen de functie main(). Hierin beschrijven we eerst de class Persoon() met daarin de eigenschappen naam, geslacht en leeftijd:

class Persoon() {

var naam = ""

var geslacht = ""

var leeftijd = 0

}

Daarna maken we een object op basis van deze klasse:

var Collega = Persoon()

Hierna kun je de eigenschappen van het object eenvoudig veranderen, bijvoorbeeld:

Collega.naam = "Jan"

Collega.geslacht = "m"

Collega.leeftijd = 35

println(Collega.naam) // Jan

Je kunt ook andere objecten maken met weer andere eigenschappen op basis van dezelfde klasse:

var Receptioniste = Persoon()

Receptioniste.naam = "Lisa"

Receptioniste.geslacht = "v"

Receptioniste.leeftijd = 39

Voorbeeld met een klasse en enkele objecten op basis van die klasse.

© PXimport

07 Functies in je klasse

Een klasse bevat vaak ook functies. Als voorbeeld voegen we een functie stelVoor() toe die een String retourneert met een beschrijving van de persoon:

class Persoon() {

var naam = ""

var geslacht = ""

var leeftijd = 0

fun stelVoor() : String {

return "Hallo, ik heet " + naam + " en ik ben " + leeftijd + " jaar oud."

}

}

Daarna kun je de functie stelVoor() gebruiken voor je object en het resultaat bijvoorbeeld printen:

var Collega = Persoon()

Collega.naam = "Jan"

Collega.geslacht = "m"

Collega.leeftijd = 35

println(Collega.stelVoor()) // Hallo, ik heet Jan en ik ben 35 jaar oud.

08 Getters en setters

In veel programmeertalen kom je zogenoemde getters en setters tegen. Een getter is in feite een methode om de waarde van een eigenschap te lezen, terwijl je met een setter een waarde toekent. Binnen Kotlin worden ze automatisch gemaakt, maar je kunt ze wel beïnvloeden. Als voorbeeld maken we een aangepaste setter voor de eigenschap leeftijd.

class Persoon() {

var naam = ""

var geslacht = ""

var leeftijd = 0

set(value) {

field = value

if (value >= 18 ) volwassen = true

}

var volwassen = false

}

Als de leeftijd wordt aangepast, wordt eerst met field = value de waarde toegekend. Daarna stellen we de boolean volwassen in, zodat deze de juiste waarde krijgt, afhankelijk van de leeftijd. In het onderstaande voorbeeld zie je hoe deze eigenschap volwassen automatisch wordt bijgewerkt als de leeftijd wordt aangepast:

var Collega = Persoon()

Collega.naam = "Jan"

Collega.leeftijd = 17

println(Collega.volwassen) // false

Collega.leeftijd = 25

println(Collega.volwassen) // true

Bibliotheken voor objecten en JSON

Moshi is een bekende bibliotheek voor het werken met JSON binnen Android.

© PXimport

Databases

De SharedPreferences uit het begin van dit artikel zijn handig, maar vrij beperkt. Veel flexibeler ben je met SQLite, waarmee we in de rest van het artikel gaan werken, in combinatie met Room. SQLite is een zogenoemde relationele database. Zo’n database is opgebouwd uit tabellen die een onderlinge relatie met elkaar (kunnen) hebben. Voor het doorzoeken van de tabellen gebruik je opdrachten geschreven in de taal SQL. Room kun je als een abstractie-laag voor deze database zien.

In je code hoef je niet meer met SQL-opdrachten te werken om gegevens in je database te verwerken. Je maakt in feite gebruik van objecten. Omdat Room de voorkeursmethode voor het werken met SQLite is, krijgt het ook aandacht in deze cursus.

Een relationele database kun je zien als tabellen met onderlinge relaties.

© PXimport

09 Takenlijst bijhouden

We gaan een database gebruiken voor het bijhouden van een takenlijst. Een tabel in Excel dient als voorbeeld (zie afbeelding). Hierin zie je de kolommen id, titel en belangrijk. Elke rij bevat een taak met een unieke id. Die zul je in Excel misschien niet altijd gebruiken, maar is in een database gangbaar om te zorgen dat elke rij uniek is en gemakkelijk aanwijsbaar. Voor het veld belangrijk gebruiken we hier een boolean om aan te geven of de taak belangrijk is. Met de Excel-tabel als voorbeeld is de transitie naar SQLite eenvoudig.

Voorbeeld voor onze takenlijst in Excel.

© PXimport

10 SQLite

In de volgende deel van de cursus gaan we met Room aan de slag, dat op de achtergrond gebruikmaakt van SQLite. Daarom beginnen we met een uitleg van SQLite. Stel dat je van onze takenlijst een SQLite-database wilt maken, dan kan dat met de volgende opdracht:

CREATE TABLE taak (

id INTEGER PRIMARY KEY,

titel TEXT NOT NULL,

belangrijk INTEGER NOT NULL

);

Bovenstaande wordt ook wel een database-schema genoemd. Hierbij dient de id voor het identificeren van elke rij, wat we aangeven met PRIMARY KEY. Merk op dat we voor de kolom belangrijk geen boolean maar een integer gebruiken, omdat SQLite geen booleans ondersteunt. Je kunt gewoon 0 voor false of 1 voor true gebruiken. Wil je gegevens ophalen uit de database, dan gebruik je een zogenoemde SELECT. Zo’n verzoek wordt ook wel een query genoemd. Wil je bijvoorbeeld de id en titel van alle taken ophalen, dan kan dat met onderstaande query:

SELECT id, titel FROM taken;

Je kunt met een wildcard ook in één keer alle taken ophalen:

SELECT * FROM taken;

Wil je een selectie maken en bijvoorbeeld alleen de belangrijke taken ophalen, dan kun je de SELECT uitbreiden met extra condities via een WHERE, wat zodoende als een soort filter werkt:

SELECT * FROM taken WHERE belangrijk=1

Op vergelijkbare wijze zijn er opdrachten om bijvoorbeeld rijen toe te voegen, bij te werken of te verwijderen. Omdat we bij de kolom id de optie PRIMARY KEY hebben gebruikt, zal SQLite er zelf voor zorgen dat (als je een rij toevoegt) een unieke id wordt toegekend door de huidige hoogste waarde met één op te hogen. In je app voor Android kun je prima met SQLite werken, maar je hebt dan wel relatief veel code nodig. Room maakt het eenvoudiger en geeft ook andere voordelen.

11 Relaties tussen tabellen

SQLite en varianten worden zoals gezegd ook wel relationele databases genoemd. Dat komt omdat er meestal meerdere tabellen zijn die met elkaar samen (kunnen) hangen. Je zou onze tabel bijvoorbeeld uit kunnen breiden met een kolom perid en vervolgens een tweede tabel maken met de naam persoon en de kolommen perid en naam. Via perid maak je dan een relatie tussen die twee tabellen. Je kunt dit gebruiken om bijvoorbeeld taken aan een bepaalde persoon toe te wijzen. De afbeelding laat dat voorbeeld in Excel zien. Je zou hierna een query kunnen schrijven om bijvoorbeeld alle taken voor een bepaalde persoon op te halen.

Bij meerdere tabellen is de kolom id de verbindende factor.

© PXimport

Deel dit artikel
Voeg toe aan favorieten