Maak een Weight Tracker-app met Cloud Firestore

Het opslaan van de gegevens van uw app in de cloud is tegenwoordig erg belangrijk omdat gebruikers de neiging hebben om meerdere apparaten te bezitten en willen dat hun apps over al deze apparaten worden gesynchroniseerd. Met Cloud Firestore, een real-time NoSQL-database die beschikbaar is op het Firebase-platform, is het eenvoudiger en veiliger dan ooit tevoren.

In een eerdere zelfstudie heb ik je laten kennismaken met alle krachtige functies die Cloud Firestore te bieden heeft. Vandaag laat ik u zien hoe u het naast andere Firebase-producten, zoals FirebaseUI Auth en Firebase Analytics, kunt gebruiken om een ​​eenvoudige, maar zeer schaalbare app voor gewichtstracker te maken.

voorwaarden

Als u deze stapsgewijze zelfstudie wilt volgen, heeft u het volgende nodig:

  • de nieuwste versie van Android Studio
  • een Firebase-account
  • en een apparaat of emulator met Android 5.0 of hoger

1. Projectinstelling

Om gebruik te kunnen maken van Firebase-producten in uw Android Studio-project, heeft u de Google Services Gradle-plug-in, een Firebase-configuratiebestand en enkele implementatie afhankelijkheden. Met de Firebase-assistent kun je ze allemaal heel gemakkelijk krijgen.

Open de assistent door naar te gaan Hulpmiddelen> Firebase. Selecteer vervolgens de Analytics optie en klik op de Log een Analytics-evenement link.

U kunt nu op de Maak verbinding met Firebase om uw Android Studio-project aan een nieuw Firebase-project te koppelen.

Echter, om de plug-in daadwerkelijk toe te voegen en de implementatie afhankelijkheden, moet u ook op de Voeg Analytics toe aan uw app knop.

De weight tracker-app die we vandaag maken, heeft maar twee functies: gewichten opslaan en weergeven als een lijst in omgekeerde chronologische volgorde. We zullen Firestore natuurlijk gebruiken om de gewichten op te slaan. Om ze als een lijst weer te geven, gebruiken we Firestore-gerelateerde componenten die beschikbaar zijn in de FirebaseUI-bibliotheek. Voeg daarom het volgende toe implementatie afhankelijkheid van de app module build.gradle het dossier:

implementatie 'com.firebaseui: firebase-ui-firestore: 3.2.2'

Gebruikers moeten alleen hun eigen gewichten kunnen bekijken, niet het gewicht van iedereen die de app gebruikt. Daarom moet onze app de mogelijkheid hebben om gebruikers op unieke wijze te identificeren. FirebaseUI Auth biedt deze mogelijkheid, dus voeg de volgende afhankelijkheid vervolgens toe:

implementatie 'com.firebaseui: firebase-ui-auth: 3.2.2'

We hebben ook enkele Material Design-widgets nodig om onze app een aangename uitstraling te geven. Zorg er dus voor dat u de Design Support-bibliotheek en de Material Dialogs-bibliotheek als afhankelijkheden toevoegt.

implementatie 'com.android.support:design:26.1.0' implementatie 'com.afollestad.material-dialogen: kern: 0.9.6.0'

Druk ten slotte op Synchroniseer nu knop om het project bij te werken.

2. Firebase-verificatie configureren

Firebase-verificatie ondersteunt verschillende identiteitsproviders. Ze zijn echter allemaal standaard uitgeschakeld. Als u een of meer ervan wilt inschakelen, moet u de Firebase-console bezoeken.

Selecteer in de console het Firebase-project dat u in de vorige stap hebt gemaakt, ga naar het bijbehorende authenticatie sectie en druk op de Inlogmethode instellen knop.

Om gebruikers toe te staan ​​zich met een Google-account bij onze app aan te melden, moet u inschakelen Google geef als provider een betekenisvolle openbare naam aan het project en druk op Opslaan knop.

Google is de gemakkelijkste identiteitsprovider die u kunt gebruiken. Het heeft geen configuratie nodig en uw Android Studio-project heeft hiervoor geen extra afhankelijkheden nodig.

