Aplicație Android pentru închirierea proprietăților rezidențiale [306135]

Aplicație Android pentru închirierea proprietăților rezidențiale

PROIECT DE DIPLOMĂ

Autor: Matei-Marius LAZĂR

Conducător științific: ȘL. Dr. Ing. Mihai HULEA

Autor: Matei-Marius LAZĂR

Aplicație Android pentru închirierea

proprietăților rezidențiale

Enunțul temei: [anonimizat]: [anonimizat], [anonimizat], Titlul capitolului 1, Titlul capitolului 2,… Titlul capitolului n, Bibliografie, Anexe.

Locul documentației: [anonimizat]: ing. Prenume Nume (dacă este cazul)

Data emiterii temei:

Data predării:

Semnătura autorului

Semnătura conducătorului științific

Declarație pe proprie răspundere privind

autenticitatea proiectului de diplomă

Subsemnatul(a) Matei-[anonimizat](ă) cu CI/BI seria SX nr. 383979 , CNP [anonimizat] ,

autorul lucrării:

Aplicație Android pentru închirierea proprietăților rezidențiale

elaborată în vederea susținerii examenului de finalizare a [anonimizat] , [anonimizat], sesiunea a anului universitar 2019-2020, [anonimizat], [anonimizat], și în bibliografie.

Declar, [anonimizat] a convențiilor internaționale privind drepturile de autor.

Declar, [anonimizat] a mai fost prezentată în fața unei alte comisii de examen de licență.

In cazul constatării ulterioare a [anonimizat], respectiv, anularea examenului de licență.

Data Prenume NUME

8,07,2020 Matei-Marius LAZĂR

(semnătura)

SINTEZA

proiectului de diplomă cu titlul:

Aplicație Android pentru închirierea

proprietăților rezidențiale

Autor: Matei-Marius LAZĂR

Conducător științific: ȘL. Dr. Ing. Mihai HULEA

1. Cerințele temei: O [anonimizat], [anonimizat], să își poată modifica profilul și să comunice.

2. Soluții alese: Am ales mediul de programare Android Studio pentru a [anonimizat], am folosit Firebase .

3. Rezultate obținute: S-a [anonimizat]: [anonimizat], [anonimizat], [anonimizat].

4. Testări și verificări: [anonimizat]. [anonimizat] a fost instalată pe mai multe dispozitive pentru a se asigura funcționarea normală a acesteia.

5. Contribuții personale: [anonimizat] o [anonimizat].

6. Surse de documentare: Sursele de documentare sunt menționate în capitolul 6

Semnătura autorului

Semnătura conducătorului științific

Introducere

Context general

În această lucrare am descris implementarea unei aplicații pe platforma Android.

Această aplicație vine în special în ajutorul studenților, când doresc să își caute chirie în orașul Cluj-Napoca.

Scopul aplicației este de a aduce într-un loc majoritatea evenimentelor prin care un student trebuie să treacă atunci când caută să închirieze o proprietate rezidențială.

În acest moment, nu este pe piață o aplicație care să fie dedicată studenților și nevoilor acestora, aceștia fiind obligați să recurgă la mai multe aplicații de tip imobiliar și grupuri pe diferite platforme sociale online pentru a-și găsi chirie.

Numele aplicației este Kariru. Unul dintre motivele pentru care am ales acest nume este pentru că înseamnă „a închiria” în limba japoneză.

Această lucrare este alcătuită din următoarele capitole:

1.      Introducere – familiarizează cititorul cu motivele, obiectivele și funcționalitatea acestei aplicații.

2.      Studiu bibliografic – prezintă mediul de dezvoltare și tehnologiile folosite în dezvoltarea proiectului.

3.      Analiză, proiectare, implementare – detaliază abordarea temei alese, prezintă structura proiectului și metodele de implementare preferate

4.      Testare – prezintă modul în care se poate testa o aplicație Android cu ajutorul mediului de dezvoltare ales

5.      Concluzii – opinii despre potențialul succes pe piață al aplicației și îmbunătățiri

Obiectivele lucrării

În aplicația Kariru, obiectivele pe care doresc să le realizez sunt:

Proprietarii de apartamente să poată să își posteze proprietatea în aplicație pentru a fi vizibilă utilizatorilor

Proprietarii să își poată vedea postările lor

Proprietarii să poată edita postările proprii

Utilizatorii să poată vizualiza toate postările

Utilizatorii să poată filtra postările în funcție de preț

Utilizatorii să poată sorta postările în funcție de preț și numărul de camere, ascendent sau descendent

Utilizatorii să poată accesa detaliile postărilor

Utilizatorii să poată contacta proprietarii

Utilizatorii să își poată edita profilul

Specificații

În această aplicație doresc să obțin următoarele funcționalități:

Înregistrarea unui utilizator

Dacă utilizatorul nu este autentificat, la deschiderea aplicației, acesta va fi direcționat către ecranul de autentificare. Utilizatorul poate alege opțiunea de autentificare cu email, urmând ca apoi să își creeze un cont, dacă emailul nu este deja salvat în baza de date.

Recuperarea parolei

Dacă utilizatorul și-a uitat parola, după ce apasă pe butonul de autentificare cu email, introduce email-ul și apasă “Next”, acesta are opțiunea de a apăsa pe butonul “Trouble signing in?”. Mai departe, utilizatorul poate apăsa pe butonul “Send” pentru a primi pe adresa de email, un mesaj cu un link pe care îl poate folosi să își schimbe parola.

Autentificarea cu email sau contul de Google

In ecranul de autentificare, utilizatorul poate opta intre a se autentifica cu email-ul, daca s-a inregistrat deja in baza de date, sau cu un cont de Google. Daca alege un cont de Google, pe ecran va fi prezentata o lista cu conturile de Google care sunt deja autentificate pe dispozitiv. Dupa ce utilizatorul selecteaza unul dintre ele, nu mai este nevoie sa introduca o parola, Google ocupandu-se de acest lucru automat.

Închiderea sesiunii

Dupa ce utilizatorul are acces in aplicatie, acesta poate alege sa isi incheie sesiunea, inainte de a iesi din aplicatie. Aceasta functionalitate are avantajul de a oferi securitate utilizatorului, deoarece nimeni altcineva nu poate sa acceseze aplicatia de pe acelasi dispozitiv, cu contul utlizatorului.

Pentru aceasta, trebuie apasat pe butonul “Profile” din bara de navigatie. Mai departe, in ecranul care s-a deschis cu profilul utilizatorului, se apasa pe butonul “Sign Out”.

Editare nume

In aplicatie, utilizatorul poate sa-si editeze numele in pagina profilului. Pentru aceasta, se apasa pe butonul “Profile” din bara de navigatie. Mai departe, se da click in campul unde este situat numele si se editeaza acesta. Ultimul pas este salvarea modificarii, care se realizeaza prin apasarea butonului “Save”.

Alegere imagine de profil

Pentru a seta o poza de profil, se apasa pe butonul “Profile” din bara de navigatie. Dupa aceea, se apasa pe imaginea de profil standard din partea de sus a ecranului. Mai departe, se selecteaza o poza din Galerie si se apasa “Ok“. In acest moment, aplicatia revine in pagina profilului, unde se apasa pe butonul “Save” pentru a pastra imaginea de profil.

Ștergere cont

Tot din pagina profilului, utilizatorul are optiunea de a apasa pe butonul ”Delete account”, urmand a fi intampinat de o fereastra de avertizare. Daca se apasa „Yes”, contul va fi sters din baza de date, odata cu toate proprietatile utilizatorului, daca aceasta a postat proprietati.

Adăugare proprietate

In aplicatie, utilizatorul poate sa intre in pagina de adaugare a unei proprietati apasand pe butonul „Add” din bara de navigatie.

Aici se apasa pe butonul „Add property” si utilizatorul va fi directionat catre Google Maps, harta fiind situata deasupra orasului Cluj-Napoca. Mai departe, trebuie apasat pe campul „Search” si introdusa adresa proprietatii pe care userul doreste sa o posteze. Dupa ce se selecteaza adresa din lista de recomandari care apare, harta se va muta automat deasupra locatiei adresei selectate. Locatia este reprezentata de un marker rosu pe harta. Totodata, dupa selectarea unei locatii, pe harta apare butonul „Next”, care atunci cand este apasat, trimite utilizatorul catre urmatorul ecran unde acesta trebuie sa introduca detaliile proprietatii.

Aici, sunt vizibile in primele campuri numele si numarul strazii selectate. Mai jos se introduc numele blocului si numarul apartamentului.

Urmeaza ca utilizatorul sa incarce in aplicatie pozele proprietatii, apasand pe butonul „Upload”. Acest buton va permite utilizatorului sa aleaga galeria de poze sau orice alta aplicatie care intra in aceasta categorie. Mai departe utilizatorul selecteaza pozele si apasa „Next” sau „Done”, in functie de dispozitivul pe care este instalata aplicatia Kariru.

Imaginile sunt incarcate pe ecran si utilizatorul poate sa gliseze cu degetul spre stanga, pentru a vedea selectia facuta. De asemenea, aceste imagini pot fi sterse apasand pe butonul „Delete”.

Mai departe, utilizatorul completeaza sectiunea „Details”, unde alege etajul, numarul de camere, bai si balcoane din optiunile afisate de catre meniurile de optiuni.

In urmatoarele campuri, urmeaza introducerea descrierii proprietatii, a suprafetei acestea in metri patrati si pretul lunar al chiriei.

La sfarsit, se apasa butonul „Save”, urmand ca proprietatea sa fie incarcata in baza de date si utilizatorul este directionat spre pagina de adaugare proprietati.

Vizualizare proprietăți proprii

Dupa ce utilizatorul adauga o proprietate si este redirectionat spre pagina initiala, pe langa butonul de „Add property”, situat jos pe pagina, in partea de sus a paginii apare proprietatea pe care tocmai a postat-o. De asemenea, daca are mai multe proprietati, acesta poate sa gliseze cu degetul in sus, deoarece proprietatile sunt puse intr-o lista.

Editare proprietăți proprii

In pagina “Add” din bara de navigatie, utilizatorul poate da click pe o proprietate deja adaugata. Acest eveniment va deschide un ecran cu detaliile proprietatii, unde toate campurile sunt deja completate. Utilizatorul poate modifica orice doreste, dupa care trebuie sa apese butonul “Save” pentru a salva modificarile facute si ca sa fie redirectionat spre pagina initiala.

Vizualizarea tuturor proprietăților

