Wat gaan we doen

Lees verder na de advertentie.
Deze DevTutorial draait om de de interne Database van Android: SQLite. Op veel tutorials die je op het internet tegenkomt wordt je geleerd om op een best omslachtige manier gegevens in de database te stoppen en er weer uit te halen. De bedoeling is dat je via deze DevTutorial leert hoe dat veel sneller kan. Hierbij kom je de volgende onderwerpen tegen:
De extra oefening gaat over het combineren van de AutoCompleteTextView met je database: Langzamerhand maak je ingewikkelde mooie dingen. 🙂 Alle oefeningen van deze DevTutorial gaan door op de Twitter-app uit de vorige twee DevTutorials.
Succes ermee!
Java package downloaden en toevoegen: OrmLite
Voordat je straks verder kan met de DevTutorial heb je een extra verzameling van Java-packages nodig. Zoals je weet zie je hier alle packages die standaard in Android zitten, hierin zitten de Activity class, de View class, de LayoutInflator class en ga zo nog maar een tijdje door. Soms is het zo dat je bepaalde classes nodig hebt waarop je bepaalde methods wil kunnen gebruiken en dat deze classes niet standaard aanwezig zijn in Android. De eerste oplossing hiervoor is het zelf maken van nieuwe classes: dat heb je bijvoorbeeld in de twee vorige DevTutorials gedaan toen je zelf twee eigen classes vanaf het begin opbouwde. De tweede oplossing is zoeken op het internet of iemand anders al classes heeft geschreven die een groot deel van de methods bevatten die je nodig hebt. In deze DevTutorial willen we de lokale database van Android gebruiken. Er zijn al wel classes in Android die je hiervoor kan gebruiken, maar hiervoor moet je best veel programmeerwerk doen. De standaard Androidclasses hiervoor gebruiken is helemaal niet verstandig als je bedenkt dat er classes bestaan die je veel programmeerwerk uit handen nemen. Deze classes zijn samen gebundeld in de OrmLite package, daar kan je hier meer over lezen. Om de OrmLite package te kunnen gebruiken moet je hem downloaden en in Eclipse toevoegen aan je Androidproject. Hoe je deze package kan gebruiken lees je straks, eerst gaan we hem toevoegen:
Nu heb je de packages aan je Androidproject toegevoegd die horen bij OrmLite Core (de kern van OrmLite) en Orm Lite Android (de packages speciaal voor Android). Hierdoor kan je nu ook al deze classes en al deze classes gebruiken in je app. Een belangrijke class in deze packages is de OrmLiteSqliteOpenHelper class en een belangrijke interface is de Dao<T, ID> interface. Nadat zometeen wordt uitgelegd wat een database is, worden deze class en interface uitgelegd.
De database van Android: SQLite
Android heeft een eigen database-systeem waarin je gemakkelijk gegevens kan opslaan zodat je gegevens bewaard blijven als je je app afsluit. De technologie die Android hiervoor gebruikt heet SQLite. De homepage van SQLite ziet er wel een beetje simpel uit, maar dit is geen reden om deze technologie te onderschatten (de iPhone en iPad gebruiken SQLite bijvoorbeeld ook). Om te begrijpen wat een database is, kan je eerst het beste denken aan meerdere kaartenbakken die vroeger gebruikt werden in bedrijven om verschillende soorten gegevens bij te houden:
Als je vroeger een bedrijf had dat bijvoorbeeld boeken verkocht, dan hadden ze een kaartenbak voor boeken, een kaartenbak voor klanten en een kaartenbak voor bestellingen van klanten. In de kaartenbak voor boeken zaten dan allemaal verschillende kaarten en op elke kaart stond de beschrijving van een boek, inclusief de titel, jaar van uitgave, prijs en meer van dat soort gegevens. Op dezelfde manier had je de kaartenbak voor klanten met allemaal kaarten waar op elke kaart de gegevens van één klant stonden (naam, woonplaats, rekeningnummer, etcetera). En op dezelfde manier hadden alle bestellingen die klanten gedaan hebben één kaart in de kaartenbak van bestellingen. Het idee van deze kaartenbakken was dat je verschillende plekken had (kaartenbakken dus) waar je verschillende gegevens in bewaarde. Door de informatie op deze manier op te delen wist je waar je moest zijn als je wat voor informatie dan ook nodig had, in plaats van dat alle kaarten allemaal in één kaartenbak werden gestopt. Als je dit verhaal wil snappen in database-taal, dan zijn alle kaartenbakken samen de database: dus de verzameling van álle gegevens (data). Maar omdat het belangrijk is verschillende soorten gegevens gescheiden te houden heb je in de database verschillende tabellen (dat zijn de kaartenbakken). Een tabel is dus eigenlijk een afgescheiden plek in de database waarin je dezelfde soort gegeven kan opslaan. Voordat je een tabel kan gebruiken moet je aangeven welke soort gegevens je erin wilt opslaan, of in kaartenbak-taal: welke informatie wil je op elke kaart kunnen invullen zodat je dit later kan teruglezen. Nu even terug naar onze Twitter-app: als we een kaartenbak (tabel dus) van Twitter-gebruikers willen hebben, dan willen we de gebruikersnaam van de gebruiker kunnen opslaan en ook de bio, de website en het aantal favoriete Tweets. En verder: omdat de kaartenbak altijd blijft bestaan (de tabel verdwijnt niet als we de Androidapp afsluiten) willen we weten of de gegevens in de kaartenbak niet te oud zijn: we willen op elke kaart kunnen bijhouden wanneer we hem voor het laatst hadden gecontroleerd op actualiteit. Hierdoor kunnen we straks programmeren: als de gegevens ouder dan één dag zijn, download dan nieuwere gegevens. Als we straks een kaart willen opvragen uit één specifieke kaartenbak, dan moeten we dat kunnen doen via een code die voor elke kaart anders is in de kaartenbak. Op deze manier kan je vanuit andere plekken in je app een verbinding maken met één specifieke kaart. In het geval van Twitter-gebruikers kan je als code prima de gebruikersnaam gebruiken: er is namelijk altijd maar één Twitter-gebruiker gekoppeld aan één specifieke gebruikersnaam (er zijn niet twee mensen die met een ander e-mailadres en ander wachtwoord gekoppeld zijn aan hetzelfde Twitter-account). In database-taal noem je zo’n unieke code een id, dat is een afkorting van identificatie. Meestal gebruik je als id per kaart in een kaartenbak (in database-taal: per record per tabel) een getal (int of long), maar voor het opslaan van Twitter-gebruikers is de gebruikersnaam ook een goede keuze. Als je nou één kaartenbak gebruikt voor Twitter-gebruikers, dan is het logisch om een andere kaartenbak te gebruiken voor Tweets van die gebruikers. Op elke kaart in de kaartenbak (database-taal: in elk record in de tabel) wil je een Tweet kunnen opslaan: dus elke kaart heeft dan een stippellijntje waarop je de tekst van de Tweet kan invullen en een stippellijntje voor de datum van de Tweet. Maar alle Tweets van iedereen in één kaartenbak gooien heb je niet zoveel aan: je kan nu wel alle Tweets snel opzoeken, maar vaak wil je alleen de Tweets opzoeken die bij een bepaalde gebruiker horen (want je hebt immers van verschillende gebruikers een kaartje in de kaartenbak voor Twitter-gebruikers). Een oplossing hiervoor is om een touwtje te knopen tussen elke Tweet en de Twitter-gebruiker die bij die Tweet hoort (er zit inderdaad een gaatje bovenin elke kaart in de kaartenbak waar je de touwtjes aan vast kan knopen). Als je dan een kaart uit de Twitter-gebruikers kaartenbak optilt, dan zie je door middel van de touwtjes welke Tweets er bij die Twitter-gebruiker horen. Hoe je dit verhaal van de touwtjes knopen vertaalt naar Databases wordt je uitgelegd in de volgende DevTutorial.
De Twitter class klaar maken voor zijn eigen tabel
Voordat je begint met de Twitter class klaarmaken voor het opslaan in zijn eigen tabel, wil je nadenken welke gegeven je wilt opslaan. Dit zijn sowieso de gegevens die je al in je TwitterUser class had:
Nu komt het leuke van OrmLite: je kan aangeven dat je deze gegevens wilt opslaan door middel van annotaties. Een annotatie (annotation) is een soort notitie bij je code die gebruikt wordt om extra eigenschappen toe te schrijven aan (onder andere) je variabelen. Een annotatie gebruik je door ‘@‘ te schrijven boven het definiëren van de variabele, en daarachter de naam van de annotatie te schrijven. De notatie die je moet gebruiken om aan te geven dat je een variabele wil opslaan in een tabel is de DatabaseField annotatie die in de package com.j256.ormlite.field.DatabaseField zit.
[gist id=1290047] Zoals net beschreven wil je ook een id, een unieke code, hebben voor elk TwitterUser object. Je kan hiervoor mUserName gebruiken, maar soms zijn bepaalde letters van gebruikersnaam in hoofdletters en soms juist niet. Als je dan wil zoeken naar een bepaalde Twitter-gebruiker weet je niet welke letters uit de naam met hoofdletters zijn. Voor het opslaan van de id kan je als oplossing een extra String onthouden die altijd de gebruikersnaam in kleine letters opslaat. Als er dan via onze Twitter-app gezocht wordt op een gebruikersnaam, veranderen we de zoekopdracht eerst naar kleine letters en zoeken we daarna verder. Hierdoor zoek je op alle gebruikersnamen en maakt het niet uit of de gebruiker hoofdletters en/of kleiner letters gebruikt. Om dit te doen willen we de gebruikersnaam in kleine letters op kunnen slaan. Dit moet in een String object gebeuren, deze String geven we de naam mId. Om aan OrmLite te laten weten dat je deze variabele als id wil gebruiken moet je een parameter meegeven in de annotatie. Het speciale aan annotaties is dat je parameters altijd een naam geeft. In dit geval heeft de parameter de naam id en is het een boolean die je de waarde true of false kan geven. Voeg deze code toe aan het begin van je class block van je TwitterUser class: [gist id=1290054] Je hoeft voor deze variabele geen get-method te maken. Maar je moet er wel voor zorgen dat deze variabele gevuld wordt in je constructor. Hiervoor moet je de volgende regel code toevoegen in je constructor: [gist id=1290060] Omdat we de TwitterUsers opslaan en bij het volgende keer opstarten van de app weer op kunnen vragen, moeten we bijhouden hoe oud de gegevens zijn die bij het TwitterUser object horen. Hiervoor gebruiken we een long, dat is een getal dat erg grote afgeronde getallen kan opslaan. Voeg deze code toe bovenin je class block: [gist id=1290057] Voor het opslaan van dit getal gebruiken we de statische methode elapsedRealTime van de SystemClock class, dat getal geeft aan hoeveel milliseconden het geleden was dat dit specifieke Androidapparaat voor het eerst was opgestart. Wanneer we op een bepaald moment dit getal opvragen en we krijgen het getal 8888810000 terug en een tijdje daarna vragen we weer dit getal op en we krijgen 8888840000 dan weten we dat er 30 seconden zaten tussen het opvragen van deze twee getallen. Door het eerste getal op te slaan in de database (via het TwitterUser object) bij het aanmaken van een TwitterUser object en op elk willekeurig moment een nieuw getal op te vragen kunnen we uitrekenen hoe oud de gegevens zijn die bij een bepaald TwitterUser object horen. Voeg deze code toe in de constructor van je TwitterUser class: [gist id=1290063] En maak ook ergens onderin je class block een get-method aan voor deze variabele: [gist id=1290056] Het laatste wat je moet doen voordat je class ‘OrmLite-Ready’ is, is ervoor zorgen dat je class een constructor heeft zónder parameters. Dit heeft OrmLite nodig als het automatisch de objecten gaat laden vanaf je SQLite database.
[gist id=1290088] Nu hebben we via de annotaties genoeg informatie toegevoegd aan je class zodat OrmLite hem kan verwerken tot een tabel in je database. Voor de verwerking hiervan moet je de OrmLiteSqliteOpenHelper class subclassen, zie de volgende paragraaf.
OrmLiteSqliteOpenHelper: de verbinding tussen je classes en je database inclusief de tabellen
De OrmLiteSqliteOpenHelper class vormt de verbinding tussen de SQLite database op je Androidtelefoon en de classes die je gebruikt in je app. Maak eerst een nieuwe class aan met als superclass de OrmLiteSqliteOpenHelper class:
[gist id=1290111]
De constructor die je net hebt toegevoegd wordt aangeroepen als deze class wordt aangemaakt, dat is dus wanneer er vanuit de app behoefte is om gegevens weg te schrijven naar de SQLite-database of juist uit te lezen. Bij het aanmaken van een nieuw Object van het type DatabaseHelper wordt de constructor van de superclass aangeroepen, dat gebeurt met de regel code die begint met super. Daar worden als parameters onder andere de databasenaam en het versienummer van de database meegegeven. Deze twee parameters kan je zelf veranderen door de statische variabelen aan te passen die boven je constructor staan. Je geeft een naam van de database mee zodat je controle hebt over in welk bestand de SQLite database wordt opgeslagen. Het SQLite-bestand komt in een afgesloten map op je Androidapparaat die alleen toegankelijk is voor je jouw Androidapp. Deze map wordt automatisch verwijderd als je de app verwijdert (maar hij blijft staan bij een update van de app). Het meegeven van een versienummer wat je doet via de statische variabele DATABASE_VERSION is belangrijker: hiermee geef je aan of je de annotaties van je TwitterUser class (of een andere class die je gebruikt in OrmLite) hebt aangepast. Zodra je een annotatie aanpast (weghalen, toevoegen, parameters veranderen) of een class toevoegt of verwijdert van OrmLite, moet je het getal DATABASE_VERSION ophogen naar het daaropvolgende getal. Dit heeft ermee te maken dat je niet zomaar je annotaties of classes die voor je OrmLite gebruikt mag aanpassen: als je dat aanpast dan moeten namelijk ook de tabellen worden aangepast. Bij SQLite betekent dat in het eenvoudigste geval dat je database opnieuw moet worden opgebouwd, zodat hij de nieuwe annotaties en classes kan opslaan. Doe je dit niet dan krijg je bijvoorbeeld deze fout: [gist id=1290129] De fout hierboven kreeg ik toen ik vergeten was het getal op te hogen en mLastUpdate had toegevoegd (ik was toen bezig met de code van deze DevTutorial). Dit is alles wat je moet onthouden over de constructor van deze class. De eerste method die automatisch voor je is gegenereerd is de onCreate method. Deze method wordt aangeroepen als er een nieuwe database wordt aangemaakt. Dit aanmaken gebeurt als er nog geen SQLite database was. In deze method moet je de tabellen aanmaken die je nodig hebt voor je database. In dit geval moet je de tabel aanmaken voor je TwitterUser class. Het aanmaken van een tabel doe je met deze code: [gist id=1290141] De enige regel code in het try block zorgt voor het aanmaken van de tabel voor de TwitterUser class. Wat hier gebeurt is dat er een method wordt aangeroepen die voor jou automatisch een tabel aanmaakt. Het is nu niet belangrijk waar de method vandaan komt of wat connectionSource doet. Je moet wel begrijpen dat als tweede parameter van deze method-aanroep het Class type van de TwitterUser class wordt meegeven. Door de naam van de class te schrijven en daarachter .class te gebruiken maak je een verwijzing naar de class zelf, zonder er een object of iets anders van te maken. Hierdoor krijg je een object van het type Class<T> (in dit geval van het type Class<TwitterUser>) en dit object bevat informatie over de class zelf: onder andere welke variabelen er zijn en welke annotaties daarbij zijn geschreven. Dit wordt dus gebruikt door OrmLite om automatisch een tabel voor je TwitterUser class aan te maken. Dit alles is omringd door een try catch block waar in het catch block een nieuwe Exception wordt aangemaakt. Een Exception van het type RuntimeException hoef je niet op te vangen met een catch block, dit zorgt ervoor dat je hiermee je app kan laten crashen. Dit doe je meestal alleen bij ernstige fouten: bijvoorbeeld als het aanmaken van je database niet goed gaat (een vrij essentieel iets).
Ongeveer hetzelfde geldt voor de method onUpgrade. Deze method wordt aangeroepen als je het getal DATABASE_VERSION hebt opgehoogd. Voor nu kiezen we ervoor om in deze method alle tabellen te verwijderen (met de method dropTable) en daarna de method onCreate aan te roepen zodat alle tabellen opnieuw worden aangemaakt. Als je in de toekomst je gegevens wil blijven behouden tijdens het veranderen van DATABASE_VERSION moet je in deze method je code schrijven die de tabellen aanpast in plaats van ze verwijdert.
[gist id=1290155] Je weet bijna het broodnodige over OrmLite. Het laatste is de Dao<T, ID> interface.
De Dao<T, ID> interface
De Dao<T, ID> interface draait om het opvragen en bewerken van objecten van classes die je opslaat in de SQLite-database. Zoals je van de vorige DevTutorial hebt geleerd, is een interface een beschrijving van welke methods er in een class aanwezig moeten zijn. Zodra je weet dat een object waar je mee werkt een interface implementeert, weet je welke methods je er sowieso op kan aanroepen, zonder dat je weet welk object het nou precies is. Zoals je ziet kan je ook bij de Dao<T, ID> interface aangeven dat je hem met een type van een bepaalde class wilt gebruiken, maar in dit geval gaat het om het aangeven van twee types: T en ID. Het type T betekent het type van het object dat je wil opvragen/bewerken met de Dao<T, ID> interface. Dus zodra je een object hebt die ook de interface Dao<TwitterUser, ID> implementeert, dan heb je onder andere de method create tot je beschikking: Maar omdat je voor type T als type TwitterUser gebruikt, verandert deze method in de volgende method: Op dezelfde manier heeft de Dao<T, ID> interface ook methods voor het verwijderen (delete) en aanpassen (update) van een object uit de database. Een andere belangrijke method van de Dao<T, ID> interface is de method queryForId: Deze method gebruik je door het id van het object wat je wil opvragen mee te geven. Het resultaat van deze method is het object dat je aan de hand van zijn id hebt opgevraagd. Het id is, zoals je daarstraks las, de unieke code voor een object waar je mee werkt in een tabel. Voor het TwitterUser object hebben we ingesteld dat de id een String is waar we de Twitter-gebruikersnaam in kleine letters in opslaan. Als we een een Dao<T, ID> gebruiken als Dao<TwitterUser, String> dan krijg je voor queryForId de volgende method: Wanneer je dus een Dao<T, ID> interface gebruikt, dan geef je als extra types mee het type T van de class waarvoor je objecten van de SQLite database wil opvragen en het type ID dat beschrijft van welk type de id (identificatie) variabele is die bij de class hoort. Als je een Dao<T, ID> wilt gebruiken met de TwitterUser class, dan moet je werken met de Dao<TwitterUser, String> variant van de Dao<T, ID> interface. De Dao’s kan je aanmaken in je DatabaseHelper class.
[gist id=1290222]
Daarna moet je een method aanmaken die deze Dao teruggeeft en aanmaakt als dat nodig is. Het aanmaken gebeurt via de method getDao van OrmLiteSqliteOpenHelper class waar je wederom een beschrijving van de TwitterUser class meegeeft via het Class<T> object.
[gist id=1290230]
Voeg ook deze code toe in je class block die ervoor zorgt dat je mTwitterUsersDao object op null wordt gezet als de DatabaseHelper gesloten wordt, dit zorgt voor een betere benutting van het geheugen op je Androidapparaat: [gist id=1290238] Nu is de class DatabaseHelper klaar voor gebruik.
Dao<TwitterUser, String> gebruiken in MainActivity
Om de DatabaseHelper te gebruiken in je Activity kan je het beste hiervoor een variabele aanmaken in je class block van je MainActivity class.
[gist id=1290240] Als je een object van de DatabaseHelper class wil hebben, moet je dat niet doen via de constructor van die class, maar via de statische getHelper method van de OpenHelperManager class. Die method zorgt ervoor dat er niet onnodig meerdere DatabaseHelper objecten worden aangemaakt. Dit zou namelijk teveel geheugen kosten. Voor het aanmaken van een DatabaseHelper object heeft de getHelper method een Context object nodig en een Class<T> object die een beschrijving is van de DatabaseHelper class. Als Context object kan je het woordje this gebruiken dat verwijst naar de huidige class (de Activity class is een indirecte subclass van de Context class).
[gist id=1290245] Om goed om te gaan met het geheugen van het Androidapparaat kan je verder nog het beste het object mDatabaseHelper weer leeg maken als je hem niet meer nodig hebt, anders blijft hij onnodig lang rondzweven. Hiervoor kan je de method onDestroy() gebruiken van de Activity class. Deze method wordt aangeroepen zodra je Activity wordt afgesloten. Als je deze method overschrijft (‘override’) dan wordt je method uitgevoerd als je Activity wordt afgesloten. Je moet niet vergeten de method onDestroy van de superclass nog aan te roepen in je method. Anders krijg je namelijk een foutmelding omdat in de method onDestroy van de superclass nog essentiële code moet worden uitgevoerd om de Activity netjes af te sluiten.
[gist id=1290252] Nu moet je een aparte method in je Activity aanmaken die het gedownloade TwitterUser object kan opslaan in je SQLite database.
[gist id=1290266]
[gist id=1290270]
[gist id=1290277]
[gist id=1290279]
[gist id=1290283] Je TwitterUser object wordt vanaf nu netjes opgeslagen in je SQLite database. Het laatste wat je moet doen is voordat het downloaden wordt gestart, kijken of je nog een TwitterUser in je SQLite database hebt. Als je die hebt en hij is niet ouder dan tien minuten (1000 milliseconden * 60 seconden * 10 minuten = 600000 milliseconden), dan hoeft er niet gedownload te worden en wordt de gevraagde Twitter-gebruiker meteen getoond vanuit je SQLite database.
[gist id=1290296]
[gist id=1290298]
[gist id=1290301] Als er al een TwitterUser aanwezig was in de SQLite database, dan is het object twitterUser van het type TwitterUser gevuld met een TwitterUser. Bestond er nog geen TwitterUser met de gevraagde username, dan heeft twitterUser de waarde null. In een if block kan je vragen of twitterUser null is of niet. Is dat niet het geval, dan kan je vragen of twitterUser ouder is dan 10 minuten. Dat kan je doen met de volgende regel: [gist id=1290307]
Doordat eerst gevraagd wordt of twitterUser niet de waarde null heeft, en daarna pas de leeftijd van de twitterUser wordt opgevraagd, kan altijd de leeftijd worden opgevraagd. Als twitterUser de waarde null heeft, dan wordt namelijk niet geprobeerd de tweede vraag te beantwoorden. Als je de vragen tussen de haakjes ‘(‘ en ‘)‘ zou omkeren, dan wordt soms ook gevraagd aan twitterUser wat zijn leeftijd is terwijl hij dan de waarde null heeft. In dat geval crashet je app. Vandaar dat deze vragen in deze specifieke volgorde staan. |
Als twitterUser inderdaad gevuld is en hij is niet ouder dan tien minuten, dan mag twitterUser toegewezen worden aan mTwitterUser en mag updateView worden aangeroepen. Is dit niet het geval dan wordt het ‘normale’ downloadproces opgestart inclusief het tonen van de ProgressDialog.
Uiteindelijk kan je op dit uitkomen voor de method downloadOrShowFromDb: [gist id=1290315] En dit voor de method downloadUserInfo: [gist id=1290313]
Extra oefening
We gaan de AutoCompleteTextView gebruiken zodat er suggesties worden weergegeven als de gebruiker een Twitter-gebruikersnaam invoert. De lijst van suggesties bouwen we op basis van de Twitter-gebruikers die eerder ingevoerd waren:
[gist id=1292791]
[gist id=1292805]
Nu weet je ook hoe je een AutoCompleteTextView kan gebruiken.
Sourcecode en Forum
Als je vragen hebt over deze DevTutorial dan kan je ze stellen aan mij en andere programmeurs in . Als je wil weten hoe de code van deze app er uiteindelijk uit moet zien, kan je hier de sourcecode bekijken op github.
Reacties
Inloggen of registreren
om een reactie achter te laten
danku danku !
waar kan ik alle tutorials opnieuw chekken
Ik zie ernaar uit om vanavond met deze Dev Tutorial aan de slag te gaan. Ik ben wel benieuwd hoe eea zou werken als de de OrmLite package niet hebt. Ik vind het nooit zo leuk om code van anderen te gebruiken. Maar ja, het bespaart wel een hoop werk…
Hey wouter, ik was afgehaakt bij tutorial 3 en had het druk maar bedankt voor de mooie tutorials, leuk dat jij niet afgehaakt bent en door bent gegaan, ik zal de draad weer oppakken.
Respect..!
Leuk, je kaartenbak uitleg! Maakt het kinderlijk eenvoudig om de rest van het verhaal aan te relateren.
Wederom erg veel dank voor de serie!
Erg mooie handleiding weer!
Kijk al uit naar de laatste delen van de serie
Maar onUpdate moet natuurlijk wel onUpgrade zijn 😉