3. Cloud Firestore configureren

U moet Firestore inschakelen in de Firebase-console voordat u het gaat gebruiken. Om dit te doen, gaat u naar Database sectie en druk op de Begin knop aanwezig in de Cloud Firestore Beta kaart.

U wordt nu gevraagd om een ​​beveiligingsmodus voor de database te selecteren. Zorg ervoor dat u de Start in de vergrendelde modus optie en druk op de in staat stellen knop.

In de vergrendelde modus kan niemand standaard de inhoud van de database openen of wijzigen. Daarom moet u nu een beveiligingsregel maken waarmee gebruikers alleen die documenten kunnen lezen en schrijven die bij hen horen. Begin met het openen van de Reglement tab.

Voordat we een beveiligingsregel voor onze database maken, moeten we afronden hoe we de gegevens erin gaan opslaan. Laten we zeggen dat we een verzameling op het hoogste niveau zullen krijgen gebruikers met documenten die onze gebruikers vertegenwoordigen. De documenten kunnen unieke ID's hebben die identiek zijn aan de ID's die de Firebase-verificatieservice genereert voor de gebruikers.

Omdat de gebruikers verschillende gewichtsvermeldingen aan hun documenten toevoegen, is het gebruik van een subcollectie om die items op te slaan ideaal. Laten we de subcollectie noemen gewichten.

Op basis van het bovenstaande schema kunnen we nu een regel voor het pad maken gebruikers / user_id / gewichten / gewicht. De regel is dat een gebruiker alleen mag lezen van en schrijven naar het pad als het gebruikersnaam variabele is gelijk aan de Firebase-verificatie-ID van de gebruiker.

Werk daarom de inhoud van de regelseditor bij.

service cloud.firestore match / databases / database / documenten match / users / user_id / weights / weight allow read, write: if user_id == request.auth.uid; 

Druk ten slotte op Publiceren om de regel te activeren.

4. Authenticatie van gebruikers

Onze app moet alleen bruikbaar zijn als de gebruiker is ingelogd met een Google-account. Daarom moet het, zodra het wordt geopend, controleren of de gebruiker over een geldig Firebase-verificatie-ID beschikt. Als de gebruiker de ID heeft, moet deze doorgaan en de gebruikersinterface weergeven. Anders moet een aanmeldscherm worden weergegeven.

Om te controleren of de gebruiker een ID heeft, kunnen we eenvoudig controleren of de huidige gebruiker eigendom van de FirebaseAuth klasse is niet nul. Als het null is, kunnen we een intentiedoel maken door het createSignInIntentBuilder () methode van de AuthUI klasse.

In de volgende code ziet u hoe u dit doet voor Google als identiteitsprovider:

if (FirebaseAuth.getInstance (). currentUser == null) // Aanmelden startActivityForResult (AuthUI.getInstance (). createSignInIntentBuilder () .setAvailableProviders (arrayListOf (AuthUI.IdpConfig.GoogleBuilder (). build ())). build ( ), 1) else // Al aangemeld in showUI ()

Merk op dat we een methode met de naam noemen ShowUI () als een geldige ID al aanwezig is. Deze methode bestaat nog niet, dus maak het en laat zijn lichaam leeg voor nu.

private fun showUI () // Te doen

Om het resultaat van de inlogintentie te vangen, moeten we de. Intikken onActivityResult () methode van de activiteit. Binnen de methode, als de waarde van de resultCode argument is RESULT_OK en de huidige gebruiker eigenschap is niet langer nul, dit betekent dat de gebruiker erin geslaagd is om succesvol in te loggen. In dit geval moeten we opnieuw de ShowUI () methode om de gebruikersinterface weer te geven.

Als de gebruiker niet inlogt, kunnen we een toast weergeven en de app sluiten door te bellen naar af hebben() methode.

Voeg de volgende code toe aan de activiteit:

override plezier onActivityResult (requestCode: Int, resultCode: Int, data: Intent?) super.onActivityResult (requestCode, resultCode, data) if (requestCode == 1) if (resultCode == Activity.RESULT_OK && FirebaseAuth.getInstance () .currentUser! = null) // Succesvol aangemeld showUI () else // Aanmelden mislukt Toast.makeText (dit, "U moet inloggen om door te gaan", Toast.LENGTH_LONG) .show () voltooien () 