Pentru a vizualiza toate proprietatile postate de catre oricine, utilizatorul trebuie sa apese pe butonul “Search” din bara de navigatie. In aceasta pagina se gaseste o lista cu toate proprietatile disponibile in momentul respectiv.

Filtrare și sortare proprietăți

Pentru a filtra proprietatile, in ecranul Search, se apasa pe butonul rotund din dreapta jos. Apare fereastra dialog cu optiunile “Room” si “Price”. Se selecteaza “Price”, dupa care se selecteaza ordinea, “Ascending” sau “Descending” iar la final se introduc valorile pentru pretul minim si pretul maxim si se apasa pe butonul “Ok”.

Se apasa din nou pe buton si in fereastra dialog se apasa pe „Room“ iar apoi se selecteaza ordinea de sortare, “Ascending” sau “Descending”.

TODO chat

Studiu bibliografic

In dezvoltarea aplicatiei Kariru, s-a pus accent pe unele dintre cele mai bune practici de dezvoltare a aplicatiilor Android, biblioteci, platforme de dezvoltare si limbaj de programare.

Kotlin

Kotlin este limbajul de programare promovat de Google, in special din 2019, cand a devenit principalul limbaj de programare pentru platforma Android.

Cateva dintre avantajele pe care le are Kotlin fata de Java, sunt:

Kotlin este complet interoperabil cu Java – ambele limbaje de programare se pot folosi in acelasi proiect, fara sa fie nevoie de conversia codului Java in Kotlin sau invers. De asemenea, toate tool-urile si framework-urile Java pot fi folosite si de Kotlin

Este mult mai concis decât Java – acesta este unul dintre cele mai mari avantaje. Aceleasi probleme se pot rezolva in mai putine linii de cod. Din acest lucru rezulta urmatoarele: codul este mai usor de administrat, mai citet, mai usor de modificat si implicit daca este mai putin cod, sunt mai putine bug-uri.

Are un compilator mai deștept și mai sigur – Unul dintre cele mai importante aspecte ale compilatorului este ca detecteaza erori in timpul compilarii, nu in timpul rularii

A fost creat pentru a îmbunătăți producția – acest aspect revine la codul mai concis, din monent ce codul este mai intuitiv si mai curat, avand caracteristici precum parametri cu valori implicite sau functii de extensie

Null este unul dintre tipurile de date – din moment ce lipsa unei valori este reprezentata ca si null si rezulta intr-o exceptie foarte comuna, NullPointerException, in Kotlin poti sa atribui unei valori proprietatea de a lua valoarea null cu operatorul “?”. De asemenea, Kotlin ne pune la dispozitie si operatorul “‼”, pe care il folosim cand dorim transformarea unei variabile care poate lua valoarea null, intr-una ce nu este nula, altfel va arunca exceptia de mai sus.

Inca unul din punctele tari ale limbajului Kotlin este reprezentat de Coroutines. Acestea permit simplificarea codului care se executa asincron. In Android, acestea ajuta la administrarea task-urilor care ruleaza mai mult timp si pot bloca firul de executie principal, care este responsabil cu rularea codului ce tine de interfata grafica.

Android Studio

Android Studio este mediul de dezvoltare integrat (IDE) oficial pentru dezvoltarea aplicatiilor Android. Acesta prezinta multe caracteristici pentru dezvoltare software, dintre care unele vitale, cum ar fi:

Sistem de constructies (build system) bazat pe Gradle

Emulator performant cu multe caracteristici

Un mediu in care se pot dezvolta aplicatii pentru toate dispozitivele care ruleaza cu sistemul de operare Android

Unelte de testare si framework-uri

Structura unui proiect

Fiecare proiect in Android Studio contine unul sau mai multe module, fiecare avand fisiere cu cod sursa si fisiere de tip resursa.

Cele mai importante directoare dintr-un proiect, sunt:

manifests – contine fisierul AndroidManifest.xml, care la randul lui contine informatii legate de toate componentele cum ar fi Activitatile, permisiunile pentru acces la internet sau componente hardware ale dispozitivului, serviciile si bibliotecile folosite.

java – contine fisierele cu cod sursa Kotlin sau Java, incluzand si codul Junit pentru teste

res – contine toate resursele care nu sunt cod, cum ar fi machetele, siruri de caractere afisate in interfata grafica sau imagini

Interfața grafică

In fereastra principala avem urmatoarele elemente:

Bara de instrumente (toolbar) – asigura executia unei varietati de actiuni, inclusiv rularea aplicatiei

Bara de navigație (navigation bar) – ajuta la navigarea in adancime in structura proiectului si deschiderea fisierelor pentru a fi editate

Fereastra editorului (editor window) – este locul in care este scris si modificat codul. In functie de tipul fisierului deschis, editorul se poate schimba. De exemplu, in cazul unui fisier de tip schema (layout), fereastra afiseaza Editorul de scheme

Bara ferestrei cu instrumente (tool window bar) – se afla pe marginea ferestrei IDE-ului si contine butoane care la randul lor extind alte ferestre cu intrumente

Ferestre cu instrumente (tool windows) – permit accesul la diferite sarcini cum ar fi administrarea proiectului, cautare sau depanare

Bara de stare (status bar) – afiseaza starea proiectului si a IDE-ului impreuna cu avertizari si mesaje

Firebase

Firebase a inceput ca serviciu de tip backend (BaaS) si dupa ce a fost cumparat de Google, a devenit o platforma de dezvoltare a aplicatiilor, cum ar fi Web, Mobile, Games, de ultima generatie.

Folosind Firebase, avantajul major este ca nu trebuie sa administram servere sau sa programam APIs. Firebase devine un inlocuitor pentru acestea, precum si pentru bazele de date.

Principalele functionalitati Firebase folosite in aplicatia Kariru sunt:

Authentication

Ajuta in configurarea sistemului de inregistrare, autentificare si identificare a utilizatorilor, intr-un mod securizat. De asemenea, este foarte dificila implementarea pe cont propriu a unui sistem asa de securizat si complex, fapt pentru care autentificarea cu Firebase este unul dintre serviciile preferate de developeri.

Cloud Firestore

Firestore este o baza de date in timp real care ruleaza in Google Cloud si este de tip NoSQL. Unul dintre marile avantaje ale Cloud Firestore este ca dupa ce setezi un “listener” in locatia unde vrei sa folosesti datele stocate, acesta va fi invocat de fiecare data cand datele sunt modificate in cloud. Prin urmare, datele din interfata grafica vor fi actualizate automat.

Cloud Storage

Cloud Storage este un spatiu de stocare care permite download-ul si upload-ul direct din cloud. Se pot salva in el fisiere de tip binar, cele mai comune fiind imaginile. Are propriul sistem de securitate cu reguli adminstrate din consola, care restrictioneaza accesul persoanelor in functie de dorintele dezvoltatorului.

Jetpack

Jetpack este un set de biblioteci care are scopul de a ajuta dezvoltatorii de aplicatii Android sa scrie aplicatii de calitate mai usor, care de asemenea suporta versiuni mai vechi ale sistemului de operare Android.

Cateva din bibliotecile Jetpack utilizate in aplicatia Kariru sunt:

Data Binding Library

Aceasta biblioteca permite “legarea” componentelor UI din layout-urile aplicatiei cu surse de date.

Cateva din avantajele folosirii acestei biblioteci sunt faptul ca nu mai trebuie apelate multe dintre functiile UI intr-o activitate, ceea ce duce la simplificarea codului si o mai buna administrare a acestuia.

De asemenea, data binding imbunatateste performanta aplicatiei si ajuta la prevenirea pierderilor de memorie si a exceptiilor de tip null pointer.

Navigation

Navigation este un framework care face parte din Jetpack. Acesta include trei parti esentiale, care impreuna cu principii bine stabilite, usureaza munca dezvoltatorilor:

Graful de navigație (Navigation graph) – contine toata informatia legata de navigatie intr-un singur loc. Aceasta include toate locurile din aplicatie, numite destinatii, si de asemenea caile pe care un utilizator le poate lua in aplicatie.

NavHost – este un recipient gol care afiseaza destinatiile din graful de navigatie.

NavController – un obiect care administreaza navigatia din aplicatie in NavHost. Acesta schimba continutul din fragmentul de navigatie (un tip de NavHost) in timp ce utilizatorul exploreaza aplicatia

Alte beneficii oferite de componenta de navigatie sunt manipularea corecta a actiunilor butoanelor Up si Back, furnizarea de animatii si tranzitii standard, Safe Args – un plugin Gradle care asigura siguranta tipului de date cand se transmit date intre destinatii.

RecyclerView

RecyclerView este un widget mai avansat si flexbil decat competitorii lui. Functia de baza a widget-ului este sa afiseze liste.

Performanta RecyclerView-ului se datoreaza componentelor care il ajuta. Elementele din lista afisata intr-un RecyclerView sunt obiecte de tip ViewHolder. Fiecare obiect ViewHolder se ocupa de afisarea unui item din lista, in RecyclerView.

O alta componenta importanta este Adaptorul. Acesta preia o lista de date de orice tip si da fiecarui obiect ViewHolder un element din lista, ca sa il afiseze.

Numele de RecyclerView vine de la modul inteligent in care acesta opereaza. Adaptorul creeaza suficiente obiecte ViewHolder cat sa umple ecranul, plus inca niste obiecte mai sus si mai jos de obiectele vizibile. Cand utilizatorul gliseaza in sus prin RecyclerView, obiectele ViewHolder din lista care sunt mai jos initial decat cele vizibile, vor aparea pe ecran. Obiectele de mai sus initial, sunt reciclate sau repopulate cu date de catre adaptor si sunt afisate in continuarea listei, in directia in care utilizatorul gliseaza.

Astfel, RecyclerView poate sa afiseze sute de elemente din lista primita de adaptor, cu doar cateva obiecte de tip ViewHolder. Prin urmare, este imbunatatia performanta aplicatiei si implicit experienta utilizatorului cu aceasta.

ViewPager2

ViewPager2 este un widget care este folosit in principal in navigatie. Poate indeplini functionalitati precum schimbarea fragmentelor in NavHost cu un swipe la stanga sau la dreapta. De asemenea poate activa unele activitati prin intentii.

In Kariru, ViewPager2 este folosit ca si galerie de imagini.

LiveData

LiveData este un tip de clasa observabila, care poate sa tina date. Fata de alte clase observabile, LiveData este constienta de ciclul de viata. Aceasta inseamna ca respecta ciclul de viata al componentelor, cum ar fi activitati, fragmente sau servicii.

LiveData foloseste clasa Observer. Obiectele de tip Observer terbuie sa fie intr-o stare activa, mai exact “started” sau “resumed”, pentru a primi notificari de la LiveData cand aceasta se schimba.

