← Terug naar projects
8 min
Hours: een urenregistratie-app voor mijn moeder

Hours: een urenregistratie-app voor mijn moeder

Een Android-app waarmee mijn moeder de uren bijhoudt die ze bij een handvol particuliere huishoudens werkt, en wat dat bij elkaar oplevert. Ze schreef het in haar agenda en zette het aan het eind van de maand in Excel; de app draait dat om en vraagt haar alleen nog te bevestigen wat hij zelf al weet.
  • Kotlin
  • Android
  • Jetpack Compose
  • WebDAV

Inhoudsopgave

  1. Het probleem: alles twee keer opschrijven
  2. De oplossing: bevestigen in plaats van invullen
  3. De back-up
  4. Het technische fundament
  5. Waarvoor ik het heb gebouwd
  6. Huidige status

Mijn moeder maakt schoon bij een handvol particuliere huishoudens. Na elk bezoek schreef ze in haar agenda hoe lang ze er geweest was, en aan het eind van de maand ging ze achter de computer zitten om het allemaal in een Excel-bestand te zetten. Dat deed ze jarenlang, en zo’n ronde kostte haar telkens een avond.

Die avond is het probleem, niet het bijhouden. Toen ik het bestand opende, zag ik wat een spreadsheet die je met de hand onderhoudt er op den duur mee doet. Tot mei stonden de bedragen als formule in de kolom, daarna waren het met de hand ingetypte getallen. Er stonden twee totaalrijen in met verschillende bereiken, en in één daarvan een #REF!. Dat is geen slordigheid. Zo ziet een bestand eruit na een paar jaar handmatig uitbreiden, en je ziet het pas als je er gericht naar zoekt.

Het probleem: alles twee keer opschrijven

De voor de hand liggende oplossing is een nette spreadsheet, met de formules hersteld en de totalen gecorrigeerd. Daarmee krijgt ze die avond niet terug. Het werk wordt nog steeds twee keer opgeschreven: één keer in haar agenda, vlak na het bezoek, en één keer weken later achter de computer. Die eerste keer hoort erbij. De tweede is overtypen, en daar gaat de tijd in zitten.

Wat me opviel toen ik de gegevens doornam, is hoe regelmatig het werk is. Dezelfde klanten, op dezelfde weekdagen, voor bijna hetzelfde aantal uren, tegen hetzelfde tarief. De één is elke maandag en vrijdag, de ander om de donderdag. Van de 121 bezoeken in dat eerste halfjaar week maar een handvol af van het patroon.

Daar is de app op gebouwd. Als deze week op vorige week lijkt, hoeft de app niets te vragen. Hij vult de dag zelf in, en zij past alleen aan wat anders was.

De oplossing: bevestigen in plaats van invullen

De app opent op Registreer, en de dag van vandaag staat er al ingevuld. Iedere klant die deze weekdag als vaste dag heeft, staat op het scherm met de gebruikelijke uren en het uitgerekende bedrag. Eén tik op Opslaan en de dag is klaar. Duurde het langer, dan schuiven − en + het aantal met een kwartier op, en het veld zelf accepteert elk getal, dus 3,4 uur invullen is net zo makkelijk als 3,75.

Registreren, het overzicht met maandgrafiek, en de klantinstellingen

Registreren, het overzicht met de maandgrafiek, en de klanten in de instellingen.

Boven de kaarten staat een weekstrook met een stipje op elke dag waarop al iets ingevuld is. Je kunt naar elke week scrollen, dus een paar dagen bijwerken is één tik per dag in plaats van telkens een datumkiezer.

Het Overzicht geeft per maand of per jaar het totaal, het aantal keren, de uren en wat het gemiddeld per uur opleverde, met een staafgrafiek per maand en een verdeling per klant. Op een regel tikken springt naar die dag, waar hij te corrigeren is.

Een tarief verhogen mag het verleden niet herschrijven

Eén ding stond vanaf het begin vast: elk bezoek bewaart het tarief en de vergoeding zoals die golden op het moment van invullen. Verhoogt een klant het tarief, dan telt dat vanaf het volgende bezoek. Wat er al staat, blijft staan.

Dat klinkt vanzelfsprekend, maar het is niet wat een spreadsheet doet. Een kolom vol =C4*15 herschrijft een heel jaar zodra je het uurtarief aanpast, en dat merk je pas rond de belastingaangifte, als de aangifte niet meer aansluit.

De back-up