Als u de app voor de eerste keer uitvoert, ziet u op dit moment een inlogscherm dat er als volgt uitziet:

Bij volgende runs, dankzij Google Smart Lock, dat standaard is ingeschakeld, wordt u automatisch aangemeld.

5. Lay-outs definiëren

Onze app heeft twee lay-outs nodig: één voor de hoofdactiviteit en één voor de gewichtsitems die worden weergegeven als items van de omgekeerde chronologisch geordende lijst.

De lay-out van de hoofdactiviteit moet een RecyclerView widget, die als de lijst zal fungeren, en a FloatingActionButton widget, die de gebruiker kan indrukken om een ​​nieuw gewichtsitem te maken. Nadat ze beide in een Relatieve layout widget, het lay-out XML-bestand van uw activiteit zou er als volgt uit moeten zien:

     

We hebben een on-click gebeurtenishandler gekoppeld aan de naam addWeight () met de FloatingActionButton widget. De handler bestaat nog niet, dus maak er een snufje van in de activiteit.

fun addWeight (v: View) // Te doen

Om de indeling van de gewichtsinvoer eenvoudig te houden, hebben we er slechts twee Tekstweergave widgets erin: één om het gewicht weer te geven en de ander om het tijdstip weer te geven waarop het item is gemaakt. Met behulp van een LinearLayout widget als een container voor hen volstaat.

Maak daarom een ​​nieuw XML-lay-outbestand met de naam weight_entry.xml en voeg de volgende code eraan toe:

    

6. Een model maken

In de vorige stap zag je dat aan elke gewichtsinvoer een gewicht en tijd is gekoppeld. Om Firestore hiervan op de hoogte te stellen, moeten we een model maken voor de gewichtsinvoer.

Firestore-modellen zijn meestal eenvoudige gegevensklassen met de vereiste lidvariabelen.

dataklasse WeightEntry (var weight: Double = 0.0, var timestamp: Long = 0)

Het is ook een goed moment om een ​​weergavehouder te maken voor elke gewichtsinvoer. De view-houder, zoals u misschien al geraden heeft, wordt gebruikt door de RecyclerView widget om de lijstitems weer te geven. Dus maak een nieuwe klasse genaamd WeightEntryVH, waardoor de RecyclerView.ViewHolder klasse en maak lidvariabelen voor beide Tekstweergave widgets. Vergeet niet om ze te initialiseren met behulp van de findViewById () methode. De volgende code laat u zien hoe u kernachtig kunt doen:

class WeightEntryVH (itemView: View?): RecyclerView.ViewHolder (itemView) var weightView: TextView? = itemView? .findViewById (R.id.weight_view) var timeView: TextView? = itemView? .findViewById (R.id.time_view)

7. Unieke gebruikersdocumenten creëren

Wanneer een gebruiker voor het eerst een gewichtsvermelding probeert te maken, moet onze app een afzonderlijk document maken voor de gebruiker binnen het gebruikers verzameling op Firestore. Zoals we eerder hebben besloten, zal de ID van het document niets anders zijn dan de Firebase-authenticatie-ID van de gebruiker, die kan worden verkregen met behulp van de uid eigendom van de huidige gebruiker voorwerp.

Om een ​​verwijzing naar de te krijgen gebruikers verzameling, we moeten de verzameling() methode van de FirebaseFirestore klasse. We kunnen het dan noemen document() methode en sla de uid als een argument om het document van de gebruiker te maken.

We zullen toegang moeten hebben tot de gebruikerspecifieke documenten, zowel tijdens het lezen als het creëren van de gewichtsgegevens. Om te voorkomen dat bovenstaande logica twee keer wordt gecodeerd, raad ik u aan hiervoor een afzonderlijke methode te maken.

private fun getUserDocument (): DocumentReference val db = FirebaseFirestore.getInstance () val users = db.collection ("users") val uid = FirebaseAuth.getInstance (). currentUser !!. uid return users.document (uid)