Unele din avantajele furnizate de LiveData sunt:

UI-ul este sincronizat cu starea datelor – LiveData notifica obiectele de tip Observer cand starea ciclului de viata a unei componente se schimba. Astfel, in obiectele Observer se foloseste cod care actualizeaza componentele UI de fiecare data cand exista o schimbare.

Nu sunt pierderi de memorie – obiectele Observer sunt legate de componentele cu ciclu de viata si “curata” memoria dupa ele cand componentele asociate lor sunt distruse.

Tot timpul la curent cu noutățile – daca o componenta cu ciclu de viata devine inactiva, cand redevine activa, primeste date de la actualizarea cea mai recenta

Glide

Glide este un framework open source folosit pentru administrarea si incarcarea imaginilor, fotografiilor video si GIF-urilor din diferite surse. Are functii precum redimensionarea imaginilor si se ocupa si de memoria si spatiul ocupat in dispozitiv.

Scopul principal al framework-ului este de a face posibila glisarea imaginilor intr-o lista cat de rapid si fluent posibil.

In Kariru este folosit pentru incarcarea imaginilor in RecyclerView sau ViewPager2.

Google Places

Google Places este un serviciu care returneaza informatii despre locuri (places) prin cereri HTTP. Acesta este folosit pe platforma Android prin intermediul Places SDK.

Interfata pentru Android furnizeaza 2 puncte de intrare pentru cereri:

Places – da acces la baza de date Google cu informatii despre locatii si afaceri, de asemenea si locatia curenta a dispozitivului

Autocomplete – furnizeaza widget-uri care returneaza predictii cu numele locurilor de care utilizatorul intreaba.

Timber

Timber este un logger care pe langa unele functionalitati in plus, este mai usor de folosit decat clasa Log de la Android. Este implementat cu “DebugTree” in clasa aplicatiei in metoda “onCreate”. Din acel loc, Timber stie automat cand este folosit si va face tag-uri pentru clasa in care este utilizat.

Ciclul de viață al componentelor

Activitatea

Activitatea este una dintre componentele fundamentale ale unei aplicatii Android. Aceasta este responsabila pentru introducerea utilizatorului in aplicatie si este o subclasa a clasei “Activity”.

Activitatea furnizeaza fereastra in care aplicatia deseneaza interfata grafica. De obicei, aceasta fereastra umple tot ecranul dispozitivului, dar poate fi si mai mica decat ecranul si sa pluteasca deasupra altor ferestre (activitati).

Cele mai multe aplicatii au mai multe ecrane, insemnand ca ele contin mai multe activitati. De cele mai multe ori, activitatea principala este “MainActivity” si aceasta introduce utilizatorul in aplicatie.

Pentru a folosi o activitate intr-o aplicatie, aceasta trebuie inregistrata in fisierul AndroidManifest.xml al aplicatiei.

Ciclul de viata al unei activitati este unul dintre subiectele vitale pe care orice dezvoltator de aplicatii Android trebuie sa il cunoasca.

Ciclul de viata ne permite sa cunoastem starile prin care trece o activitate si ne da posibilitatea de a implementa logica corespunzatoare evenimentelor pe care vrem sa le punem in actiune.

Pentru a putea controla ce se intampla intre starile unei activitati, clasa Activity pune la dispozitie sase metode callback de baza. Sistemul invoca fiecare dintre aceste metode atunci cand activitatea intra intr-o noua stare.

onCreate() – este metoda care trebuie implementata neaparat. Ea este declansata cand sistemul creeaza activitatea. In aceasta metoda trebuie implementata logica de baza care dorim sa aiba loc atunci cand activitatea este creata, o singura data pentru durata de viata a acesteia. De exemplu, aici asociem activitatea cu un ViewModel (descris in urmatorul subcapitol) sau instantiem variabile globale. Metoda primeste parametrul “savedInstanceState”, care contine starea salvata a activitatii dintr-o etapa anterioara. Daca activitatea nu a existat niciodata inainte, parametrul este nul.

onStart() – cand activitatea intra in starea “Started”, sistemul invoca aceasta metoda. Ea face activitatea vizibila utilizatorului pentru a se putea interactiona cu aceasta.

onResume() – dupa ce aceasta metoda este invocata, este posibila interactiunea utilizatorului cu activitatea. Activitatea mentine starea “Resumed” pana cand ceva se intampla si isi pierde focus-ul (atentia). De exemplu, primirea unui apel, navigarea spre alta activitate sau oprirea ecranului.

onPause() – sistemul declanseaza aceasta metoda in momentul in care utilizatorul paraseste activitatea. Aceasta nu mai este in centrul atentiei, desi mai poate fi vizibila daca o alta activitate care nu ocupa tot ecranul apare deasupra ei. Daca utilizatorul revine la activitatea initiala, sistemul apeleaza metoda onResume().

onStop() – sistemul apeleaza aceasta metoda atunci cand o activitate nou lansata acopera tot ecranul sau cand activitatea si-a terminat rularea. Pentru performanta, este indicat ca in aceasta metoda sa se elibereze resurse de care aplicatia nu are nevoie.

onDestroy() – aceasta metoda este apelata inainte ca activitatea sa fie distrusa. Sistemul poate face asta daca metoda finish() a fost apelata undeva in aplicatie sau se modifica configuratia (se roteste ecranul sau intra in modul multi-window).

Fragmentul

Un fragment reprezinta un comportanment sau o bucata din interfata grafica intr-o activitate. Se pot utiliza mai multe fragmente intr-o singura activitate sau se poate folosi acelasi fragment in mai multe activitati.

Acesta are propriul ciclu de viata, urmeaza aceleasi principii ca si ciclul de viata al unei activitati, cu mici diferente.

Fragmentul mai poate fi interpretat ca si o parte modulara, care primeste propriul input sau evenimente si care poate fi adaugat sau indepartat in timp ce activitatea ruleaza.

De asemenea, fragmentul trebuie sa fie tot timpul intr-o activitate si ciclul lui de viata este influentat direct de catre ciclul de viata al gazdei. De exemplu, daca activitatea intra in starea “Paused”, toate fragmentele din aceasta activitate intra in aceeasi stare.

Desi, in timp ce o activitate ruleaza, fragmentele pot fi manipulate independent de aceasta.

Arhitectura MVVM

Acest sablon, Model-View-ViewModel, este un mod de a structura codul intr-un proiect si ofera mai multe beneficii, dintre care:

Ofera flexibilitate – mai multi oameni pot lucra pe acelasi proiect in acelasi timp, pe diferite componente

Permite schimbarea interfetei grafice fara a fi nevoie de a modifica logica din spate

Este mai usor de testat

Model

Modelul reprezinta datele cu care se lucreaza in aplicatie. De exemplu, un model poate fi o adresa (contine oras, strada, numar, bloc etc.).

Este important de retinut ca desi modelul stocheaza informatie, acesta nu este responsabil pentru diferite comportamente sau servicii care manipuleaza informatia.

View

View reprezinta interfata grafica cu care utilizatorul interactioneaza, aceasta componenta fiind singura la care utilizatorul are acces. Interfata poate avea evenimente, de exemplu acceptarea de input, apasarea de butoane, sau gesturi prin atingerea ecranului.

Fata de alte modele de structurare a codului, in MVVM, view-ul este activ, ceea ce inseamna ca are cunostinta despre Model si ViewModel, prin data-binding, apeluri sau comenzi spre acestea.

Un lucru important de retinut este faptul ca View-ul nu este responsabil pentru mentinerea starii proprii. In schimb, acesta se sincronizeaza cu ViewModel-ul.

ViewModel

ViewModel este componenta cheie care leaga View de Model. El controleaza interactiunile dintre cele doua componente.

De exemplu, ViewModel ia input din View si il pune in model sau poate interactiona cu un serviciu extern pentru a recupera un model si apoi sa afiseze proprietatile acestuia in interfata grafica.

Acesta expune metode, comenzi sau variabile care ajuta la mentinerea starii interfetei grafice, mai ales cand aceasta trece prin schimbari de configurare, fiind distrusa si recreata.

Dupa cum se poate observa, durata de viata a unui ViewModel acompaniaza o activitate sau un fragment de la creare pana cand sunt distruse. Acesta este un lucru foarte important, deoarece nu mai trebuie sa salvam manual informatia din View care poate fi pierduta, in parametru din onCreate(), savedInstanceState. Acest parametru de tip Bundle are o capacitate mica de stocare si poate afecta performanta aplicatiei.

Analiză, proiectare, implementare

Analiza aplicației

In acest capitol este prezentata structura aplicatiei si modul in care fiecare componenta structurala este implementata. De asemenea, in diagrama de cazuri de mai jos este reprezentata functionalitatea aplicatiei.

TODO use case diagram

Proiectare

Structura aplicației

Pentru aceasta aplicatie, am ales sa folosesc sablonul MVVM pentru a putea lucra intr-un mediu organizat. In interiorul proiectului, directorul “java” contine pachetul principal “ths.kariru”. Conventia de numire a pachetelor este “com.domain.name”. Din moment ce nu am domeniul meu unic pentru aceasta aplicatie, am ales sa numesc pachetul “ths.kariru”, de la cuvantul “this” si numele aplicatiei in loc de domeniu.

In interiorul acestuia, am structurat proiectul in mai multe pachete: adapters, fragments, models, utils, viewmodels.

In pachetul principal, “ths.kariru”, care contine toate celelalte pachete, avem doua clase:

MainActivity

KariruApplication

Pachetul “adapters” contine adaptori pentru trei RecyclerView si un ViewPager2:

AddRecyclerViewAdapter

AddViewPagerAdapter

EditRecyclerViewAdapter

SearchRecyclerViewAdapter

Pachetul “utils” contine trei activitati, doua clase si doua obiecte:

LoginActivity

SplashActivity

MapsActivity

GlideModule

TopSpacingItemDecoration

FirestoreUtil

StorageUtil

Celelalte pachete vor fi prezentate in subcapitolele urmatoare.

Tot ce nu este cod, insemnand resursele, sunt salvate in directorul “res”, in subdirectoare, in functie de natura sau scopul lor.

Drawable – contine imaginile sau pictogramele intalnite in aplicatie

Layout – contine toate machetele care sunt responsabile cu aranjarea pe ecran a diferitelor tipuri de componente UI

Menu – contine layout de tip meniu

Mipmap – contine pictograma aplicatiei in mai multe rezolutii

Navigation – contine graful de navigatie

Values – contine culori, siruri de caractere si stiluri