Alles staat op één telefoon. Raakt die kwijt of gaat hij kapot, dan is de administratie weg. Android heeft weliswaar een eigen back-upvoorziening, maar die zet alleen iets terug bij het inrichten van een nieuw toestel, en een vergissing van vanmiddag krijg je er niet mee ongedaan. Dus stuurt de app zijn eigen kopie weg.

Een eigen API met een database erachter vond ik te veel voor iets van deze omvang. In mijn homelab draait al een Nextcloud, en Nextcloud praat WebDAV, dus de plek om de back-up neer te zetten was er al.

Elke upload is een volledige momentopname, als JSON weggeschreven naar een WebDAV-map. Er is geen slimme synchronisatie. Een decennium aan uren blijft ruim onder een megabyte, dus is elk bestand op zichzelf compleet. Daarmee is de serverkant niet meer dan een map, en terugzetten niet meer dan een bestand kiezen.

Hij verstuurt op twee momenten: één keer per dag, zodat een telefoon waar niemand aan komt zich toch meldt, en een kwartier na een wijziging, zodat een avond aanpassen op één upload uitloopt in plaats van vijf. Staat er werk op de telefoon dat langer dan een uur niet is weggeschreven, dan zegt het registratiescherm dat, met een knop naast de melding. Anders merk je pas dat de back-up niet meer loopt op het moment dat je hem nodig hebt.

De map wordt niet ingetypt maar op de server opgezocht, met een knop om er een aan te maken. En fouten komen terug als zinnen in plaats van statuscodes: de gebruikersnaam of het app-wachtwoord klopt niet zegt tegen wie de telefoon vasthoudt welk veld hij moet aanpassen, en HTTP 401 doet dat niet.

Twee aandachtspunten bij WebDAV op Android

Java’s HttpURLConnection weigert elk werkwoord dat niet in HTTP/1.1 staat, en WebDAV heeft juist PROPFIND en MKCOL nodig. De methodenaam moet daarom rechtstreeks in het veld van het object gezet worden, want de setter wijst hem af.

De tweede kostte meer tijd om te vinden. Over https geeft Android geen gewone verbinding terug, maar een omhulsel dat alles doorstuurt naar een tweede object. De methode alleen op dat omhulsel zetten laat de onderliggende verbinding op POST staan, en dan antwoordt de server met 405. Over http werkte het dus wel en over https niet, wat het opsporen er niet makkelijker op maakte. De oplossing is de keten van doorverwijzingen aflopen en ze allemaal zetten.

Het technische fundament

Hours is een native Android-app in Kotlin, met Jetpack Compose en Material 3 voor de interface en een MVVM-opzet met een repository-laag. Klanten en bezoeken staan lokaal in een Room-database, WorkManager verzorgt de geplande upload, en Gson schrijft het back-upformaat. Het geheel is tweetalig, Nederlands en Engels, en volgt de taal van het toestel.

Onderweg liep ik tegen twee dingen aan die ik niet zag aankomen. Het eerste is dat een release-build met R8 de veldnamen van je datamodel hernoemt, waardoor Gson het meegeleverde startbestand niet meer kon lezen en de app bij de allereerste start crashte, uitsluitend in release. Het tweede is dat het bouwen van een NumberFormat duur genoeg is om merkbaar te zijn wanneer je het per lijstregel doet: het overzicht ging van 6,97% naar 1,95% haperende beeldjes door die instanties te hergebruiken en de datums en klanten één keer per wijziging klaar te zetten in plaats van per beeldje.

Waarvoor ik het heb gebouwd

Net als bij Garen Collectie ging dit project niet over de techniek. Er is één gebruiker. De avonden achter Excel zijn weg, de totalen rollen nu rechtstreeks uit wat ze die dag heeft ingevuld, en mijn moeder is er blij mee.

De 121 regels uit de oude spreadsheet heb ik eenmalig ingelezen, dus vanaf het eerste openen stond het hele jaar er al in, tot op de cent gelijk aan wat ze zelf had bijgehouden. Wat in de repo staat is verzonnen voorbeelddata; haar eigen gegevens blijven op haar telefoon en in haar back-up.

Huidige status

De app is af en in gebruik. De back-up draait en de uren staan erin, dus de spreadsheet is niet meer nodig.

Hij is open source, dus wie hem zelf wil draaien kan de code van GitHub halen en bouwen. Je gooit dan het startbestand met voorbeelddata eruit en vult je eigen klanten, tarieven en WebDAV-gegevens in. De app is voor één situatie gemaakt, maar het idee erachter (vaste dagen, en een voorstel dat je alleen hoeft te bevestigen) past op elk werk dat zich elke week herhaalt. Wie daar iets anders van wil maken hoeft niet bij nul te beginnen.