Houd er rekening mee dat het document slechts één keer per gebruiker wordt gemaakt. Met andere woorden, meerdere oproepen naar de bovenstaande methode retourneren altijd hetzelfde document, zolang de gebruiker hetzelfde Google-account gebruikt.

8. Gewichtsgegevens toevoegen

Wanneer gebruikers op de zwevende actieknop van onze app drukken, moeten ze nieuwe gewichtsvermeldingen kunnen maken. Om hen in staat te stellen hun gewichten in te voeren, kunnen we nu een dialoogvenster maken met een Tekst bewerken widget. Met de Material Dialog-bibliotheek is dit uiterst intuïtief.

Binnen in de addWeight () methode, die dient als de on-click event-handler van de knop, maakt u een MaterialDialog.Builder voorbeeld en bel zijn titel() en inhoud() methoden om uw dialoog een titel en een zinvolle boodschap te geven. Op dezelfde manier belt u de inputType () methode en doorgeven TYPE_CLASS_NUMBER als argument om ervoor te zorgen dat de gebruiker alleen getallen in het dialoogvenster kan typen.

Bel vervolgens de invoer() methode om een ​​hint op te geven en een gebeurtenishandler aan het dialoogvenster te koppelen. De handler ontvangt het gewicht dat de gebruiker heeft getypt als argument.

Zorg er ten slotte voor dat u de laten zien() methode om het dialoogvenster weer te geven.