Gradle Scripts se ocupa de construirea aplicatiei inainte de a fi rulata. In fisierele “build.gradle” se introduc dependintele pentru bibliotecile folosite in aplicatie.

Model

Pachetul “models“ contine modelele care sunt folosite in aplicatie:

Address

Property

User

View

View este alcatuit de doua componente. Layout-ul care este afisat pe ecran si clasa din spate, care il afiseaza.

Trei componente care se incadreaza in View, au fost deja prezentate in pachetul “utils”, si anume: SplashActivity care este putin speciala, deoarece nu are un layout, dar are o imagine de fundal setata cu ajutorul unui stil, LoginActivity care afiseaza layout-ul “activity_login.xml”, MapsActivity care afiseaza layout-ul “activity_maps.xml”.

Pachetul “fragments” contine urmatoarele fragmente, care sunt responsabile cu initializarea interfetei grafice pe ecran sau schimbarile datorate evenimentelor cum ar fi apasarea pe un buton sau glisarea prin continut:

AddFragment

AddFragment2

EditFragment

ProfileFragment

SearchFragment

SearchFragment2

Alte layout-uri care sunt folosite de alte componente sunt:

add_view_pager.xml

property_post_item.xml

ViewModel

In pachetul “viewmodels” este reprezentat ViewModel, prin trei clase care contin atat logica aplicatiei, cat si conexiunea cu Cloud Storage si Cloud Firestore. Acestea sunt:

AddFragmentViewModel

ProfileFragmentViewModel

SearchFragmentViewModel

Implementare

Configurație Firebase

Pentru a folosi serviciile Firebase in aplicatie, trebuie creat un proiect pe website-ul oficial Firebase. Pentru aceasta, este nevoie si de un cont. Dupa crearea contului, autentificare in consola Firebase si crearea proiectului, trebuie initiate cereri pentru sercivii. Mai exact, in serverele Firebase, se aloca un spatiu pentru proiectul din consola.

Tab-ul Authentication contine detalii despre utilizatorii inregistrati in Firebase, cat si metode de autentificare, pentru care trebuie setari suplimentare.

In consola, s-a ales metoda de autentificare cu email si cont Google. Pentru metoda cu email, trebuie doar setata starea pe “enabled”. Pentru metoda de autentificare cu cont Google, trebuie configurat proiectul pentru a putea folosi un Google API, care la autentificare, preia credentialele utilizatorului si se ocupa de singur de acest proces.

Pentru a configura acest lucru, trebuie sa oferim doua informatii esentiale pentru Google, ca sa se asigure ca userul se autentifica in aplicatia noastra si nu in alta parte. Prima informatie este numele pachetului principal. In aplicatia Kariru, acesta este “ths.kariru”. Motivul este ca, atunci cand lansezi aplicatia pe piata, numele pachetului principal reprezinta un identificator unic.

A doua informatie este un cod SHA1, generat in proiect. Sunt mai multe metode de a procura acest cod. Cel mai usor este de a intra in tab-ul Gradle, situat in partea dreapta, pe margine, in Android Studio. Se deschide directorul “Tasks”, apoi directorul “android”.

Se da dublu click pe “signingReport“, care apare in fereastra “Run”. Acolo gasim codul SHA1, format din 40 de caractere.

Dupa cum se observa in figura, utilizatorii sunt identificati printr-un sir de caractere generat de Firebase. Sunt disponibile detalii cum ar fi data inregistrarii, data ultimei autentificari sau emailul folosit pentru inregistrare.

Tab-ul Database contine baza de date aleasa, in acest caz Cloud Firestore.

In aceasta imagine se poate vedea structura bazei de date. Aceasta contine doua colectii, numite “properties” si “users”.

Pe a doua coloana, se observa continutul fiecarei colectii. In colectia “properties” sunt mai multe documente, fiecare cu un id unic, generat de Firebase. In fiecare document sau proprietate in acest caz, se gasesc campurile de date disponibile, care sunt aceleasi cu cele din modelul “Property”.

De asemenea, fiecare document poate sa contina colectii, care la randul lor pot contine alte documente. Acest lucru se poate repeta pana cand se ajunge la un arbore cu adancimea de 100.

In colectia “users” sunt mai multe documente cu identificator unic, aceleasi id-uri ca si in tab-ul Authentication. Fiecare user contine campurile name, care poate fi modificat in fragmentul Profile, si profilePicture.

Pentru a securiza baza de date si a ne asigura ca nu oricine are acces sa o modifice, sunt folosite reguli pentru a limita accesul la un numar restrans de utilizatori. In cazul de fata, doar utilizatorii care sunt autentificati au dreptul de a scrie sau citi din baza de date.

Tab-ul Storage stocheaza fisiere mari, de cele mai multe ori imagini.

In aceasta figura, in capul tabelului se poate observa calea catre aceste imagini. Primul element reprezinta identificatorul pentru storage-ul aplicatiei. Urmeaza directorul

“images”. Acesta contine mai multe directoare, fiecare identificate prin id-ul unic al utilizatorilor. In fiecare director al unui utilizator, pot fi directoare care reprezinta proprietatile postate de acestia, identificate printr-un id, care este acelasi cu cel din Cloud Firestore. In fiecare director care reprezinta o proprietate, sunt imaginile proprietatii respective.

Daca se da click pe oricare dintre imagini, in dreapta apare o fereastra cu detalii. Tab-ul “File location” contine link-ul spre locatia imaginii, pe care il vom stoca in fiecare proprietate din Cloud Firestore, pentru fiecare imagine.

Pe langa directorul “images”, se creeaza un director cu id-ul utilizatorului curent pentru fiecare utilizator care isi incarca o poza de profil in ProfileFragment si o salveaza cu butonul “Save”. In acest director, se gaseste directorul “profilePictures”, in care este salvata imaginea.

In continuare se prezinta detaliile de implementare pentru clasele prezentate in subcapitolul anterior si al arhitecturii MVVM, prin explicarea metodelor si tehnicilor folosite.

Pachetul “ths.kariru”

In pachetul principal, “ths.kariru”, care contine toate celelalte pachete, avem doua clase:

MainActivity

Aceasta clasa reprezinta o activitate spre care este directionat utilizatorul dupa ce procesul de autentificare s-a incheiat cu succes. Aici se leaga layout-ul “activity_main.xml” de clasa MainActivity. De asemenea se configureaza o variabila “navController”, care se ocupa de administrarea fragmentului “nav_host_fragment”, care la randul lui, afiseaza fragmentele din aplicatie prin care utilizatorul navigheaza.

Tot aici, cu ajutorul “navController”, se configureaza si bara de navigatie, ca sa se sincronizeze cu “nav_host_fragment”.

KariruApplication

Este clasa care starteaza rularea aplicatiei. Aici este initializata biblioteca Timber. De asemenea, tot aici se pot initializa servicii care ruleaza in background, cand aplicatia nu este pornita, pentru update-uri, daca este vorba de cache offline de exemplu.

Pachetul “adapters”

Pachetul “adapters” contine adaptori pentru trei RecyclerView si un ViewPager2:

AddRecyclerViewAdapter

Este folosit pentru a incarca imagini din Galerie intr-un RecyclerView, in AddFragment2. Acesta primeste ca si parametru o lista de URI, care reprezinta locatia imaginilor in dispozitiv. Clasa extinde RecyclerView.Adapter asa ca se implementeaza cele trei metode care trebuie suprascrise:

onCreateViewHolder – metoda folosita de adaptor pentru a crea obiecte de tip ViewHolder, care mai apoi sunt populate de urmatoarea metoda

onBindViewHolder – adauga in obiectul ViewHolder de la pozitia specificata de adaptor, datele furnizate de adaptor

getItemCount – returneaza numarul elementelor din RecyclerView pe care utilizatorul le vede

Ultima componenta este clasa AddRecyclerViewHolder de tip “inner”, care leaga variabilele ce primesc valori in onBindViewHolder, de elementele UI din layout-ul folosit de adaptor. Aici este un ImageView.

AddViewPagerAdapter

Este folosit pentru a incarca imagini din modelul Property trimis prin Safe Args din SearchFragment, in SearchFragment2. Imaginile sunt transmise printr-un parametru de tip “MutableList<String>”. Lista nu mai este de tip URI pentru ca acum sunt transmise link-urile imaginilor care au ca referinta, locatia imaginilor in Cloud Storage. Cele trei metode de suprascris au aceeasi functie. Difera in onBindViewHolder faptul ca imaginile sunt incarcate cu GlideApp, care are si optiunea de a adauga placeholder la imagine pana cand aceasta este descarcata din Cloud Storage sau in caz ca este vreo eroare.

EditRecyclerViewAdapter

Este folosit pentru a incarca imagini din modelul Property trimis prin Safe Args din AddFragment, in EditFragment. Primeste ca parametru lista de imagini de tip “MutableList<String>” si contextul in care se foloseste adaptorul.

In onBindViewHolder pentru a afisa imaginea, se foloseste GlideApp, care are nevoie de context, care poate fi transmis atata printr-o variabila de tip “Context”, cat si prin ImageView-ul in care incarca imaginea.

SearchRecyclerViewAdapter

Este folosit pentru a incarca imagini din modelul Property, preluat din Firestore, iar imaginile preluate din Cloud Storage, in fiecare obiect ViewHolder din RecyclerView. Adaptorul primeste ca si parametri o variabila “priceMinimum” de tip Int, initializata cu “0”, un listener si un obiect “options” de tip “FirestoreRecyclerOptions<Property>”. Listener-ul face ca atunci cand obiectul ViewHolder este apasat de catre utilizator, se va deschide fragmentul SearchFragment2 aratand detaliile despre proprietatea obiectului. In onBindViewHolder se folosesc String resources pentru a formata campurile ce afiseaza detaliile proprietatii. Mai este folosit AddViewPagerAdapter pentru a afisa imaginile din fiecare proprietate. De asemenea este implementata o logica, care in functie de numarul de camere sau bai, daca este singular sau plural, va folosi un string resource sa afiseze cuvantul camera sau baie la singular sau plural, pentru a pastra acordul. Nu in ultimul rand, se verifica daca pretul proprietatii este mai mic decat pretul minim. Daca da, acestea sunt ascunse de pe ecran, pentru a ramane doar cele care sunt intre pretul minim si pretul maxim.

Pachetul “utils”

Pachetul “utils” contine trei activitati, trei clase si doua obiecte:

LoginActivity

Aici se gaseste logica pentru setarea in aplicatie a Firebase UI, o biblioteca construita peste Firebase Authentication, care starteaza o activitate folosind AuthUI. Pentru aceasta activitate se implementeaza providerii care sunt legati de butoanele de autentificare. Acestia se pun intr-o lista numita providers: AuthUI.IdpConfig.EmailBuilder().build() si AuthUI.IdpConfig.GoogleBuilder().build().

Mai departe se foloseste metoda “startActivityForResult()“ unde se starteaza activitatea pentru login furnizata de Firebase, prin AuthUI.getInstance(). Mai departe se ataseaza providerii cu “.setAvailableProviders(providers)”, se seteaza tema cu “setTheme(R.style.SigninTheme), o tema personalizata, si se adauga logo-ul cu “.setLogo(R.drawable.ic_logo_text2)”. Ultima data, se termina constructia acestei activitati cu “.build()” si se adauga un cod, “RC_SIGN_IN”, care ne va ajuta sa identificam rezultatele returnate de catre metoda “startActivityForResult()”.

Dupa ce am suprascris “startActivityForResult()”, dorim sa preluam din aceasta rezultatul, in cazul de fata, raspunsul dat de Firebase, daca utilizatorul s-a autentificat sau nu cu succes.

Se verifica daca “requestCode”, un parametru furnizat de metoda, este egal cu “RC_SIGN_IN”. Daca da, mai departe preluam raspunsul intr-o variabila “response“ cu ajutorul “IdpResponse.fromResultIntent(data)”.

Se verifica daca activitatea s-a incheiat cu succes cu “resultCode == Activity.RESULT_OK”. Daca da, se creeaza o intentie care starteaza MainActivity. Daca nu, preluam eroarea din “response” si il afisam sau daca nu avem nici eroare, terminam pur si simplu activitatea cu “finish()”.

Daca dorim sa inregistram un cont cu serviciul Email, suntem condusi la un alt ecran unde introducem emailul, apoi suntem directionati la alt ecran unde introducem numele utilizatorului si parola pentru cont. Dupa ce apasam “Save”, intram in aplicatie in MainActivity.

Daca dorim sa ne autentificam cu serviciul Email, dar ne-am uitat parola, putem apasa pe textul “Trouble signing in?” si suntem directionati spre un ecran unde avem completat deja campul cu adresa de email unde dorim sa trimitem un link pentru a reseta parola. Dupa ce apasam butonul “Send”, pe contul de email o sa apara in scurt timp un mesaj cu linkul promis. Daca apasam pe el putem sa schimbam parola.

SplashActivity

O activitate foarte mica, dar utila. Aceasta este activitatea care introduce utilizatorul in aplicatie. Daca utilizatorul nu este autentificat, de aici este condus la LoginActivity, altfel este directionat catre MainActivity.

MapsActivity

Activitatea care este pornita atunci cand un utilizator adauga o proprietate. Pentru a putea folosi Google Places API, prima data trebuie adaugat proiectul in Google Cloud. Dupa aceea se introduce un card de credit pentru ca fiecare cautare in GoogleMaps are un anumit cost. Pentru aplcatiile care nu au intrat inca in productie, Google ofera 300$ timp de un an, pentru a testa API-ul. Mai departe, din consola Google Cloud, trebuie creat un API key, cu care se initializeaza serviciul in MapsActivity. Dupa ce cheia a fost introdusa MapActivity, aceasta va fi restrictionata din consola pentru aplicatia Kariru, pentru o mai buna securitate. Astfel, doar aplicatia Kariru va putea accesa serviciul oferit de Google Places Api cu acea cheie unica.

Tot in onCreate() se initializeaza fragmentul din “activity_maps.xml”, in care este afisata harta. De asemenea, se mai initializeaza un “autocompleteFragment” care este responsabil cu predictia numelor strazilor pe care le scriem in campul “Search” si un buton “Next” care devine vizibil doar dupa ce o locatie este selectata, pentru a trece la pasul urmator.

Fiindca toate rezultatele returnate de API costa, trebuie sa filtram ce nu ne intereseaza pentru a nu plati in plus. De asemenea, pentru a avea predictii mai bune. Astfel, in “.setTypeFilter()” spunem API-ului ca o sa introducem doar adrese. Cu “.setLocationBias() introducem coordonate pentru a primi predictii cu adrese doar dintr-un dreptunghi pe harta, in care este inclus orasul Cluj-Napoca. Cu “.setCountries()” cautam doar in Romania. Cu “.setPlaceFields()” API-ul ne returneaza doar adresa completa si coordonatele locatiei pe care o cautam. Cu “.setOnPlaceSelectedListener()” se implementeaza comportamentul aplicatiei dupa ce o locatie din lista este selectata. In acest caz, harta se centreaza pe locatia selectata si se pune un marker deasupra ei.

In figura 3.9 este prezantata utilizarea MapsActivity. Cand este apasat butonul de “Add Property”, este startata activitatea. Apoi se introduce adresa locatiei proprietatii pe care dorim sa o postam. Dupa ce utilizatorul selecteaza locatia, harta se muta deasupra acestei locatii, pune un marker si devine vizibil butonul “Next” care deschide un alt fragment si ii trimite acestuia coordonatele si adresa locatiei.

GlideModule

Este o clasa in care setezi diferite configurari personalizate, incepand cu versiunea Glide4. Aceasta clasa permite bibliotecii Glide sa incarce imagini direct din Cloud Storage. In interior, se suprascrie metoda “registerComponents”.

TopSpacingItemDecoration

O simpla clasa cu care se pot adauga diferite configurari obiectelor de tip ViewHolder din RecyclerView. In cazul de fata, aceasta clasa este folosita pentru a adauga spatiu intre obiecte.

Dialog

Este clasa in care se afla logica pentru filtrare si sortare, ferestre afisate la apasarea butonului din SearchFragment. Aceasta contine trei metode si o interfata FilterInterface, cu doua metode, ale caror definitie este creata pentru suport in celelalte metode din clasa Dialog.

Metoda filterDialog() seteaza in fereastra popup numele campurilor TextView. De asemenea, in functie de TextView-ul pe care se apasa, pe acesta se apeleaza metoda sortDialog().

Metoda sortDialog() identifica combinatiile dintre campurile “price”, “room” si tipurile de sortare “Ascending”, “Descending”, acestea fiind conditiile dupa care se face sortarea. Pentru campul “price” se apeleaza metoda priceRangeDialog() cu parametrul “Ascending” sau “Descending” ca tip de sortare si filter, care este parametrul metodei sortDialog(). Pentru campul “room”, se foloseste interfata filterInterface pentru a sorta query-ul (lista cu proprietati) in functie de tipul de sortare ales, “Ascending” sau “Descending”.

Metoda priceRangeDialog() foloseste layout-ul “range_layout.xml”, care contine doua campuri, unul pentru pretul minim si unul pentru pretul maxim. De asemenea, se initializeaza o fereastra pop up pentru acest layout si se creeaza doua variabile care sunt legate de campurile de tip TextInputEditText din “range_layout.xml”. Mai departe se seteaza cateva optiuni pentru fereastra pop up care este de tip alertDialogBuilder. Se ataseaza butonul “Cancel” cu “.setNeutralButton()” si butonul “Ok” cu “.setPositiveButton()”. Daca se apasa pe butonul “Cancel”, se inchide fereastra. Daca se apasa pe butonul “Ok”, se creeaza doua variabile priceMinimum and priceMaximum care preiau valorile din campurile care trebuie populate de catre utilizator. Dupa o verificare ca acele campuri de priceMinimum si priceMaximum nu sunt goale, se face filtrarea cu ajutorul interfetei filterInterface.

FirestoreUtil

Un obiect de tip singleton (este instantiat un singur obiect pe toata durata rularii aplicatei) care contine referinte catre Cloud Firestore si cateva metode. Prima dintre acestea este “initUserIfFirstTime()” care este apelata in “startActivityForResult()” din LoginActivity. In aceasta metoda se verifica daca utilizatorul nu exista si daca este adevarat, il adauga in colectia “users” din Cloud Firestore.

O alta metoda, “updateCurrentUser()”, este apelata in ProfileFragment. Aceasta verifica daca poza de profil si campul cu numele utilizatorului nu sunt nule si actualizeaza utilizatorul in colectia “users” din Cloud Firestore.

Metoda “getCurrentUser()” returneaza utilizatorul curent din Cloud Firestore

StorageUtil

Un obiect de tip singleton care contine referinte catre Cloud Storage si o metoda de incarcare a imaginii de profil a utilizatorului.

Model

Pachetul “models“ contine modelele care sunt folosite in aplicatie:

Address

Este o clasa care stocheaza date, avand cuvantul cheie “data” in fata. Are doar un constructor care initializeaza obiectele de tip Address, cu urmatoarele campuri: street, streetNumber, blockName, apartmentNumber, neighborhood. De asemenea, clasa Address extinde clasa Parcelable, pentru a putea fi transmise obiecte de tip Address ca si argumente intre fragmente. Pentru a nu implementa metodele care trebuie suprascrise din clasa Parcelable, Address este adnotata cu @Parcelize

Property

Clasa care stocheaza date. Are doar constructor care initializeaza obiectele de tip Property. Constructorul are urmatoarele campuri: address (de tip Address), latitude, longitude, floor, room, bath, balcony, surface, description, price, userId, propertyId, imageList. Si aceasta clasa extinde Parcelable si este adnotata cu @Parcelize. De asemenea, acest model este folosit peste tot prin aplicatie.

User

Clasa care stocheaza date. Constructorul folosit pentru a initializa obiectele de tip User are urmatoarele campuri: name, profilePicture. Aceasta clasa este folosita pentru a popula colectia “users” cu utilizatorii care se autentifica in aplicatie. De asemenea este folosita in ProfileFragment.

View

Mai departe este prezentat detaliat continutul pachetului “fragments”:

AddFragment

Afiseaza layout-ul “fragment_add.xml”, care are in componenta sa un RecyclerView pentru afisarea proprietatilor unui utilizator si un buton “Add property” pentru a adauga proprietati.

In metoda onCreate() se inițializează o variabila “viewModel” de tip AddFragmentViewModel cu ajutorul data-binding. De asemenea se seteaza un “setOnClickListener” pe butonul “searchMapButton”, care atunci cand este apasat, starteaza MapsActivity, cu ajutorul metodei “startActivityForResult()” prin codul “REQUEST_PROPERTY_COORDINATES”. De asemenea, se instantiaza si un navController.