MaterialDialog.Builder (this) .title ("Gewicht toevoegen") .content ("Wat is uw gewicht vandaag?") .InputType (InputType.TYPE_CLASS_NUMBER of InputType.TYPE_NUMBER_FLAG_DECIMAL) .input ("weight in pounds", "", false, _, gewicht -> // Taken) .show ()

Binnen de gebeurtenishandler moeten we nu code toevoegen om daadwerkelijk een nieuw gewichtinvoerdocument te maken en te vullen. Omdat het document moet behoren tot de gewichten verzameling van het unieke document van de gebruiker, om toegang tot de verzameling te krijgen, moet u het verzameling() methode van het document dat wordt geretourneerd door de getUserDocument () methode.

Zodra u de verzameling heeft, kunt u deze bellen toevoegen() methode en geef een nieuw exemplaar van de WeightEntry klasse om het item op te slaan.

getUserDocument () .collection ("weights") .add (WeightEntry (weight.toString (). toDouble (), Date (). time))

In de bovenstaande code kunt u zien dat we de gebruiken tijd eigendom van de Datum class om een ​​tijdstempel te associëren met het item.

Als u de app nu uitvoert, moet u nieuwe gewichtsitems kunnen toevoegen aan Firestore. Je ziet ze nog niet in de app, maar ze zullen zichtbaar zijn in de Firebase-console.

9. De gewichtsgegevens weergeven

Het is nu tijd om de RecyclerView widget van onze lay-out. Dus begin met het maken van een referentie voor het gebruik van de findViewById () methode en toewijzen van een nieuw exemplaar van de LinearLayoutManager klasse eraan. Dit moet worden gedaan binnen de ShowUI () methode die we eerder hebben gemaakt.

val weightsView = findViewById(R.id.weights) weightsView.layoutManager = LinearLayoutManager (this)

De RecyclerView widget moet alle documenten tonen die aanwezig zijn in de gewichten verzameling van het document van de gebruiker. Bovendien moeten de nieuwste documenten eerst verschijnen. Om aan deze vereisten te voldoen, moeten we nu een query maken door de verzameling() en orderBy () methoden.

Omwille van de efficiëntie kunt u het aantal waarden dat door de query wordt geretourneerd beperken door de begrenzing() methode.

Met de volgende code wordt een query gemaakt die de laatste 90 gewichtsitems retourneert die door de gebruiker zijn gemaakt:

val query = getUserDocument (). collection ("weights") .orderBy ("timestamp", Query.Direction.DESCENDING) .limit (90)

Met behulp van de query moeten we nu een maken FirestoreRecyclerOptions object, dat we later zullen gebruiken om de adapter van ons te configureren RecyclerView widget. Wanneer u de vraag bijvoorbeeld naar de setQuery () methode van de bouwer, zorg ervoor dat u specificeert dat de geretourneerde resultaten de vorm hebben van WeightEntry voorwerpen. De volgende code laat zien hoe je dit doet:

val opties = FirestoreRecyclerOptions.Builder() .setQuery (query, WeightEntry :: class.java) .setLifecycleOwner (this) .build ()

U hebt misschien gemerkt dat we onze huidige activiteit de eigenaar van de levenscyclus van de FirestoreRecyclerOptions voorwerp. Dit is belangrijk omdat we willen dat onze adapter op gepaste wijze reageert op algemene levenscyclusgebeurtenissen, zoals het openen of sluiten van de app door de gebruiker.

Op dit punt kunnen we een maken FirestoreRecyclerAdapter object, dat de FirestoreRecyclerOptions object om zichzelf te configureren. Omdat het FirestoreRecyclerAdapter klasse is abstract, Android Studio moet automatisch de methoden overschrijven om code te genereren die er als volgt uitziet:

val adapter = object: FirestoreRecyclerAdapter(opties) override plezier opBindViewHolder (houder: WeightEntryVH, positie: Int, model: WeightEntry) // Te doen overschrijf plezier onCreateViewHolder (ouder: ViewGroup ?, viewType: Int): WeightEntryVH // Te doen

Zoals u kunt zien, de FirestoreRecyclerAdapter klasse lijkt erg op de RecyclerView.Adapter klasse. In feite is het daarvan afgeleid. Dat betekent dat je het op dezelfde manier kunt gebruiken als je zou gebruiken RecyclerView.Adapter klasse.

Binnen in de onCreateViewHolder () methode, alles wat je hoeft te doen is de weight_entry.xml lay-outbestand en retourneer a WeightEntryVH bekijk houder object op basis ervan.

val layout = layoutInflater.inflate (R.layout.weight_entry, null) return WeightEntryVH (layout)

En binnen de onBindViewHolder () methode, moet u de model- argument om de inhoud van de update bij te werken Tekstweergave widgets die aanwezig zijn in de view-houder.

Tijdens het updaten van de weightView widget is eenvoudig, het bijwerken van de timeView widget is enigszins ingewikkeld omdat we niet willen dat de tijdstempel, die in milliseconden is, direct aan de gebruiker wordt getoond.

De eenvoudigste manier om de tijdstempel in een leesbare datum en tijd om te zetten, is door de FormatDateTime () methode van de DateUtils klasse. Naast de tijdstempel, kan de methode verschillende vlaggen accepteren, die hij zal gebruiken om de datum en tijd te formatteren. U bent vrij om vlaggen te gebruiken die overeenkomen met uw voorkeuren.

// Show weight holder.weightView? .Text = "$ model.weight lb" // Show datum en tijd val formattedDate = DateUtils.formatDateTime (applicationContext, model.timestamp, DateUtils.FORMAT_SHOW_DATE of DateUtils.FORMAT_SHOW_TIME of DateUtils.FORMAT_SHOW_YEAR ) holder.timeView? .text = "On $ formattedDate"

Tot slot, vergeet niet om de RecyclerView widget naar de adapter die we zojuist hebben gemaakt.

weightsView.adapter = adapter

De app is klaar. Je zou nu in staat moeten zijn om nieuwe items toe te voegen en ze bijna onmiddellijk in de lijst te zien verschijnen. Als u de app uitvoert op een ander apparaat met hetzelfde Google-account, ziet u dezelfde items voor het gewicht erop staan.

Conclusie

In deze zelfstudie zag u hoe snel en eenvoudig het is om een ​​volledig functionele app voor gewichtstracking voor Android te maken met behulp van Cloud Firestore als een database. Aarzel niet om meer functionaliteit toe te voegen! Ik raad ook aan om het op Google Play te publiceren. Met het Firebase Spark-abonnement, dat momenteel 1 GB gratis gegevensopslag biedt, hebt u geen problemen met het bedienen van ten minste een paar duizend gebruikers.

En terwijl je hier bent, bekijk enkele van onze andere berichten over de ontwikkeling van Android-apps!