In metoda initRecyclerView() se initializeaza un RecyclerView, care tine postarile utilizatorului. Aici se configureaza layoutManager, pentru a spune RecyclerView-ului daca sa afiseze postarile pe orizontala, verticala sau pe mai multe coloane. De asemenea se adauga spatiu intre postari cu TopSpacingDecoration. Dupa aceea, se face un query care retrage din Cloud Firestore proprietatile ale carui “userId” este cel curent. Acest query este adaugat unei variabile options de tip “FirestoreRecyclerViewOptions”. Mai apoi, se configureaza adaptorul de tip SearchRecyclerViewAdapter care primeste o lista cu proprietati prin variabila options si o actiune de navigare cu navController. Aceasta actiune este declansata la click pe o postare.

In metoda onViewCreated() se apeleaza metoda initRecyclerView(). Dupa aceea se apeleaza o metoda din ViewModel, viewModel.fetchProperties(), care descarca din Cloud Firestore doar proprietatile utilizatorului curent si le adauga intr-o lista de proprietati de tip LiveData. Mai departe, este atasat un Observer pe lista de proprietati din ViewModel cu “.observe()”. In metoda observe(), de fiecare data cand este vreo schimbare in lista de proprietati din ViewModel, lista de proprietati locala este golita si se adauga proprietatile din ViewModel, apoi se actualizeaza RecyclerView-ul.

Ultima metoda este onActivityResult(), care este suprascrisa. In aceasta se verifica daca s-au primit rezultate de la MapsActivity, acestea fiind coordonate si adresa strazii. Daca da, acestea sunt puse intr-o actiune de navigatie, care este declansata de navController atunci cand se termina MapsActivity. Aceste date sunt trimise spre AddFragment2.

In figura 3.10, prima imagine arata AddFragment cand este gol si ultima imagine dupa ce s-a adaugat o proprietate.

AddFragment2

Afiseaza layout-ul “fragment_add2.xml” care contine mai multe campuri “TextView” ce afiseaza informatie, campuri “TextInputEditText” in care se introduc detalii despre proprietate, meniuri de tip drop-down (spinners) tot pentru input, un RecyclerView pentru incarcarea imaginilor din galerie, buton de upload pentru imagini, buton de stergere a imaginilor si buton de salvare a proprietatii in baza de date.

In AddFragment2 se gasesc mai multe metode.

In metoda onCreateView() se initializeaza variabila viewModel de tip AddFragmentViewModel, prin data-binding. Se initializeaza un setOnClickListener pe butonul add2UploadButton prin metoda uploadPhotos(), pe butonul add2DeleteButton cu metoda deletePhotos() si add2SaveButton cu metoda validateTextFields(). Se instantiaza si un navController. Nu in ultimul rand, se apeleaza metodele initializeSpinners(), initializeTextFields() si setFocusListenerOnTextViews().

In imaginile 2 si 3 din figura 3.10 se gaseste AddFragment2.

In metoda onViewCreated(), din variabila “arguments” a sistemului, se extrage adresa introdusa anterior in MapsActivity si se populeaza campurile add2StreetText si add2StreetNrText.

In metoda uploadPhotos(), cand butonul “Upload” este apasat, se starteaza o intentie de tip “GET_ACTION_CONTENT”, care deschide galeria, de unde putem selecta fisiere de tip “image/*”, prin metoda startActivityForResult() cu codul “PICK_MULTIPLE_IMAGES”.

In metoda deletePhotos(), actualizam adaptorul RecyclerView-ului cu o lista goala de imagini si imaginile din variabila de tip lista le stergem.

In metoda validateTextFields() se creeaza o lista de tip boolean, in care adaugam rezultatele verificarilor tuturor campurilor din “fragment_add2.xml”, vizibile si in Figura 3.10, imaginile 2 si 3. Se verifica fiecare camp. Daca este gol, se seteaza proprietatea “.isErrorEnabled” cu “true” si se transmite un mesaj de eroare “Field can’t be empty”. Daca nu, se seteaza “.isErrorEnabled” cu “false”. La sfarsit, se verifica toata lista de tip boolean si daca cel putin un element este fals, atunci cand se apasa pe butonul “Save” nu se intampla nimic. Altfel, se apeleaza metodele saveProperty() si clearDataAfterSave(), urmand ca navController sa foloseasca o actiune de control care ne duce inapoi in AddFragment, dupa ce proprietatea a fost adaugata.

In metoda saveProperty() se preia din variabila sistemului “arguments” latitudinea si longitudinea trimise din MapsActivity si sunt stocate in variabile locale. Dupa aceea, toate valorile campurilor din “fragment_add2.xml” sunt stocate in variabile locale. Mai apoi se folosesc modelele Adress si Property pentru a instantia un obiect din fiecare. Obiectul de tip Property, impreuna cu un obiect user care reprezinta utilizatorul curent si cu lista imaginilor alese din galerie, sunt trimise ca argumente in metoda “.savePropertyToFirestore()” apelata cu “viewModel”.

Metoda clearDataAfterSave() sterge datele din toate campurile layout-ului “fragment_add2.xml” dupa care afiseaza un mesaj pe ecran “Property has been saved”.

In metoda initializeSpinners(), se instantiaza spinnerele add2FloorSpinner, add2RoomsSpinner, add2BathsSpinner si add2BalconiesSpinner. Inainte de asta, pentru fiecare spinner, un ArrayAdapter este incarcat cu o lista de stringuri care vor fi afisate in spinner, dupa care acesta este atasat spinnner-ului. Dupa aceea, pe fiecare spinner se pune un “.onItemSelectedListener”, care atunci cand un string din lista a fost selectat, valoarea respectiva este salvata intr-o variabila globala numita floor, rooms, baths sau balconies.

In metoda initializeTextFields(), se instantiaza un adaptor de tip ArrayAdapter cu o lista de stringuri, care este apoi atasat campului de tip Autocomplete, add2NeighborhoodText. Acest camp, atunci cand scriem, ne va oferi predictii pentru numele cartierelor din orasul Cluj-Napoca.

In metoda setFocusListenerOnTextViews(), se aplica tuturor campurilor de tip “InputFieldEditText” un “.onFocusChangeListener”, care detecteaza atunci cand elementul isi pierde focus-ul si apeleaza metoda hideSoftKeyboard()

Metoda hideSoftKeyboard() inchide automat tastatura telefonului.

In metoda suprascrisa onActivityResult(), se verifica daca exista rezultate primite, dupa care, se preiau imaginile si se adauga URI-ul in variabila globala “images”. Tot aici este configurata si logica pentru a limita numarul de imagini care poate fi incarcat in RecyclerView, din moment ce nu se poate limita numarul de imagini selectat din galerie. Asadar, se compara numarul de elemente din “images” cu o variabila globala “imageLimit”. Daca sunt egale, urmatoarele imagini nu mai sunt adaugate listei “images”. Daca s-a incarcat doar o imagine din galerie, este logica separata intr-un else, pentru a adauga imaginea in lista “images”.

In metoda suprascrisa onResume() se actualizeaza RecyclerView-ul deoarece, atunci cand se deschide galeria, fragmentul este acoperit si intra in starea “Paused”. Daca nu s-ar fi actualizat RecyclerView-ul in aceasta metoda, dupa ce imaginile au fost selectate din galerie, acestea nu o sa apara in AddFragment2.

ChatFragment

TODO implement

ChatFragment2

TODO implement

EditFragment

Afiseaza layout-ul “fragment_edit.xml”. Acesta contine aceleasi campuri ca si layout-ul “fragment_add2.xml” si se pot edita. Toate campurile sunt prepopulate cu informatia necesara din Cloud Firestore.

In onCreateView(), se ataseaza “.setOnClickListener” pe butonul editUploadButton cu metoda uploadPhotos(), butonul editDeleteButton cu metoda deletePhotos() si pe butonul editSaveButton cu metoda validateTextFields(). Se apeleaza si metodele initializeFloorSpinner(), initializeRoomSpinner(), initializeBathSpinner(), initializeBalconySpinner(), initializeTextFields(). La sfarsit se instantiaza si un obiect navController.

In metoda onViewCreated(), din variabila sistemului “arguments”, se preia obiectul de tip Property, transmis prin safeArgs. Acesta este transmist ca parametru in metoda bindData().

Metoda bindData() preia datele din obiectul de tip Property primit ca parametru si le adauga in elementele UI din “fragment_edit.xml”. De asemenea, in cazul spinner-elor, valoarea default este cea din atributele proprietatii, nu prima valoare din lista.

Metoda validateTextFields() valideaza toate elementele UI din “fragment_edit.xml” la fel ca si in AddFragment2, cu diferenta ca, daca in lista de tip boolean nu este niciun element fals, dupa ce se apeleaza metoda saveProperty(), actiunea de navigatie declansata de navController va conduce utilizatorul din EditFragment in AddFragment.

Metoda saveProperty() este ca si cea din AddFragment2, cu diferenta ca dupa ce s-au creat obiectele de tip Address si Property, se verifica cu ajutorul variabilei de tip boolean “imagesHaveBeenUpdated” daca au fost incarcate imagini din galerie si se transmite aceasta variabila ca parametru, impreuna cu obiectul property si variabila globala images in metoda updatePropertyToFirestore() accesata prin viewModel.

Metodele initializeFloorSpinner(), initializeRoomSpinner(), initializeBathSpinner() si initializeBalconySpinner() au acelasi rol. In fiecare dintre acestea se initializeaza un adaptor de tipul ArrayAdapter, care primeste o lista de stringuri, salvate in string resources. Apoi se atribuie adaptorul spinnerului si se seteaza pe acesta un “.onItemSelectedListener”. Listener-ul este initializat cu un obiect ce extinde AdapterView si trebuie sa suprascrie doua metode. In metoda onNothingSelected(), valoarea care se afla deja in spinner va fi salvata in variabilele globale floor, rooms, baths sau balconies. In metoda onItemSelected(), valoarea selectata se salveaza in aceleasi variabile globale.

Metodele uploadPhotos(), deletePhotos(), initializeTextFields(), onActivityResult() si onResume() fac acelasi lucru ca si cele din AddFragment2.

ProfileFragment

Afiseaza layout-ul “fragment_profile.xml”, care contine un “ImageView” pentru poza de profil, un camp prepopulat cu numele utilizatorului, care poate fi editat si trei butoane. Unul pentru salvarea editarii, unul pentru terminarea sesiunii si iesirea din aplicatie si unul pentru stergerea contului cu tot continutul postat de utilizatorul respectiv.

In metoda onCreateView() se initializeaza obiectul viewModel de tip ProfileFragmentViewModel. De asemenea se seteaza “.setOnClickListener” pe butonul profileDeleteButton, profileSaveButton, profileSignoutButton si pe TextView-ul profileProfilePicture.

Listener-ul pe profileDeleteButton intampina utilizatorul cu un pop up window cu

titlul “Delete Account” setat cu “.setTitle()”. Mesajul afisat cu “.setMessage()” este „This is permanent, are you sure?”. Butonul “Yes” se seteaza cu “.setPositiveButton” si butonul “No” cu “.setNegativeButton”. Daca se apasa “Yes”, se incepe cu stergerea utilizatorului din colectia “users” din Cloud Firestore, cu metoda “.delete()” atasata referintei utilizatorului din baza de date. Mai departe, pe referinta colectiei “properties” din Cloud Firestore se ataseaza un “.addSnapshotListener”. Daca este vreo problema, acesta arunca o exceptie, daca nu, prea toate documentele din colectia “properties”. Dupa asta, cu un “.forEach” se trece prin toate aceste documente si daca userId-ul, care este un atribut al fiecarei proprietati, este egal cu id-ul userului curent ce tocmai a fost sters, atunci si proprietatea respectiva se sterge. Mai departe, se sterge contul utilizatorului din tab-ul Authentication din consola Firebase. Daca s-a executat cu succes, se starteaza SplashActivity, urmand ca utilizatorul sa ajunga inapoi la LoginActivity. Daca nu, se afiseaza un mesaj de eroare “Delete account failed”.

Listener-ul pe profileSaveButton are urmatoarea functie. Daca poza selectata din galerie nu este nula, atunci ea este salvata in colectia “users”, in proprietatea utilizatorului curent, impreuna cu modificarea facuta pe numele acestuia, daca este cazul. Daca poza este nula, se apeleaza din nou metoda updateCurrentUser() din obiectul FirestoreUtil, dar se transmite ca parametru pentru poza, null.

Listener-ul pe profileSignOutButton termina sesiunea utilizatorului cu metoda “.signOut()” atasata unui obiect de tip AuthUI. Daca actiunea s-a completat cu succes, se starteaza activitatea LoginActivity cu o intentie care are doua flag-uri. Unul este de tip NEW_TASK si celalalt este de tip CLEAR_TASK. Aceste flag-uri au rolul de a impiedica utilizatorul, dupa ce a dat sign out, sa apese pe butonul “back” si sa ajunga inapoi in ProfileFragment, dar fara sa fie autentificat in aplicatie.

Listener-ul pe profileProfilePicture apeleaza metoda uploadProfilePicture().

Metoda uploadProfilePicture() initializeaza o intentie de tip “ACTION_GET_CONTENT”, care deschide galeria si permite utilizatorului sa aleaga fisiere de tip “image/*”, adica imagini cu orice terminatie: jpg, png, svg etc. Intentia este startata cu metoda startActivityForResult(), cu codul RC_SELECT_IMAGE.

Metoda startActivityForResult() returneaza rezultatele unor activitati. Aici se cauta codul RC_SELECT_IMAGE daca este egal cu requestCode, daca rezultatul e ok cu resultCode si daca datele returnate nu sunt nule. Daca toate aceste conditii se indeplinesc, inseamna ca am primit o imagine. Aceasta este dupa aia convertita din URI in ByteArray si este incarcata cu biblioteca Glide prin GlideApp in TextView-ul profileProfilePictures si imaginea se salveaza in Cloud Firestore cu selectedImage = byteArray (selectedImage e variabila globala care este folosita in listener-ul de la butonul de “Save”). Ultima data, variabila booleana pictureJustChanged (default = false) se seteaza “true”.

In metoda suprascrisa onStart(), verificam daca fragmentul e vizibil. Daca da, incarcam numele utilizatorului in campul de tip TextInputEditText si daca poza de profil nu este nula, o incaram si pe aceasta cu GlideApp direct din Cloud Storage, cu metoda .”pathToReference” din obiectul StorageUtil.

SearchFragment

Afiseaza pe ecran layout-ul “fragment_search.xml”, care contine un RecyclerView ce afiseaza toate postarile cu proprietati si un buton de tipul “FloatingActionButton”, care afiseaza fereastra pentru sortare si filtrare.

In metoda onCreateView() se initializeaza obiectul viewModel te tip SearchFragmentViewModel, obiectul dialog de tip Dialog si un navController.

Dialog este o fereastra mai mica, care nu acopera ecranul si il pune pe utilizator sa introduca informatii suplimentare.

In metoda onViewCreated() se initializeaza RecyclerView-ul prin metoda initRecyclerView(). De asemenea se creeaza un Observer pe variabila properties de tip LiveData, din SearchFragmentViewModel. Cand aceasta variabila sufera schimbari, tot codul care se afla in metoda .observe(), este rulat. Asadar, variabilei globale “propertyList” este golita de toate elementele cu “.removeAll”. Dupa aceea se adauga in aceasta valorile variabilei properties de tip LiveData. Nu in ultimul rand, adaptorul RecyclerView-ului este notificat ca lista s-a schimbat.

In metoda initRecyclerView(), se adauga la acesta topSpacingDecoration, pentru spatiu intre postari. De asemenea se instantiaza obiectul “options”, care adauga referinta in Cloud Firestore si tipul clasei pe care se vor face queries, anume Property. La sfarsit, se initializeaza adaptorul care primeste ca parametru options (detine lista de proprietati din Cloud Firestore) si un listener care are rolul de a directiona utilizatorul spre SearchFragment2, atunci cand acesta apasa pe o postare. Directionarea se face cu ajutorul unei actiuni de navigatie, care primeste ca parametru proprietatea postarii pe care se apasa. Aceasta este declansata cu ajutorul navController-ului.

Din moment ce SearchFragment extinde si interfata FilterInterface din clasa Dialog, pachetul “utils”, este obligatoriu sa se suprascrie cele doua metode definite in interfata.

Metoda onSortSelected() are doi parametri, sort si filter, care sunt pasati mai departe metodei updateRecyclerView(), care se afla in corpul acesteia.

Metoda onPriceRange() are patru parametri, sort, filter, priceMinimum si priceMaximum, care sunt pasati mai departe in metoda updateRecyclerView(), care se gaseste in corpul acesteia.

Metoda updateRecyclerView are parametrii priceMinimum si priceMaximum initializati cu 0. In corpul metodei, se verifica daca cei doi parametri sunt diferiti de 0. Daca da, inseamna ca metoda a fost apelata din interiorul metodei onPriceRange(). Atunci, se initializeaza un obiect “options” care primeste proprietatile din query-ul facut pe colectia “properties” din Cloud Firestore, cu metoda updatePriceQuery() din SearchFragmentViewModel. Aceste proprietati sunt deja filtrate dupa pretul maxim. Mai departe, obiectul options care contine lista de proprietati, impreuna cu priceMinimum, sunt date ca parametri in adaptor, impreuna cu un listener care activeaza o actiune de navigatie spre SearchFragment2, prin navController, atunci cand un utilizator apasa pe o proprietate.

Daca priceMinimum sau priceMaximum sunt 0, inseamna ca metoda updateRecyclerView() a fost apelata din metoda onSortSelected(). Atunci, se instantiaza un obiect “options” care primeste o lista de proprietati sortata in functie de ce a ales utilizatorul in fereastra Dialog, prin metoda “.updateQuery” din SearchFragmentViewModel.

SearchFragment2

Afiseaza layout-ul “fragment_search2.xml” care contine aceleasi campuri ca si “fragment_edit.xml”, dar fara butoane si optiunea de a edita campurile. Si acest layout este prepopulat cu informatia care trebuie din modelul “Property”, transmis ca parametru prin SafeArgs din SearchFragment, atunci cand utilizatorul apasa pe o postare.

In metoda onCreateView() se initializeaza obiectul viewModel de tip SearchFragmentViewModel. TODO search2User click listener

In metoda onViewCreated(), din obiectul sistemului “arguments” preluam proprietatea trimisa din SearchFragment si o stocam in propertyItem. Apoi, salvam userId-ul acestei proprietati in variabila globala otherUserId. Ultima data, apelam metoda bindData() careia ii transmitem ca parametru propertyItem.

Metoda bindData() are mai multe variabile locale pentru a procesa atributele

proprietatii primite ca parametru cu string resources. Dupa aceea, se initializeaza o referinta userRef, a utilizatorului care a postat proprietatea. Daca operatia este cu succes, se salveaza numele acestuia intr-o variabila locala si apoi se populeaza campurile din “fragment_search2.xml” cu datele din variabilele locale.

Alte layout-uri care sunt folosite de alte componente sunt:

“add_view_pager.xml”

Contine doar un “ImageView” care este folosit de ViewPager2 sa afiseze pe ecran o galerie de imagini, cu ajutorul bibliotecii Glide.

“property_post_item.xml”

Contine un CardView care la randul lui contine un ViewPager, cateva pictograme care sunt defapt imagini vectoriale si campuri care afiseaza informatii relevante despre o proprietate. Acest CardView este rotunjit la capete cu proprietatea “cardCornerRadius” si este elevat deasupra RecyclerView-ului cu proprietatea “cardElevation”. De asemenea, impreuna cu continutul lui, este folosit ca si sablon de catre ViewHolder-ul din SearchRecyclerViewAdapter din pachetul “adapters”.

ViewModel

AddFragmentViewModel contine referinte catre Firebase Authentication si Cloud Firestore, live data pentru a sti atunci cand proprietatile sunt modificate in cloud si cateva metode:

Metoda savePropertyToFirestore(property, user, images) foloseste coroutines. Acest lucru ii permite sa ruleze pe alt fir de executie decat cel principal, care e folosit pentru interfata grafica. Aici se salveaza proprietatea in CloudFirestore. Pentru inceput, se initializeaza o variabila locala care tine referinta spre un document nou creat cu metoda “.document()”. Fiind un document nou generat, i se atribuie un id unic. Acesta este mai apoi salvat in atributul propertyId al proprietatii primite ca si parametru, initial fiind gol. Tot aici se initializeaza si atributul userId al proprietatii. Dupa aceea se apeleaza pe documentul nou creat metoda “.set(property).await()”. Metoda .set() incarca proprietatea in colectia “properties” din Cloud Firestore. Metoda .await() este caracteristica coroutines si inseamna ca firul de executie pe care se executa metoda asteapta pana cand se termina salvarea in Cloud Firestore, dupa care trece mai departe. Daca nu s-a reusit salvarea, se scrie un mesaj cu Timber. Daca s-a reusit, se apeleaza metoda uploadImagesToStorage2()

Metoda uploadImagesToStorage2(property, images) foloseste coroutines. Aceasta metoda se ocupa cu salvarea imaginilor in Cloud Storage, deoarece in Cloud Firebase, fiecare document care contine o proprietate, nu poate avea mai mult de 1Mb. Se creeaza o referinta locala spre Cloud Storage, calea acesteia fiind images/userId/propertyId, pentru a se pune toate imaginile care tin de o proprietate intr-un singur director. Daca images este mai mare ca 0, insemnand ca s-a ales cel putin o imagine din galerie, se parcurge fiecare imagine cu un “for” si se pune in Cloud Storage cu metoda .putFile(image). Daca operatia are succes, se initializeaza o variabila locala downloadUrl care contine link-ul de download al imaginii respective din Cloud Storage. Mai departe, se apeleaza metoda updateImagesToDatabase2()

Metoda updateImagesToDatabase2(property, uri.toString()) – foloseste coroutines. Aceasta metoda acceseaza proprietatea care tocmai ce a fost incarcata in Cloud Firestore si in atributul imageList a proprietatii, incarca link-ul de acces al imaginii din Cloud Storage. Astfel, cand o proprietate este descarcata din Cloud Firestore, ViewModel-ul respectiv poate accesa imaginile proprietatii direct din Cloud Storage.

Metoda fetchProperties() – foloseste coroutines. Aceasta are in interior o referinta catre colectia “properties” din Cloud Firestore. Se ataseaza un snapshot listener pe aceasta referinta, care retrage toate proprietatile. Se parcurg proprietatile cu .forEach si daca id-ul utilizatorului curent este acelasi cu atributul userId al proprietatii, atunci proprietatea se adauga unei liste locale. Dupa ce s-a terminat parcurgerea, aceasta lista este atribuita listei _properties care este de tip LiveData.

Metoda updatePropertyToFirestore(property, images, imagesUpdated) utilizeaza si ea coroutines. Aceasta are o referinta locala spre documentul in care este stocata proprietatea primita ca parametru. TODO repar-o

ChatFragmentViewModel TODO chat

SearchFragmentViewModel are o referinta spre colectia “properties” din Cloud Firestore si o lista de proprietati de tip LiveData, _properties.

Metoda updateQuery(filter, sort) returneaza colectia de proprietati dupa ce

se face un query pe ea, in functie de parametrii primiti.

Metoda updatePriceQuery(filter, sort, priceMaximum) returneaza lista de proprietati dupa un query, in functie de filter, sort si de metoda .whereLessThanOrEqualTo(filter, priceMaximum). Acest query o sa returneze toate proprietatile care au pretul mai mic decat priceMaximum.

Metoda getQuery() returneaza colectia de proprietati.

Testare și validare

In acest capitol se explica modalitatea de testare a aplicatiei pentru functionalitatile pe care le ofera.

Testarea este foarte importanta deoarece o aplicatie trebuie sa fie perfect functionala inainte de a fi scoasa pe piata, pentru ca utilizatorul sa aiba o experienta de folosire a aplicatiei cat mai placuta. Acest lucru poate avea un impact sever asupra succesului aplicatiei.

Înregistrare și autentificare

Pentru verificarea acestor functionalitati, se procedeaza astfel:

Se apasa pe butonul de ”Sign in with email”. Se deschide o fereastra in care se introduce email-ul utilizatorului si se apasa ”Next”. Daca aceasta adresa de email a mai fost folosita, atunci utilizatorul este directionat catre o pagina „Welcome back!”, aplicatia recunoscand acest lucru. Se introduce parola si optional se poate apasa pe imaginea cu ochiul din partea dreapta a campului, pentru a afisa sau a ascunde parola. Mai departe se apasa pe ”Sign in”. Daca parola este corecta, utilizatorul s-a autentificat cu succes. Daca nu, utilizatorul poate alege optiunea ”Trouble signing in?” care deschide o noua pagina unde campul email este deja completat. Se apasa butonul ”Send” pentru a primi pe adresa de email un mesaj cu instructiuni pentru schimbarea parolei.

Daca adresa de email nu este recunoscuta, se deschide o pagina in care utilizatorul trebuie sa mai introduca numele si prenumele si parola pentru cont. Dupa ce se apasa pe butonul ”Save”, acesta este introdus in aplicatie.

Daca nu exista conexiune la internet, nu se trece la pasul urmator, aparand un mesaj cu o eroare necunoscuta.

Se apasa pe butonul de „Sign in with Google”. Se deschide o fereastra de alegere a unui cont Google deja inregistrat de dispozitiv. Dupa ce se alege un cont din lista, utilizatorul este introdus in aplicatie. Daca s-a oprit conexiunea la internet, nu se trece mai departe.

Daca se alege optiunea de adaugare a unui cont nou, se verifica identitatea utilizatorului printr-o metoda cum ar fi scanarea amprentei sau folosirea modelului. Dupa aceasta, se introduce contul de Google. De asemenea, se poate crea un cont nou sau se poate revendica parola unui cont. Daca totul este in regula si se apasa ”Next”, apare o fereastra de confirmare ca utilizatorul se increde in aplicatia Kariru. Daca se apasa pe butonul ”Allow”, utilizatorul este introdus in aplicatie.

Filtrare și sortare

Pentru filtrare si sortare, se apasa butonul ”Search” din bara de navigatie. Apoi se apasa pe butnoul din dreapta jos a paginii. Apare fereastra dialog unde se poate apasa pe optiunea ”Room” sau ”Price”. Daca se apasa pe ”Room”, se trece la urmatoarea fereastra dialog, unde se alege tipul de sortare: ”Ascending” sau ”Descending”. Dupa selectarea acestuia, lista de proprietati este sortata. Daca se alege optiunea ”Price”, se trece la fereastra dialog cu sortarea, unde se alege optiunea ”Ascending” sau ”Descending”. Mai departe, apare fereastra dialog ”Set price range” unde se introduce pretul minim si pretul maxim. Daca se apasa ”Cancel”, fereastra se inchide si operatia este abordata. Daca se apasa ”Ok”, proprietatile se filtreaza in functie de preturile introduse.

Adăugare proprietate

Pentru a adauga o proprietate se apasa pe butonul “Add” din bara de navigatie. Dupa aceea se apasa pe butonul “Add property” care deschide Google Maps. Se introduce adresa in campul “Search” si se selecteaza predictia corecta. In acel moment apare un buton “Next” in partea de jos a hartii. Dupa ce se apasa pe acesta, utilizatorul este redirectionat spre pagina de “Upload property”. Daca se apasa pe butonul “Save” direct, se vor activa erorile la campurile care trebuie completate. Fiecare dintre aceste campuri are un numar maxim de caractere permis peste care nu se poate trece. De asemenea, este setat deja tipul de text pe care utilizatorul il poate introduce. Nu o sa poata sa scrie text in campurile care necesita cifre. Dupa ce se completeaza toate campurile si se incarca imagini, care nu pot depasi numarul setat, in acest caz 5, se apasa butonul “Save”. Daca nu este conexiune la internet cand se apasa pe buton, utilizatorul va fi directionat spre pagina “Add” unde va vedea postarea lui, dar fara imagini. Dupa ce se porneste conexiunea la internet, se termina procesul de salvare, apare mesajul “Property has been saved” si se incarca imaginile in postare.

Editare proprietate

Pentru a edita o proprietate, se apasa pe butonul “Add” din bara de navigatie. In aceasta pagina, utilizatorul apasa pe una din lista de proprietati pe care le-a postat. In continuare se deschide o pagina cu detaliile proprietatii, chiar daca nu este conexiune la internet, pentru ca aceasta proprietate a fost deja incarcata in memorie. Dupa ce utilizatorul modifica ce doreste, sub constrangerea validarilor si a limitei de caractere, acesta poate apasa butonul “Save” sau “Delete property”, in caz ca doreste sa stearga postarea.

Editare profil

Pentru a se edita profilul utilizatorului, se apasa pe butonul “Profile” din bara de navigatie. Aici, se poate modifica numele utilizatorului si se poate adauga imagine, apasand pe imaginea standard din partea de sus a ecranului. Dupa aceea se deschide Galeria de unde se poate alege o imagine. La sfarsit, se apasa pe butonul “Save”, urmand ca schimbarile sa fie actualizate in baza de date. Daca nu este conexiune la internet cand se apasa pe “Save”, se va relua procesul de salvare dupa ce apare din nou conexiunea la internet.

Chat

TODO

Delogare și ștergere cont

In pagina „Profile”, se apasa pe butonul ”Sign not” pentru delogare. Aceasta actiune functioneaza chiar daca nu exista conexiune la internet. Mai departe, utilizatorul este directionat catre pagina de autentificare si daca apasa pe butonul ”Back” nu poate sa se intoarca inapoi in pagina profilului fara sa fie autentificat.

Daca se apasa pe butonul „Delete account”, apare o fereastra dialog cu mesajul ”This is permanent, are you sure?”. Daca se apasa pe butonul ”Yes”, contul utilizatorului este sters din baza de date si este redirectionat spre pagina de autentificare. De asemenea, daca se autentifica inapoi in acelasi cont, se observa ca nu mai are proprietatile postate si numele sau imaginea de profil ca inainte.

Concluzii

Rezultate obținute

In urma dezvoltarii acestei aplicatii, scopul principal a fost atins. Am reusit sa realizez toate obiectivele propuse.

Unul dintre cele mai importante obiective a fost adaugarea proprietatii, deoarece procesul de dezvoltare a constat din foarte multi pasi: Setarea Google Places API, crearea design-ului pentru pagina de “Add” din bara de navigare si pagina de “Upload property”, validare pe campuri, navigare intre mai multe fragmente si activitati cu trimitere de date intre acestea, folosirea Cloud Firestore si Cloud Storage pentru a salva si retrage datele. Am stat mult pana sa completez acest obiectiv si am invatat multe.

De asemenea, folosirea ultimelor tehnici de programare cu data-binding, live data, arhitectura MVVM, Firebase, NavigationUI, toate acestea in limbajul Kotlin, m-a pregatit pentru a-mi gasi un loc de munca foarte usor.

Direcții de dezvoltare

Unele din imbunatatirile care pot fi aduse aplicatiei Android pentru a garanta o sansa mare a succesului pe piata includ :

Adaugarea unei pagini unde studentii sa posteze faptul ca isi cauta colocatari

Adaugarea unui card de credit pentru a plati chiria lunar, eventual automat, proprietarului

Mai multe optiuni pentru filtrare si sortare

Un buton care deschide harta si afiseaza locatia proprietatilor filtrate

Mai multe detalii in procesul de adaugare proprietate

Design UI mai placut

Similar Posts