Aplicație mobilă pentru administrarea blocurilor de locuințe [628379]

Universitatea „Politehnica” din București
Facultatea de Electronică, Telecomunicații și Tehnologia Informației

Aplicație mobilă pentru administrarea blocurilor de locuințe

Proiect de diplomă
prezentat ca cerință parțială pentru obținerea tit lului de
Inginer în domeniul Calculatoare și Tehnologia Informației
programul de studii de licență Ingineria Informa ției

Conducători științifici Absolvent: [anonimizat]. Adrian Ioan LIȚĂ Andrada ȘERBAN
Prof. Dr. Ing. Ovidiu GRIGORE

2018

Anexa 6

Copyright © 2018, Șerban V. Andrada

Toate drepturile rezervate

Autorul acordă UPB dreptul de a reproduce și de a distribui public copii pe hîrtie sau
electronice ale acestei lucrări, în formă integrală sau parțială.

Declarație de onestitate academică

Prin prezenta declar că lucrarea cu titlul “Aplicație mobilă pentru administrarea
blocurilor de locuințe ”, prezentată în cadrul Facultății de Electronică, Telecomunicații și
Tehnologia Informației a Universității “Politehnica” din București ca cerință parțială
pentru obținerea titlului de Inginer în domeniul Electronică și Telecomunicații , programul
de studii Calculatoare și Tehnologia Informației este scrisă de mine și nu a mai fost
prezentată niciodată la o facultate sau instituție de învățământ superior din țară sau
străinătate.

Declar că toa te sursele utilizate, inclusiv cele de pe Internet, sunt indicate în lucrare,
ca referințe bibliografice. Fragmentele de text din alte surse, reproduse exact, chiar și în
traducere proprie din altă limbă, sunt scrise între ghilimele și fac referință la sur să.
Reformularea în cuvinte proprii a textelor scrise de către alți autori face referință la sursă.
Înțeleg că plagiatul constituie infracțiune și se sancționează conform legilor în vigoare.

Declar că toate rezultatele simulărilor, experimentelor și măsur ătorilor pe care le
prezint ca fiind făcute de mine, precum și metodele prin care au fost obținute, sunt reale
și provin din respectivele simulări, experimente și măsurători. Înțeleg că falsificarea
datelor și rezultatelor constituie fraudă și se sancțione ază conform regulamentelor în
vigoare.

București, 21.06.2018 Absolvent: [anonimizat] 11
Lista acronimelor ………………………….. ………………………….. ………………………….. ………………………….. …. 13
Introducere ………………………….. ………………………….. ………………………….. ………………………….. ………….. 15
Capitolul 1 – Introducere în Android ………………………….. ………………………….. ………………………….. …… 17
1.1 Istoric ………………………….. ………………………….. ………………………….. ………………………….. …………. 17
1.2 Arhitectura sistemului Android ………………………….. ………………………….. ………………………….. ….. 17
1.3 Versiuni Android ………………………….. ………………………….. ………………………….. …………………….. 20
1.4 Funcționalități Android ………………………….. ………………………….. ………………………….. …………….. 21
Capitolul 2 – Android Studio IDE ………………………….. ………………………….. ………………………….. ……….. 23
2.1 Prezentare generală ………………………….. ………………………….. ………………………….. ………………….. 23
2.2 Structura unui proiect ………………………….. ………………………….. ………………………….. ……………….. 26
Capitolul 3 ………………………….. ………………………….. ………………………….. ………………………….. …………… 29
Firebase ………………………….. ………………………….. ………………………….. ………………………….. ………………. 29
3.1 Istoric ………………………….. ………………………….. ………………………….. ………………………….. ……….. 29
3.2 Servicii Fi rebase ………………………….. ………………………….. ………………………….. ……………………… 29
3.3 Adăugarea serviciilor Firebase ………………………….. ………………………….. ………………………….. ….. 30
3.4 FirebaseAuth ………………………….. ………………………….. ………………………….. ………………………….. . 30
3.4.1 Prezentare generală ………………………….. ………………………….. ………………………….. ……………………….. 30
3.4.2 Autentificarea cu E -mail și parolă folosind SDK -ul Firebase ………………………….. ………………………….. . 31
3.5 Firebase Storage ………………………….. ………………………….. ………………………….. ………………………. 34
3.5.1 Prezentare generală ………………………….. ………………………….. ………………………….. ……………………….. 34
3.5.2 Utilizare ………………………….. ………………………….. ………………………….. ………………………….. ……………. 35
3.5.3 Reguli le de securitate ………………………….. ………………………….. ………………………….. ……………………… 36
3.6 Cloud Firestore ………………………….. ………………………….. ………………………….. ………………………… 38
3.6.1 Prezentare generală ………………………….. ………………………….. ………………………….. ……………………….. 38
3.6.2 Utilizare ………………………….. ………………………….. ………………………….. ………………………….. ……………. 39
Capitolul 4 ………………………….. ………………………….. ………………………….. ………………………….. …………… 41
Descrierea aplic ației ………………………….. ………………………….. ………………………….. ………………………….. 41
4.1 Prezentare generală ………………………….. ………………………….. ………………………….. ………………….. 41

4.2 Gestionarea conturilor ………………………….. ………………………….. ………………………….. ………………. 41
4.2.1 Înregistrarea utilizatorului : ………………………….. ………………………….. ………………………….. ………………. 41
4.2.2 Autentificarea utilizatorului ………………………….. ………………………….. ………………………….. …………….. 44
4.2.3 Recuperarea parolei ………………………….. ………………………….. ………………………….. ……………………….. 45
4.3 Meniul principal ………………………….. ………………………….. ………………………….. ………………………. 46
4.4 Profil ………………………….. ………………………….. ………………………….. ………………………….. …………. 47
4.5 Conversații ………………………….. ………………………….. ………………………….. ………………………….. …. 49
4.6 Anunțuri ………………………….. ………………………….. ………………………….. ………………………….. …….. 51
4.7 Administrație ………………………….. ………………………….. ………………………….. ………………………….. . 53
Concluzii ………………………….. ………………………….. ………………………….. ………………………….. …………….. 57
Referințe ………………………….. ………………………….. ………………………….. ………………………….. ……………… 59

11

Lista figurilor

Figura 1.1 Arhitectura Android ………………………………………………………………….………18
Figura 1.2 Versiuni Android ……………………………………………………………………………20
Figura 2.1 Android Studio fereastră principală …………………………………………………………23
Figura 2.2 Layout Editor ………………………………………………………………………………..24
Figura 2.3 Modulul de aplicație Android ………………………………………………………..………26
Figura 3.1 Autentificare F irebase ……………………………………………………………….………31
Figura 3.2 E -mail de confirmare ………………………………………………………………………..32
Figura 3.3 Mesaj de confirmare …………………………………………………………………………33
Figura 3.4 Listă Utilizatori ……………………………………………………………………….……..34
Figura 3.5 Vizualizare Firebase Storage ……………………………………………………..…………35
Figura 3.6 Vizualizare informații despre un fișier Firebase Storage ……………………………………35
Figura 3.7 Încărcarea fișierelor în baza de date …………………………………………………………36
Figura 3.8 Setul de reguli implicite pentru Firebase Storage …… ……………… ………………………37
Figura 3.9 Structura datelor în Firestore ………………………………………… ………………………38
Figura 4.1 Ecran înregistrare …………………………………………………… …..…………………..42
Figura 4.2 Mesaj de eroare ……………………………………………………… ……. ………………..42
Figura 4.3 Colecția "Utilizatori" ………………………………………………… .……………………..43
Figura 4.4 Mesaj trimitere mail de confirmare …………………………………… .……………………44
Figura 4.5 Ecran autentificare ……………………………………………………… …………………..44
Figura 4.6 Mesaj de eroare ………………………………………………………… ……. ……………..45
Figura 4.7 Ecran recuperare parolă …………………………………………………… ..………………45
Figura 4.8 Formular resetare parolă ……………………………………………………………… .……46
Figura 4.9 Ecran meniu principal ……………………………………………………………………….46
Figura 4.10 Profilul utilizatorului ……………………………………………………………………….47
Figura 4.11 Ecran listă locatari ………………………………………………………………… ………..49
Figura 4.12 Conversație …………………………………………………………………… ……….. …..51
Figura 4.13 Structura colecției "Anunțuri" ……………………………………………… ……… ………52
Figura 4.14 Ecran anunțuri ……………………………………………………………… ……. ………..52
Figura 4.15 Bara de m eniu pentru administrație ………………………………………… ..……………53
Figura 4.16 Eroare accesare date nepermise ……………………………………………………………53
Figura 4.17 Vizualizare consum apă ………………………………………………………… …………54
Figura 4.18 Vizualizare total ………………………………………………………………… …..……..55

12

13
Lista acronimelor

AOT – Ahead of Time
API – Application Programming Interface
APK – Android Application Package
ART – Android Runtime
AVD – Android Virtual Device
GIF – Graphics Interchange Format
GPL – General Public License
HAL – Hardware Abstraction Layer
ID – Identity Document
IDE – Integrated Development Environment
NDK – Native Development Kit
OHA – Open Headset Alliance
RGB – Red Green Blue
RTT – Round -Trip-Time
SDK – Software Development Kit
URI – Uniform Resou rce Identifier
URL – Uniform Resource Locator
VLB – Viața la Bloc
XML – eXtensible Markup Language

14

15

Introducere

Scopul acestei lucrări este realizarea unei soluții prin care blocurile de locatari să își poată
gestiona cu ușurință detaliile administrative.
În prezent există un număr mare de companii care se ocupă cu administrarea blocurilor de
locuințe, însă astfel de servicii sunt costisitoare și nu oricine poate apela la o astfel de soluț ie. O altă
variantă de luat în calcul este reprezentată și de soluțiile web, și acestea în număr destul de mare.
Dezavantajul principal al acestora este lipsa de portabilitate. Site -urile web, deși pot fi încărcate și de pe
dispozitive mobile, nu pot fi la fel de eficiente precum o aplicație nativă, iar funcționalitățile lor sunt
limitate.
Decizia privind realizarea aplicației pentru sistemul de operare Android a fost luată ținând cont
de argumentele de mai sus, dar și de statisticile conform cărora peste 2 miliarde de oameni folosesc
dispozitive care rulează acest sistem de operare. În plus, alegerea acestei teme este bazată și pe un motiv
subiectiv, și anume nevoia personală a unei astfel de soluții.
Pe lângă toate cele de mai sus, există și un interes per sonal pentru dezvoltarea de aplicații mobile.

Obiectivele lucrării sunt:
• Introducere în sistemul de operare Android(scurtă prezentare, statistici, funcționalități)
• Familiarizarea cu mediul de dezvoltare Android Studio
• Prezentarea serviciilor oferite de pl atforma Firebase și identificarea celor necesare în
realizarea prezentei aplicații
• Implementarea propriu -zisă a aplicației și descrierea acesteia

16

17
Capitolul 1 – Introducere în Android

1.1 Istoric

Compania Android,Inc. a fost fondată în anul 2003, în Palo Alto, California, cu scopul de a crea
dispozitive mobile inteligente . Doi ani mai târziu , în iulie 20 05, aceasta a fost achiziționată de către
Google, care a declarat că Android va fi mult mai mult decât un simplu telefon. [1]
În noiembrie 2007 este anunțată lansarea unei platform e Android, dezvoltată și întreținută de o
organizație non-profit denumită „Open Headset Alliance” , al cărei scop este de a accelera dezvoltarea
dispozitivelor mobile și de a le oferi consumatorilor o experiență mai plăcută . În prezent această
organizație este alcătuită din 8 4 de membri, printre care se numără: Google, Accenture, Intel, Dell,
Fujitsu, Huawei, HTC, Vodafone și T-Mobile. [2]
Android este o platformă open -sourc e ce le permite dezvoltatorilor să realizeze aplicații ce
ruluează pe o gamă largă de dispozitive mobile. În calitate de dezv oltator ai acces la codul sursă al întregii
platforme, iar ca producător te poți asigura că sistemul de operare Android este compatibil cu dispozitivul
hardware.

1.2 Arhitectura sistemului Android

După cum se poate observa și în figura 1.1, sistemul Andro id dispune de o arhitectură alcătuită
din 6 niveluri ce comunică între ele:
• Nucleul Linux;
• Nivelul de abstractizare a l hardware -ului;
• Biblioteci C++ native;
• Android Runtime;
• Cadrul pentru aplicații;
• Aplicațiile sistemului;

18

Figura 1.1 Arhitectura Android [3]

Android este construit având la baz ă nucleul Linux . Unul dintre motivele alegerii acestui tip de
nucleu îl reprezintă capacitatea sistemului de operare Linux de a fi portat cu ușurință pe diverse
arhitecturi hardware. Multe funcții din Linux sunt scrise în limbajul C, aspect ce le permite
dezvoltatorilor de aplicații portarea sistemului Android pe o mare varietate de dispozitive. Pe lângă
ușurința cu care poate fi portat, acesta beneficiază și de securitatea și funcțio nalitățile sistemului Linux.

19
Funționalități cheie ale sistemului Android, precum administrarea energiei consumate și a memoriei,
provin din nucleul Linux. [1]
Nivelul de abstractizare hardware(HAL) conține interfețe standard , implementate de către
producătorii de componente hardware, care fac legătura între componentele hardware ale dispozitivului
și nivelul cadrului de aplicații. Acesta este alcătuit dintr -o multitudine de biblioteci, fiecare
implementând o interfaț ă pentru o componentă hardware sp ecifică precum camera foto sau tastatura .
Când un API realizează un apel pentru a accesa o anumită componentă hardware, sistemul Android va
încărca biblioteca pentru respectiva componentă. [3]
Multe dintre componentele și serviciile fundamentale ale sis temului Android, precum ART și
HAL sunt implementate folosind biblioteci native scrise în C sau C++. Pentru a putea folosi
funcționalitățile unor astfel de biblioteci, platforma Android pune la dispoziție Android Native
Development Kit(NDK), un instrument prin care este posibilă implementa rea unor părți din aplicație în
cod nativ. [3]
Android Runtime(ART) reprezintă o mașină virtuală Java, implementată începând cu Android
5.0, care folosește compilare AOT(Ahead of Time). Spre deosebire de predecesorul său, Dalvik, care
interpreta secvențe d e cod, ART compilează(traduce) codul sursă al aplicației la instalarea acesteia în
limbaj mașină care poate fi executat de dispozitiv.
Întregul set de funcționalități al sistemului de operare Android este disponibil prin API -uri scrise
în limbajul Java. A ceste API -uri sunt la îndemâna dezvoltatorilor și le pot folosi în aplicațiile lor fără a
mai fi nevoie să le rescrie. Câteva exemple de API -uri:
• Sistemul de vizualizare folosit pentru a crea elementele unei interfețe grafice cu utilizatorul;
• Administrator ul de resurse oferă acces la resursele necesare în dezvoltarea aplicației;
• Administratorul de notificări permite aplicațiilor să afișeze alerte personalizate;
• Administratorul de activități gestionează ciclul de viață al aplicațiilor care rulează pe
dispozi tiv;
• Furnizorul de conținut permite aplicațiilor să acceseze datele altor aplicații;
Colecția de a plicații ale sistemului este formată din aplicații fundamentale ale sistemului
Android(Contacte, Calculator, Calendar etc.), aplicații preinstalate de către p roducătorul
dispozitivului(TouchWiz – Interfața cu utilizatorul dezvoltată de Samsung), aplicații preinstalate de către
furnizorul de servicii mobile și aplicațiile instalate de către utilizator.

20
1.3 Versiuni Android

Figura 1. 2 Versiuni Android [3]

În figura de mai sus sunt prezentate versiunile existente , la momentul actual, ale sistemului de
operare Android. Pe lângă numărul versiunii și denumirea acesteia se poate observa și distribu ția acestora
pe dispozitivele Android. Numărul versiunii este exp rimat în format major.minor și indică dacă
modificările aduse față de versiunea anterioară sunt unele semnificative sau nu.
Fiecare nouă versiune este compatibilă cu ver siunile anterioare, dar integrează și o serie de noi
funcționalități . De exemp lu, dacă realizăm o aplicație cu versiunea 6.0 (Marshmallow), aceasta va putea
rula pe toate dispozitivele cu versiuni mai noi, adică pe 62,3% din numărul total de dispozitive de pe
piață .

21
În momentul creării unui nou proiect în mediul de dezvoltare Android Studio utilizatorul trebuie
să selecteze versiunea minimă pe care aplicația va rula. Ace astă decizie trebuie luată având în vedere că,
cu cât API -ul ales este mai mare, cu atât vom putea implementa mai multe caracteristici noi, dar numărul
de dispo zitive ce vor putea rula aplicația va fi mai mic. Conform documentației ofi ciale Android este
recomandat să alegem versiunea minimă astfel încât să acoperim 90% dintre d ispozitivele active și
versiunea vizată ca fiind una dintre ultimele versiuni.

1.4 Funcționalități Android

Android este un produs open -source, destinat unei game largi de dispozitive . Fiind distribuit sub
licența Apache (cu excepția nucleului Linux) , acesta oferă dezvoltatorilor posibilitatea de a -l modifica în
mod gratuit și de a distribui versiunile modificate . Din acest motiv nu există configurații hardware sau
software standard. Pentru a asigura totuși compatibilitatea tuturor impleme ntărilor, a fost realizat un
program de compatibilitate numit „Android Compatibility Pr ogram”. Aici sunt prezentate detaliile
tehnice al e platformei Android, dar și instrumente ce permit dezvoltatorilor să realizeze dispozitive
capabile să ruleze aplicațiile. Google P lay permite vizualizarea și inst alarea doar a aplicațiil or ce pot fi
rulate pe dispozitivul de pe care acesta este accesat . [4]
Ultima versiune a acestui sistem de operare este Android 8.1, cu API 28. Printre funcționalităț ile
adăugate odată cu lansarea acestuia se numără:
• Identificarea poziției utilizatorilor în interiorul unei clădiri – acest lucru este posibil utilizând
API-ul RTT pentru a măsura distanța față de cel mai apropiat punct de acces ce folosește
protocolul IEE 802.11mc. Dacă dispozitivul are acces la trei astfel de pun cte de acces, poziția
indicată are o acuratețe d e unul sau doi metr i;
• Identificarea zonelor din interfața grafică ce nu sunt vizibile datorită noilor ecrane cu decupaje
pentru cameră și difuzoare – acestea pot fi afișate în alte zone la dorința utilizatorului;
• Notificările – este posibil ca un utilizator să vizualizeze imaginile primite direct în notificări, dar
și să răspundă la mesajele text;
• Animații – este introdusă o nouă clasă pentru desenarea și afișarea imaginilor animate de tipul
GIF si WebP numită AnimatedImageDrawable;
• Securitate – permite criptarea copi ilor de rezervă a datelor folosind un cod generat de utilizator;
• Rotirea ecranului – utilizatorul va putea declanșa în mod manual rotirea ecranului dacă dorește;
• Îmbunătățirea funcționalitățiilor disponibile și în versiunile anterioare. [5]

22

23
Capitolul 2 – Android Studio IDE

2.1 Prezentare generală
Android Studio a fost lansat de Google în anul 2013, înlocuind Eclipse ca principalul mediu de
dezvoltare pentru aplicațiile native Android. Acesta poate fi rulat pe dispozitivele cu următoarele sisteme
de operare: Windows, macOS sau Linux. În prezent Android Studio este la versiunea 3.1.3, versiune
lansată în iunie 2018.

Figura 2.1 Android Studio fereastră principală
Fereastr a principală în Android Studio, după cum se poate observa și în figura 2.1, are
următoarele componente:

24
1) Bara de instrumente – prezintă o selecție de butoane ce permit accesul rapid la o gamă largă de
comenzi precum rularea aplicației. Aceasta poate fi modi ficată după cum d orește dezvoltatorul;
2) Fereastra de editare – afișează conținutul fișierului la care dezvoltatorul lucrează în momentul
actual;
3) Fereastră individuală de instrumente – afișează structura proiectului , în modul implicit, Android ;
4) Bara ferestrei de instrumente – conține butoane ce permit extinderea sau restrângerea ferestrelor
individuale de instrumente;
5) Bara de sta re – afișează starea proiectului precum și unele avertismente ;

În Android Studio există două modalități de a realiza interf ața cu utilizatorul :
• Procedural – prin utilizarea codului Java pentru definirea interfeței. Acest tip de abordare este
utilizat în situațiile în care interfața are o structură dinamică;
• Declarativ – prin utilizarea limbajului de marcare descriptiv XML. [6]
Acesta din urmă prezintă avantajul de a putea fi realizat mult mai ușor în Android Studio, deoarece
este posibilă utilizarea unui editor de text pentru editarea fișierelor XML sau a unui utilitar vizual numit
LayoutEditor (figura 2. 2) care permite definir ea interfe ței grafice prin operații de tipul drag -and-drop.
Orice operație realizată în LayoutEditor este transpusă automat în fișierul XML, inversa fiind și ea
valabilă. Trecerea de la utilitarul vizual la editorul text se face prin apăsarea butonului „Te xt” marcat cu
* în Figura 2. 2. [6]

Figura 2. 2 Layout Editor

25
Layout Editor este alcătuit din următoarele ferestre:
1) Editorul vizual – dezvoltatorul poate vizualiza o proiecție a interfeței grafice: așa cum aceasta
este prezentată utilizatorului sau sub forma unei schițe. Alegerea uneia sau a ambelor
modalități de vizualizare se face din bara de instrumente;
2) Bara de instrumente – permite modificarea condițiilor în care este afișată interfața grafică.
Este posibilă: schimbarea temei aplicației, a dispoziti vului utilizat, precum și a orientării (între
mod portret și peisaj) și limbii acestuia;
3) Component tree – permite vizualizarea structurii interfeței grafice;
4) Paleta – conține toate elementele grafice ce pot fi adăugate. Acestea sunt grupate în funcție
de atributele lor;
5) Atributele – listă de atribute ce pot fi modificate pentru fiecare element selectat;

ConstraintLayout reprezintă modalitatea împlicită în Android Studio pentru a defini amplasarea
elementelor interfeței grafice. Acesta este disponibil pe dispozitivele Android cu un API de nivel 9 sau
mai mare.
Pentru a defini poziția unui element, ConstraintLayout se folosește de constrângeri. O
constrângere reprezintă o regulă de aliniere față de alt element. Este obligatorie definirea unei
constrângeri pe direcție orizontală și pe direcție verticală pentru fiecare element. În caz contrar, la rularea
aplicației, toate elementele ce nu îndeplinesc aceste condiții vor apărea în colțul din stânga sus al
ecranului.
Dacă nu se dorește adăugarea fiecărei con strângeri manual, se poate folosi una dintre funcțiile:
• Infer Constraints – scanează interfața(unde toate elementele au fost așezate pe pozițiile dorite) și
produce un set de constrângeri pentru toate elementele;
• Autoconnect – la aducerea fiecărui element produce un set de constrângeri în funcție de poziția
în care acesta este plasat, dar și de elementele deja existente;
O caracteristică principală a sistemului de operare Android este diversitatea dispozitivelor pe care
acesta poate rula. Acest aspect poa te reprezenta un impediment pentru dezvoltatorii de aplicații în
momentul în care doresc testarea aplicației pe mai multe variante de dispozitive fizice.
Android Studio pune la dispoziția dezvlotatorilor un emulator de Android ce permite simularea
diverse lor dispozitive fizice pe care se dorește testarea aplicației. Acest lucru este posibil prin creare unor
dispozitive virtuale (AVD) cu diferite configurații.
La realizarea unui AVD se poate alege tipul dispozitivului(televizor, ceas, telefon sau tabletă),
modelul acestuia , dar și versiunea sistemului de operare Android.

26
2.2 Structura unui proiect
Un proiect realizat în Android Studio conține unul sau mai multe module cu fișiere de cod sursă
și fișiere de resurse. Există trei tipuri de module:
1. Module de aplicații Android – conțin fișiere esențiale și unele ș abloane corespunzătoare tipului de
dispozitiv pentru care este destinată aplicația. Sunt de mai multe tipuri:
1.1. Pentru telefoane și tablete;
1.2. Pentru televizoare Android;
1.3. Pentru ceasuri inteligen te:
1.4. Pentru ochelari;
2. Module de bibliotecă – conțin fișiere de cod reutilizabil. Acestea pot fi de două tipuri:
2.1. Biblioteci Android – pot conține orice tip de fișier utilizat într -un proiect Android;
2.2. Biblioteci Java – pot conține doar fișiere Java;
3. Module Go ogle App Engine – conțin cod pentru serviciul de backend Google Cloud ; [3]

Figura 2. 3 Modulul de aplica ție Android
Într-un modul de aplicație Android , precum cel prezenta t în figura de mai sus, fișierele sunt
grupate în trei categorii :
1. Manifest – conține fișierul AndroidManifest.xml în care sunt descrise informa ții detaliate despre
aplicație precum:
1.1. Denumirea pachetului aplicației ;
1.2. Versiunea codului aplicației ;

27
1.3. Unele caracteristici vizuale: tema generală a aplicației(aceasta poate fi modificat ă pentru fiecare
activitate în parte), denumirea și pictograma aplicației ;
1.4. Permisi unile aplicației – de exemplu permisiu nea de a utiliza internetul dacă este necesar;
1.5. Toate activitățile aplicaț iei;
2. Java – conține codul sursă Java ;
3. Res – conține resursele aplicației .
Sintaxa pentru a accesa resursele diferă în funcție de ti pul de fișier din care acestea s unt accesate
astfel: pentru a accesa resursele din fișiere de cod sursă folosim:
R.<tipul_resursei>.<denumirea_resursei> , iar pentru a le accesa din fișiere xml folosim:
@<tipul_resursei>/<denumirea_resursei> .

Resursele sunt grupate în următoarele directoare:

3.1. Drawable – totalitatea resurselor grafice
3.2. Layout – conține fișiere cu extensia .xml ce definesc toate interfețele grafice utilizate pentru a
dispune conținutul aplicației;
3.3. Menu – conține diferitele meniuri atașate interfețelor grafice din cadrul aplicației;
3.4. Mipmap – conține pictograme aferen te tuturor rezoluțiilor posibil e (printre ele se numără și
picto grama aplicației;
3.5. Values – păstrează constant ele ce pot fi referențiate în cod .
3.5.1. Colors.xml – conține culorile ce pot fi folosite ca refe rință. Fiecare culoare este
reprezentată de o denumire unică și de un cod RGB, de exemplu:
<color name="colorPrimary">#29b6f6</color>
3.5.2. Strings.xml – conține șiruri de caractere definite în cadrul aplicației. Fiecare element este
reprezentat de o denumire pe baza c ăreia este referențiat și de șir ul propriu -zis de caractere,
de exemplu: <string name="app_ name">VLB</string>
3.5.3. Styles.xml – conține stilurile sau temele folosite în cadrul aplicației ;

Mediul de dezvoltare Android Studio folosește Gradle, un mecanism automat ce compilează
resursele și codul sursă și le împachetează într -un fișier de tipul APK(And roid Application Package)
pentru a putea fi testate și distri buite. Gradle permite definirea pro priilor configurații, fiecare având
propriul set de coduri sursă și de resurse , având însă acces la părțil e comune pentru toate versiunile
aplicației. [3]
În fișierul build.gradle (modul ) este definită configurația unui singur modul și conține informații
precum: numele și codul versiunii la care se află aplicația, versiunea minimă și versiunea vizată a SDK –
ului pentru care aplicația poate fi rulată , dar și to ate bibliotecile utilizate . Configurația pentru compilarea
tuturor modulelor proiectului se găseș te în fișierul build.gradle(proi ect).

28

29

Capitolul 3
Firebase

3.1 Istoric
În anul 2014 compania Firebase cu sediul în San Francisco a fost achiziționată de către Google.
Această companie oferea dezvoltatorilor soluții de integrare a serviciilor de cloud în aplicații mobile sau
web. După achiziționarea companiei, Google a combinat serviciile deja oferite cu caracteristici
comp lementare incluse anterior în platforma Google Cloud. La toate acestea s -au adăugat și funcții
caracteristice platformelor Fabric și Crashlytics, achiziționate de Google la începutul anului 2017. [7]

3.2 Servicii Firebase
Firebase pune la di spoziția ut ilizatorilor urmă toarele servicii:
1) Utilizate la realizarea unei aplicații puternice și sigure
• Cloud Firestore
• ML Kit
• Funcții Cloud
• Autentificare
• Baza de date in timp real
• Stocarea datelor în Cloud
• Găzduire web
2) Monitorizarea performanțelor și stabilității a plicației
• Crashlytics
• Monitorizarea performan țelor
• Testare
3) Pentru dezvoltarea și analiza unei aplicații cu milioane de utilizatori
• Predicții
• Testarea A/b
• Configurarea la distanța
• Mesaje Colud
• Link -uri dinamice
• Indexarea
• Google Anal ytics
În contin uare voi detalia doar serviciile utilizate pentru realizarea acestei aplicații.

30
3.3 Adăugarea serviciilor Firebase
Înainte de a adăuga Firebase la proiect trebuie îndeplinite următoarele condiții:
• Trebuie instalată ultima versiune de Android Studio ;
• Dispozitivul pe care este instalată aplicația trebuie să rulez e Android 4.0 (Ice Cream Sandwi ch)
sau o versiune mai recentă și să aibă instalate serviciile Google Play , cel puțin versiunea 15.0.0;
• Dezvoltatorul aplicației trebuie să aibă un cont Google; [ 8]

Conectare a la Firebase se poate face în două moduri: folosind Firebase Assistant sau manual.
Dacă folosim Firebase Assistant în Android Studio, acest pas devine foarte simplu. Tot ce trebuie să
facem este:
1) Conectăm contul de Google la Android Studio;
2) Apăsăm Tools -> Firebase;
3) Se va deschide o nouă fereastră ce conține lista tuturor serviciilor Firebase ce pot fi adăugate la
aplicație. După selectarea unuia dintre ele, apăsăm butonul de conectare la Firebase;
4) Alegem denumirea proiectului ;
5) Proiectul este creat și poa te fi accesat din consola Firebase
Adăugarea fiecărui serviciu în parte se face select ându -l din fereastra de asistență și apăsând
butonul adaugă. Trebuie verificat în build.gradle că a fost adău gată ultima versiune a bibl iotecii
serviciului repectiv pentru a nu apărea erori.

3.4 FirebaseAuth

3.4.1 Prezentare generală
Reprezintă un serviciu de autentificare ce permite utilizatorilor în registrarea în aplicație folosind
urmă toarele modalități:
• Pe baza mail -ului și a unei parole ;
• Pe baza contului de Google, Facebook, Twitter sau GitHub;
• Pe baza numărului de telefon: la în registrare utilizatorul va introduce un număr de telefon valid
și va primi un SMS c e conține un cod valabil pentru o durată de timp stabilită de dezvoltatorul
aplicației . Pentru înregistrare va trebui introdus codul primit ;
• Anonimă: utilizatorul poate crea un cont tempora r, urmând ca în cazul în care acesta dorește
crearea unui cont permanent, să poată continua cu informațiile de ja salvate pe contul tempora r;
• Integrarea sistemului de autentificare deja existent;
Acest serviciu poate fi utilizat pentru: Android , iOS, Web, Unity, C++ , Node.js și Java.
Introducerea serviciului de autentificare oferit de Fire base în aplicație permite, pe lâ ngă
înregistrarea utilizatorilor și păstrarea în siguranț ă a informațiilor acestora, suport pentru schimbarea
setărilor contului și recuperarea parolei.
După introdu cerea de către utilizator a informațiilor de autentificare, acestea sunt transmise către
SDK -ul pentru autentificarea Firebase. Ace st serviciu de back -end va verifica datele primite și va returna

31
un răspuns clientului. Dacă înregistrarea a avut loc cu succes, utilizatorului îi este atribuit un cod de
identificare unic ce poate fi utilizat ulterior pentru alte servicii oferite de Fireb ase.

3.4.2 Autentificarea cu E -mail și parol ă folosin d SDK -ul Firebase
Există două modalități de implementare a autentificării cu Firebase: folosind mecanismul
FirebaseUIAuth sau folosind SDK -ul Firebase.
Dintre cele două, folosirea Fire baseUIAuth necesită un timp și un efort mai mic din partea
programatorului pentru integrare , deoarece include, pe langă toate funcționalitățile utilizate în procesul
de înregistrare și autentificare, și interfețele cu utilizatorul. Cu toate că integrarea autentificăr ii cu SDK –
ul Firebase durează mai mult, prezintă avantajul flexibilității în ceea ce prive ște aspectul interfeței cu
utilizatorul. [7]
Pentru ca autentificarea cu E -mail și parol ă folosind SDK -ul Firebase să poată fi utilizat în aplicația
Android trebuie ca API -ul minim setat să fie cel puțin 16, adică Android 4.1 (Jellybean). [8]
După cum am spus în capitolul anterior, există mai multe modalități de a realiza autentificare astfel
că, în cadrul unei aplicații , trebuie specificat ce modalitate (sau modali tăți) utilizăm. Acest lucru se face
din consola Firebase -> Autentificare -> Metode de logare, unde vom vedea fereastra următoare:

Figura 3.1 Autentificare Firebase

În activitatea în care se realizează înregistrarea utilizatorului din cadrul proiectului din Android
Studio se va declara și inițializa un obiect de tipul FirebaseAuth, ceea ce ne va permite realizarea unor
acțiuni precum crearea conturilor și înregistrarea utilizatorilor .

32
Pentru a verifica dacă există deja un utilizator înregistrat se va c rea un obiect de tipul FirebaseUser
și se va inițializa cu mAuth.getCurentU ser() . În cazul în care această metodă nu returnează nimic, se va
continua cu înregistrarea .
Înregistrarea utilizatorului în baza de date se face apelând metoda
createUserWithEmailA ndPassword( e-mail, parolă) , unde e -mail și parolă au valorile date de utilizator
în interfață . Există posibilitatea ca această metodă să producă excepții , iar pentru a înțelege care sunt
acestea este necesară adăugarea unui onCompleteListener.
Posibilele erori sunt:
• FirebaseAuthUserCollisionException – Indică faptul că există deja un utilizator înregistrat cu
adresa de e -mail cu care se încearcă înregistrarea
• FirebaseAuthInvalidCredentialsException – Indică o problemă cu datele puse la dispoziție de
către utilizator, de exemplu șirul de caractere introdus în câm pul de e -mail nu are un format
corespunzător .
• FirebaseAuthWeakPas swordException – Indică o problemă cu parola dorită. În momentul de față
singura cerință impusă pentru a salva parole în Firebase este ca respectiva parolă să aibă cel puțin
șase caractere. Dacă se doresc cerințe mai stricte aceste a trebuie impuse de dezv oltator.
După înregistrare există posibilitatea de a efectua o verificare a adresei de e -mail prin trimiterea unui
mail de confirmare prin apelarea funcției sendEmailVerification() . Acesta va conține un link la apăsarea
căruia se va salva în baza de date conf irmarea utilizatorului.
Limba în care este redactat e-mailul de confirmare poate fi modificat ă din consola Firebase dup ă cum
se observă în fig ura următoare:

Figura 3.2 E -mail de confirmare

33

Figura 3.3 Mesaj de confirmar e
Se poate verifica dacă adresa de e -mail a fost confirmată utilizând metoda isEmailVerified() .
Aceasta returnează o valoare de tip boolean.
Autentificarea utilizatorului se face apelând metoda signInWithEmailAndPassword(email,
parolă) , la care adăugăm un OnCompleteListener în cazul în care există excepții.
Posibilele excepții sunt:
• FirebaseAuthInvalidUserException – Indică faptul că este o problemă cu adresa de e -mail, de
exemplu: aceasta nu există în baza de date a utilizatorilor.
• FirebaseAuthInvalidCred entialsException – Indică faptul că parola introdusă este greșită .
Se poate schimba adresa de e -mail prin apelarea metodei updateEmail(emailNou) pentru un
obiect de tipul FirebaseUser care a fost inițializat cu FirebaseAuth.getInstance().getCurrentUser() .
Trebuie ad ăugat un OnCompleteListener pentru cazul în care există erori. Există posibilitatea de a trimite
un e-mail de confirmare al acestei acțiuni . Schimbarea parolei difer ă doar prin metoda folosită. În acest
caz vom folosi metoda updatePassword(parolăNouă) .
Cele două acțiuni menționate anterior, respectiv schimbarea e -mailului și a parolei, sunt
considerate sensibile din punctul de vedere al securității, motiv pentru care este posibil să producă
excepția: FirebaseAuthRecentLoginRequiredException . Motivul pentru care este întâlnită această
excepție este trecerea unui timp îndelungat de la ultima autentificare și singura metodă de a o evita este
re-autentificarea utilizatorului.
FirebaseAuth permite, după în registrarea utilizatorului în baza de date, salvarea unor informații
suplimentare . Aceste informații sunt însă limitate la adăugarea unui nume și a un ei imagini de profil,
ceea ce le face insuficiente pentru baza de date pe care dori m să o utilizăm.

34

Figura 3.4 Listă Utilizatori

Modalitatea în care este prezentată lista utilizatorilor în consola Firebase se poate observa în
figura 3.4 . Printre informațiile disponibile nu se numără și parola acestora care este confidențială.

3.5 Firebase Storage

3.5.1 Prezentare generală
Firebase Storage este utilizat pentru salvarea obiectelor și nu a datelor, ceea ce îl face perfect
pentru c onținut generat de utilizator de dimensiune mare precum imaginile sau videoclipurile. Este
construit peste Google Cloud Storage, deci SDK -ul a moștenit funcționalități de acolo precum tratarea
cu ușurință a pierderii conexiunii în timpul încărcării sau descărcării fișierelor. Pentru a asigura
securitatea fișierelor este bine ca acest serviciu să fie utilizat împreuna cu aut entificarea Firebase , astfel
fiind posibilă manipularea accesului la fișiere. [ 9]
Caracteristici principale:
• Încărcarea și descărcarea fișierelor este realizată indiferent de calitatea conexiunii la internet –
acest lucru este posibil deoarece, în cazul în care una dintre acțiunile menționate anterior este
întreruptă, aceasta va reporni din același loc, ceea ce salvează timp și bandă;
• Securitate crescută în cazul integrării cu FirebaseAuth – se poate restricționa accesul în funcție
de denumire a fișierului , dimensiune, tipul conținutului sau me tadate;
• Scalabilitate mare – dimensiunea totală a fișierelor poate fi de ordinul exabyte (1018 octeți) . [8]

35
3.5.2 Utilizare

Toate fișierele și directoarele salvate pot fi vizualiza te din consola Firebase.

Figura 3.5 Vizualizare Firebase Storage
De asemenea, pentru fieca re fișier se pot vedea informații suplimentare precum numele, tipul,
dimensiunea, data la care a fost creat, locația unde este salvat și metadatele.

Figura 3.6 Vizualizare informații despr e un fișier Firebase Storage

Pentru a putea încărca, descărca sau șterge fișiere din Firebase Storage este nevoie să creem o
referință ce indică spre locația respectivului fișier. O referință reprezintă o instanță a clasei
StorageReference și se obține prin apelul metodei g etReference(). Putem avea oricâte referințe dori m în
aplicație , însă dimensiunea uneia n u poate depăși 1024 octeți . [9]
În mod implicit metoda getReference() indică către directorul principal, d ar există posibilitatea
de a accesa subdirectoare specifice sau chiar un anumit fișier. Pentru a realiza acest lucru trebuie să
apelăm metoda child(), căreia îi pasăm ca argument locația subdirectorului sau a fișierului.
De exemplu, accesarea imaginii Pr ofil1.jpg din directorul ImaginiProfil se face în felul următor:

36
StorageRe ference storageRef = storage.getReference().child(”ImaginiProfil/Profil1.jpg”);
Pentru a încărca un fișier in Firebase Storage trebuie apelată una dintre metodele: putBytes(),
putFil e() sau putStream(). Fișierele nu pot fi încărcate în directorul principal.
Fișierele locale , precum poze facute cu telefonul , pot fi încă rcate folosind metoda put File(), căreia
îi este pasat ca argument un obiect de tipul Uri. Deoarece această metodă poate returna excepții , este
nevoie să adăugăm metode ce verifică dacă încărcarea fișierului în baza de date a avut succes sau nu.
Dacă a avut succes se poate ex trage URL -ul unde este salvată i maginea în baza de date. [8]
În figura următoare se poa te observa codul ce duce la încă rcarea unui fișier în Firebase Storage:

Figura 3.7 Încărcarea fișierelor în baza de date [8]

Descărcarea unui fișier se face apelând una dintre metodele getB ytes(), getStream() sau, dacă se
doreste obținerea URL -ului de unde se poate descărca fișierul, getDownloadUrl().Firebase pune la
dispoziția dezvoltatorilor blblioteca FirebaseUI ce permite conectarea rapidă a elementelor din interfața
cu utilizatorul cu A PI-ul Firebase. [ 8]

3.5.3 Regulile de securitate

Regulile de securitate pentru Firebase Stor age oferă un mecanism de a controla circumstanțele în
care fișierele sunt stocate și accesate. Acestea sunt declarate folosind cuvinte cheie precum match, if și
and pentru a determina atât regulile, c ât și căror fișiere se adresează.[ 7]
În mod implicit regulile de securitate permit scrierea și citirea din toate fișierele atât timp cât un
utilizator este autentificat folosind FirebaseAuth. Un set de astfel de re guli arată în felul următor:

37

Figura 3. 8 Setul de reguli implicite pentru F irebase Storage [ 8]
Cum se definește un set de reguli:
• Trebuie specificat serviciul Firebase pentru care se aplică : service firebase.storage{}
• Trebuie specificat pentru ce fișie re se aplică regulile:
o Dacă regulile se aplică pentru toate fișierele vom adăuga: match/{allPaths=**}
o Dacă regulile se aplică doar pentru un anumit fișier specificăm calea către acesta astfel:
▪ match/numeDirector/numeFișier{} sau
▪ match / numeDirector /{imagineId} {allow read: if imagineId == "numeFișier";}
o Dacă regulile se aplică pentru toate fișierele care încep cu un anumit set de litere, de
exemplu „imagine”, vom fol osi: match /imagine {}
• Trebuie specificat tipul de acces: scriere, citire sau ambele , astfel: allow read, write
• Trebuie specificată una sau mai multe condiții:
o Dacă nu este specificată nicio condiție se acordă accesul;
o O condiție poate fi specificată astfel: if < condiție >
o Tipuri de condiții:
▪ utilizatorul este autentificat: if request.au th != null
▪ utilizatorul este o persoan ă anume:
if request.auth.uid == „ idUtilizatorSpecific ”;
▪ utilizatoru apar ține unui anumit grup: match
/numeDirector/{groupId}/{numeFișier} { allow read: if
resource.auth.token.groupId == groupId
▪ dimensiunea fișierului
▪ data la care a fost creat sau încărcat fișierul
▪ tipul de date din fișier
▪ numele fișierului [ 8]

38
3.6 Cloud Firestore
3.6.1 Prezentare generală
Cloud Firestore este o baz ă de date nerela țional ă, flexibilă și scalabilă, pentru apl icații mobile sau
web ce realizează sincronizarea datelor în timp real între o multitudine de clienți. Acesta permite și
integrarea cu alte produse Firebase sau Google Cloud. [8]

Figura 3.9 Structura datelor în Firestore

După cum se poate observa și în figura de mai sus, în F irestore d atele sunt salvate în documente ,
iar documentele sunt organizate î n colec ții. Organizarea trebuie făcută în așa fel încât colecțiile să conțină
un număr mare de documente, iar documentele să conțină un număr relativ mic de informații.
Documente le conțin subcolecții sau seturi de date de tipul cheie -valoare, unde cheia trebuie sa fie
unică în interiorul documentului, iar valoarea p oate fi de diferite tipuri, de la simple șiruri sau numere,
la structuri mai complicate precum obiectele. Dimensiunea maximă a unui document este de 1MB . [2]
Un prim pas pentru a include FireStore în aplicație îl reprezintă inițializarea unei instanțe de tipul
FirebaseFirestore .
Pentru a putea stabili cu ce date dorim să lucrăm vom avea nevoie de o referință care indică
locația acestora în baza de date.
Înainte de a începe să lucrăm cu datele trebuie să știm unde anume se găsesc acestea în baza de
date.
Colecțiile și documente le sunt adăugate automat atunci când acestea sunt referențiate pentru
prima dată, nefiind nevoie ca acestea să fie create în mod explicit , iar când acestea nu mai conțin nici un
fel de date sunt șterse automat.
Caracteristici principale:
• Flexibilitate
• Interogări complexe
• Revizuirea datelor în timp real
• Suport când dispozitivul este deconectat

39
3.6.2 Utilizare
Primul pas pentru a utiliza CloudFirestore în aplicație îl reprezintă inițializarea unei instanțe de
tipul FirebaseFirestore : FirebaseFirestore db = FirebaseFirestore.getInstance();
Pentru a putea stabili cu ce date dorim să lucrăm vom avea nevoie de o referință care indică
locația acestora în baza de date. Referințele sunt de doua tipuri:
• care referențiază o colecție, de exemplu:
CollectionRefe rence colectieRef = db.collection("Colectie");
• care referențiază un document, de exemplu:
DocumentReference documentRef = db.collection("Colectie1").document("Document1");
Colecțiile și documentele sunt adăugate automat atunci când acestea sunt referențiat e pentru
prima dată, nefiind nevoie ca acestea să fie create în mod explicit, iar când acestea nu mai conțin nici un
fel de date sunt șterse automat.
Principalele acțiuni ce pot fi re alizate asupra bazei de date sunt:
1. Adăugarea datelor se poate face doa r în documente, în unul din următoare le moduri:
1.1. Adăugarea datelor într -un document cu id specificat , se folosește comanda:
colec tieRef.document.().set(dateDeAdăugat). În cazul în care documentul există deja , datele din
acesta sunt suprascrise. Dacă nu se dorește acest lucru, se va folosi urm ătoarea comandă:
colec tieRef.document.().set(dateDeAdăugat, SetOptions.merge());
1.2. Adăugarea datelor într -un document al cărui id va fi generat în mod automat se realizează prin
apelul metodei add(dateDeAdăugat)
1.3. Crearea unui document gol cu un id generat în mod automat se realizează prin apelul metodei
doc()
2. Modificarea datelor se realizează folosind metoda update(”denumireCheie”,valoare)
3. Interogarea bazei de date poate fi re alizată în două modalități:
3.1. Prin apelul unei metode se pot obține:
3.1.1. Conținutul unui singur document: folosim metoda get() asupra referinței către documentul
dorit;
3.1.2. Conținutul documentelor ce îndeplinesc un anumit criteriu: folosim funcția where() în
combinație cu get() asupra unei referințe de tipul CollectionReference
3.1.3. Toate documentele dintr -o colecție: folosim metoda get() asupra unei referințe de tipul
CollectionReference;
3.2. Prin atașarea unui receptor de evenimente – permite interceptarea modifi cărilor din baza de date
în timp real;
4. Ștergerea datelor:
4.1. Ștergerea unui document se efectuează folosind metoda delete()
4.2. Ștergerea unui câmp din interiorul documentului se efectuează folosind metoda
FieldValue.delete() când este modificat un document; [2]

40
Toate valorile din documente sunt indexate, ceea ce face ca timpul în care este realizată orice
căutare sau sortare să fie direct proporțional cu numărul de rezultate returnate, și nu cu numărul de
documente pe care le vizează acțiunea respectivă.

41
Capitolul 4
Descrierea aplicației

4.1 Prezentare generală
Aplicația a fost realizată folosind mediul de dezvoltare Android Studio și a diverselor servicii
oferite de Firebase. Aceasta este destinată locatarilor unui bloc de locuințe și are următoarele
funcționalități:
• Înregistrarea și autentificarea utilizatorilor cu contul de E -mail și o parolă;
• Adăugarea unor date suplimentare profilului utilizatorului precum: nume, prenume, imagine de
profil și numărul apartamentului;
• Modificarea inf ormațiilor asociate contului;
• Purtarea unei conversații cu alti utilizatori ai aplicației;
• Postarea și vizualizarea anunțurilor;
• Vizualizarea ch eltuielilor administrative;

4.2 Gestionarea conturilor
În momentul deschiderii aplicației este verificat dacă există un utilizator autentificat pe
dispozitivul utilizat. Dacă această condiție este îndeplinită și dacă utilizatorul are adresa de e -mail
confirmată, atunci va avea acces la meniul principal al aplicației.
final FirebaseUser utilizatorCurent = FirebaseAuth. getInstance ().getCurrentUser();
if(utilizatorCurent!= null){
if(utilizatorCurent.isEmailVerified()) {
Intent intent = new Intent(MainActivity. this, Meniu. class );
startActivity(intent);
finish();
}
}
4.2.1 Înregistrarea utilizatorului :
Ecranul vizualizat de u tilizator în momentul în care dorește înregistrarea unui cont este prezentat
în figura 4.1. Acesta este format din logo -ul aplicației, un meniu format din două tab -uri (autentificare și
înregistrare) și un container în care este încărcat un fragment în funcție de tab -ul selectat.
Fragmentul aferent înregistrării conține următoarele câmpuri ce trebuie completate: nume,
prenume, E -mail, parolă și apartament. Pentru a pu tea fi vizualizat e și pe ecrane de d imensiune mai mică,
aceste câmpuri sunt grupate într -un Scrol lView.

42

Figura 4.1 – Ecran înregistrare

Datele introduse trebuie să îndeplinească următoarele criterii:
• Câmpurile pentru nume și prenume nu trebuie să fie goale;
• Adresa de E -mail trebuie să aibă un format corespunzător și să nu fie deja asociată unui cont;
• Parola trebuie să conțină cel puțin 6 caractere;

Dacă nu a fost îndeplinit unul dintre criterii va apărea un mesaj de eroare(precum cel din figura 4.2)
în dreptul câmpului corespunzător.

Figura 4.2 Mesaj de eroare

43
Contul unui utilizator este ad ăugat în baza de date(dacă au fost îndeplinite toate condițiile și s -a
apăsat butonul „Înregistrare”) folosind următoarea metodă , după cum se poate observa și în Anexa 1 :
mAuth .createUserWithEmail AndPassword(emailString,parolaString).addOnCompleteListener( new
OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if(task.isSuccessful()) {
}
});
Folosim metoda addOnCompleteListener, deoarece d orim să verificăm dacă utilizatorul a fost
introdus cu succes în baza de date sau dacă au existat erori. Metoda
createUserWithEmailAndPassword(mail, parolă) cre ează un cont în baza de date, cont identificat printr –
un ID unic. Pentru a obține acest Id folosim secven ța de cod:
FirebaseUser utilizator = mAuth.getCurrentUser();
final String idString = utilizator.getUid();
Vom crea o colecție în Fire Store denumită Utilizatori unde vom salva documente cu al căror Id
este reprezentat de Id -ul utilizatorului și care stochează informațiile suplimentare aferente fiecărui cont.
Structura aceste i colecții vizualizată în consola Firebase este prezentată în figura de mai jos:

Figura 4.3 Colecția „Utilizatori”
Dacă datele au fost adăugate cu succes , vom trimite un mail de confirmare către adresa furnizată
de utilizator și vom afișa o fereastră pop -up pentru a anunța această acțiune. Metoda utilizată pentru
trimiterea mail -ului este sendEmailVerification(), iar afișarea și stabilirea caracteristicil or ferestrei se
face procedural.

44

Figura 4.4 Mesaj trimitere mail de confirmare
În momentul apăsării butonului „OK” este încărcat fragmentul pentru autentificare.

4.2.2 Autentificarea utilizatorului

Figura 4.5 Ecran autentificare
Pentru a realiza a utentificar ea folosim metoda signInWithEmailAndPassword(email, parol ă).

45
Condițiile ce trebuie îndeplinite pentru a realiza aute ntificarea cu succes sunt: mail-ul să fie
asociat unui cont din baza de date, parola să corespundă contului și contul să fie confirmat . În cazul în
care una dintre primele două condiții nu este îndeplinită este afișat un mesaj de eroare asemănător cu cel
din figura 4.2, iar dacă cea de -a treia condiție nu este îndeplinită este afișată fereastra pop -up din imaginea
de mai jos.

Figura 4.6 Mesaj de eroare
4.2.3 Recuperarea parolei

Figura 4.7 Ecran recuperare parolă
Prin apăsarea textului „Recuperează parola!” din pagina de autentificare, utilizatorul este
redirecționat către pagina de recuperare a parolei. După introducerea e -mailului în câmpul corespunzător
și apăsarea butonului „trimite”, este apelată metoda sendPasswordResetEmail(email). Se va trimite un

46
mail către adresa furnizată(dacă aceasta aparține unui cont din baza de date) care conține un link către o
pagină web având următorul formular:

Figura 4.8 Formular resetare parolă
4.3 Meniul principal

Figura 4.9 Ecran meniu principal

47
După autentificarea cu succes, utilizatorul este redirecționat către meniul principal, de unde poate
opta pentru una dintre următoare acțiuni:
• Vizualizarea informațiilor referitoare cheltuielile administrative;
• Vizualizarea anunțurilor;
• Vizualizarea unei liste cu toate persoanele cu care poate purta o conversație prin intermediul
acestei aplicații;
• Vizualizarea profilului;
• Ieșirea din cont;

Pentru realizarea interfeței corespunzătoare acestui ecran au fost utilizate elemente de tipul
CardView personalizate. Apăsarea pe elementele cu textul „administrare”, „anunțuri”, „conversații” și
„profil” determină apelarea unor activități corespun zătoare, în timp ce apăsarea elementului cu textul
„ieșire” determină apelarea metodei signOut() ce efectuează ieșirea utilizatorului din cont.

4.4 Profil

Figura 4.10 Profilul utilizatorului
Inițial utilizatorul nu are o imagine de profil setată, însă poate adăuga una prin apăsarea imaginii
circulare din partea de sus a ecranului.

48
Când aceasta este apăsată, se verifică dacă dispozitivul utilizat are o versiune a sistemului de
operare mai nouă decât Marshmallow , deoarece, începând cu această versiune, e ste nevoie de
permisiunea utilizatorului pentru a accesa memoria externă aplicației. Verificarea se face astfel:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {}
Dacă condiția de mai sus es te îndeplinită, se verifică dacă permisiunea a fost deja acordată:
ContextCompat.checkSelfPermission(Setari.this,Manifest.permission.READ_EXTERNAL_STO
RAGE)!= PackageManager.PERMISSION_GRANTED){}
În cazul în care nu a fost acordată se cere permisiunea:
ActivityCompat.requestPermission s(Setari.this, new String[]{Manifest.permission.READ_
EXTERNAL_STORAGE},1);
După ce a fost obținută permisunea se utilizează activitatea CropImageActivity din biblioteca
'com.theartofdev.edmodo:android -image -cropper:2.7.+' pentru a redimensiona imaginea, conform unor
criterii impuse(dorim ca imaginea să fie pătrată). Redimensionarea imaginii se face astfel :
CropImage.activity().setGuidelines(CropImageView.Guidelines.ON).setAspectRatio(1,1)
.start(Setari.this);

La apăsarea butonului „salvează ” se întreprind următoarele acțiuni:
1. Dacă a fost setată o nouă imagine:
1.1 Se salvează imaginea în documentul ImaginiProfil din Firebase Storage cu denumirea de
forma: idUtilizator.jpg;
1.2 Se obține URL -ul imaginii salvate în Storage;
1.3 Se salvează în FireStore URL-ul obținut la pasul anterior , în documentul corespunzător
utilizatorului curent.
2. Dacă a fost schimbat unul dintre următoare le câmpuri: nume, prenume sau apartament: se vor
modifica datele stocate în Firestore, corespunzătoare utilizatorului curent
3. Dacă a fost introdus un nou e -mail: se apelează metoda updateEmail(emailNou) care salvează noul
e-mail sau, în cazul în care aceasta returnează o excepție, se afișează o eroare cu mesajul
corespunzător;
4. Dacă a fost introdusă o nouă parolă: se apeleaz ă metoda updatePassword(parolaNouă) care salvează
noua parolă sau, în cazul în care aceasta returnează o excepție, se afișează o eroare cu mesajul
corespunzător;
Codul din spatele acestor acțiuni se află în Anexa 2.
La accesarea acestei pagini sunt extrase informaț iile disponibile despre utilizatorul curent și afișate
în locațiile corespunzătoare. Extragerea informațiilor din FireSotre se face prin următoarea secvență de
cod:
firebaseFirestore.collection("Utilizatori").document(idString).get();
Pentru vizualizarea imaginii de profil a fost folosită o componentă de tipul CircleImageView
disponibilă prin adăugarea librăriei 'de.hdodenhof:circleimageview:2.2.0'.
Încărcarea imag inii pe baza URL -ului se face prin utilizarea framework -ului pentru Android, Glide,
astfel :

49
Glide.with(Setari.this).setDefaultRequestOptions(placeholderRequest).load(imagineExtrasString).i
nto(setariImagineUtilizator);

4.5 Conversații
Utilizatorul poate comunica prin intermediul mesajelor cu restul utilizatorilor aplicației. Aceștia
pot fi selectați dintr -o listă dacă se dorește începerea sau continuarea unei conversații deja existente . Lista
este actualizată în ti mp real, astfel că dacă un nou utilizator își cre ează un cont, acesta va apărea automat
în lista celor lalți utilizatori. Acest lucru se rea lizează prin următoarea secvență de cod:
firebaseReference1 = FirebaseFirestore.getInstance().collection("Utilizatori");
firebaseReference1.addSnapshotListener(new EventListener<QuerySnapshot>() {
public void onEvent (QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
for (DocumentChange doc : documentSnapshots.getDocumentChanges()) {
if (doc.getType() == DocumentChange.Type.ADDED) {
if(!doc.getDocument().getId().equals(utilizatorCurentId)) {
//Actualizăm lista
}}}});
Pentru a afișa lista utilizatorilor aplicației vom folosi un adaptor personalizat. Acesta face
legătura între lista cu obiecte de tip Utilizator unde am încărcat informațiile din baza de date și lista din
interfața grafică.

Figura 4.11 Ecran listă locatari

50
Când un utilizator este selectat din listă, s e deschide activitatea Conversaț ie, căreia îi transmitem
id-ul utilizatorului selectat astfel:
Intent intent = new Intent(Conversatii.this, Conversatie.class);
intent.putExtra("idCelalaltUtilizator", idCelalaltUtilizator);
startActivity(intent);
În noua activitate colect ăm id -ul utilizatorului cu care se dorește comunica și, pe baza acestuia,
vom realiza o intero gare a bazei de dat e în urma cărei obținem numele ș i prenumele respectivului
utilizator și le afișăm în partea de sus a ecranului.
Structura colecție i „Conversații” din baza de date este următoarea: aceasta conține documente ale
căror id -uri corespund id -urilor utilizatorilor care au conversații începute. La rândul lor, fiecare astfel de
document conține sub -colecții al căror id corespunde id -ului cu care a fost începută conversația. În aceste
sub-colecții sunt salvate mesajele sub forma unor documente cu i d aleator.
Cu alte cuvinte, un mesaj transmis între utilizatorul „A” și utilizatorul „B” este stocat în două
locații diferite:
• „Conversații/IdUtilizatorA/IdUtilizatorB/Idmesaj
• „Conversații/IdUtilizatorB/IdUtilizatorA/IdMesaj
Pentru a afișa mesajele propri i în partea din stânga și mesajele primite în partea din dr eapta verificăm
înainte de afișarea mesajului dacă id -ul utilizatorului care a trimis mesajul corespunde cu id -ul
utilizatorului cu rent și, în funcție de asta alegem ce fișier xml utilizăm.
idUtili zatorMesaj = mesaj.getIdUtilizator();
if(idUtilizatorCurent.equals(idUtilizatorMesaj)){
convertView = ((Activity) getContext()).getLayoutInflater().inflate(R.layout.vizualizare_un_mesaj,
parent, false);
}else{
convertView = ((Activity) getContext()).getLay outInflater().inflate(R.layout.vizualizare_un_mesaj2,
parent, false)
} Întregul cod sursă pentru afișarea mesajelor este disponibil în Anexa 3.
În figura de mai jos este prezentată o conversație de pe dispozitivele ambilor utilizatori
participanți.

51

Figura 4.12 Conversație

4.6 Anunțuri

Aplicația oferă posibilitatea de a vizualiza și posta anunțuri. Un anunț poate fi compus dintr -o
imagine, un titlu și o descriere. Adă ugarea unei imagini nu este obli gatorie.
Pentru postarea unui nou anunț se apasă butonul de tipul FloatingActionButton. În noua activitate
este posibilă încărcarea unei imagini folosind metoda descrisă în sub -capitolul 4.4( Profil). În momentul
apăsării butonului „p ostează”, dacă există text în câ mpul pentru titlu și cel pentru descrier e, se va salva
anunțul în baza de date, în colecția „Anunturi”, cu un id generat aleator.
Structura colecției „Anunțuri” din baza de date este prezentată în următoarea figură.

52

Figura 4.13 Structura colecției „Anunțuri”

Pentru afișarea anunțurilor folosim un adaptor personalizat ce realizează cone xiunea între lista de
obiecte de tipul Mesaj , pe care am populat -o în urma interogării bazei de date, și lista din interfața grafică
ce conține elemente de tipul vizualizare_un_a nunt.xml (interfață realizat ă pentru prezentarea unui singur
anunț).
În figura 4.14, în partea stângă este prezentată modalitatea de vizualizare a anunțurilor dej a
postate, iar în partea dreaptă este prezentată pagina unde se realizează un nou anunț în scopul de a fi
postat.

Figura 4.14 Ecran anunțuri

53
4.7 Administrație
Utilizatorii pot selecta utilitățile pentru care doresc să vadă consumul și suma de plată pentru
fiecare lună, din bara de meniu a activității Administrație. Variantele disponibile se pot observa în figura
de mai jos:

Figura 4.15 Bara de meniu pentru administrație
La selecția uneia dintre primele trei opțiuni este încărcat un fragment corespunzător opțiunii
selectate. Dacă este selectată opțiunea „Total” se efectuează verificarea dacă utilizatorul este
administrator. Pentru această verificare se extrage, pentru utilizatorul curent, valoarea câmpului
administrator(de tipul boolean) din baza de date. Dacă aceasta are valo are de adevăr, este încărcat
fragmentul corespunzător, iar dacă aceasta are valoarea fals, este afișată fereastra din figura 4.16 și nu
este încărcat un nou fragment.

Figura 4.16 Eroare accesare date nepermise

54

Figura 4.1 7 Vizualizare consum apă
În mod implicit, la deschiderea activității „Administrație”, este încărcat fragmentul corespunzător
consumului apei. După cum se poate observa în figura 4.1 7, în partea de sus a interfeței se pot selecta
anul și luna.
Inițial, anul și luna sunt setate să co respundă celor actuale. Acest lucru este realizat prin
extragerea valorilor curente prin următoarea secvență de cod:
DateFormat dateFormatAn = new SimpleDateFormat( "YYYY" );
DateFormat dateFormat = new SimpleDateFormat( "MM" );
Date date = new Date();
anCuren t = dateFormatAn.format(date);
lunaCurenta = dateFormat.format(date);
Nu este posibilă selecția unei date viitoare. În cazul în care se încearcă acest lucru, va fi afișat un
mesaj de eroare și se va afișa data curentă.
Valorile pentru restul câmpurilor din interfață sunt extrase din baza de date și afișate și nu pot fi
modificate din interfață. Excepție face cazul în care utilizatorul afișează consumul din luna curent ă,
moment în care câmpul pentru afișarea index -ului nou devine editabil.
Atunci când es te salvată o valoare pentru index -ul nou au loc următoarele acțiuni :

55
• Este verificat ca index -ul nou să nu fie mai mic decât cel vechi;
• Dacă condiția de mai sus este îndeplinită, se salvează valoarea index -ului nou în baza de date atât
pentru luna curentă, cât și pentru luna viitoare(în câmpul destinat index -ului vechi);
• Se calculează consumul și se introduce în baza de date;
• Este extrasă valoarea pentru apa.costUnitar din luna selectată(valoare stocată în baza de date la
adresa „Administrație/an_selectat/luna_selectată/Total” și pe baza acesteia este calculată suma
de plată. Aceasta este la rândul ei salvată în baza de date;
Aceste acțiuni sunt exemplificate prin codul sursă în Anexa 4.
Fragmentele pentru curent și gaze sunt imp lementate într -o manieră asemănătoare cu cea prezentată
mai sus.

Figura 4.1 8 Vizualizare total

Doar administratorul poate vizualiza și modifica câmpurile din fragmentul pentru total. Acestea
sunt cele din figura 4.1 8.
La apăsarea butonului de salvare s e vor întreprinde următoarele acțiuni:
• Pentru fiecare grup în parte(apă, gaze sau curent) se verifică dacă au fost introduse, atât valoarea
cantității consumate, cât și cea a sumei de plată ;
• Se calculează prețul unitar;
• Se introduc valorile în baza de date ;

56

57

Concluzii

Consider că aplicația realizată și -a atins scopul propus. Aceasta poate fi utilizată pe o largă
varietate de dispozitive mobile și include funcționalitățile importante privind gestionarea aspectelor
administrative pentru o asociație de locatari.
Bineînțeles, mai pot fi aduse îmbunătățiri care să completeze funcționalitățile existente. Printre
acestea se numără :
• Adăugarea conversațiilor de grup
• Posibilitatea de a vizualiza și salva imaginile primite prin mesaje
• Anunțarea mesajelor și a anunțurilor noi prin notificări
• Posibilitatea de a comenta la anunțuri
• Posibilitatea de a șterge propriile anunțuri
• Dezvoltarea aplicației pentru a funcționa la o scară mai largă(pentru mai multe blocuri)
• Posib ilitatea ca administratorul să vadă consumul declarat și suma de plată al tuturor utilizatorilor
• Efectuarea plăților prin aplicație

Pe durata dezvoltării acestei aplicații, am realizat necesitatea unor servicii precum cele puse la
dispoziție de platforma Firebase care, prin ceea ce oferă, reduc considerabil efortul și durata realizării
aplicațiilor.
Ca să conchid, realizarea acestei teme a contribuit semnificativ la dezvoltarea și aprofundarea
cunoștințelor personale privind dezvoltarea software.

58

59

Referințe

[1] Marko Gargenta; Masumi Nakamura, Learning Android, 2nd Edition, Editura O'Reilly Media, Inc ,
2014
[2] Site HOA – https://www.openhandsetalliance.com
[3] Site oficial pentru dezvoltatorii Android – https://developer.android.com/
[4] Programul de compatibilitate Android – https://source.android.com/compatibility/
[5] Prezentare Android 8.1 – https://developer.android.com/preview/ feature , accesat la data : 20.07.2018
[6] Laborator Android – https://ocw.cs.pub.ro/courses/eim/laboratoare/laborator03
[7] Neil Smyth, Firebase Essentials – Android Edition , Editura Payload Media, Inc., 2017
[8] Laurence Moroney, The Definitive Guide to Firebase , Editura Apress, 2017
[9] Documentația oferită de Firebase pentru dispozitivele Android ,
https://firebase.google.com/docs/android/setup , accesat la data: 01.06 .2018

60

Anexa 1

Înregistrare

public class Inregistrare extends Fragment {

//Declaram variablele pe care le vom utiliza in acest fragment
private EditText nume;
private EditText prenume;
private EditText email;
private EditText parola;
private EditText apartament ;
private Button buton;
private TextInputLayout parolaTextInputLayout ;
private ProgressBar inregistrareProgres ;
private Dialog dialog;

private FirebaseAuth mAuth;
private FirebaseFirestore firebaseFirestore ;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R.layout. fragment_inreg istrare, container, false);

//Identificam campurile din layout -ul pentru autentificare si le facem legatura cu cele
declarate anterior
nume = rootView.findViewById(R.id. inregistrareNumeEditText );
prenume = rootView.findViewById(R.id. inregistrarePrenumeEditText );
email = rootView.findViewById(R.id. inregistrareEmailEditText );
parola = rootView.findViewById(R.id. inregistrareParolaEditText );
apartament = rootView.findViewById(R.id. inregistrareApartamentEditText );
buton = rootView.findViewById(R.id. inregistrareButon );
parolaTextInputLayout = rootView.findViewById(R.id. setariParolaTextInputLayout );
inregistrareProgres = rootView.findViewById(R.id. inregistrareProgres );
dialog = new Dialog(getActivity());

//Instante utilizate pentru a salva datele
mAuth = FirebaseAuth. getInstance ();
firebaseFirestore = FirebaseFirestore. getInstance ();

//Cod ce realizeaza reaparitia butonului de vizibilitate a parolei dupa de modificarea textului
parola.addTextChangedListener( new TextWatcher() {

public void afterTextChanged(Editable s) {

parolaTextInputLayout .setPasswordVisibilityToggleEnabled( true);

}

public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});

//Actiunile executate la apasarea butonulu i
buton.setOnClickListener( new View.OnClickListener()
{
@Override
public void onClick(View v)
{
//Textul introdus
final String numeString = nume.getText().toString();
final String prenumeString = prenume.getText().toString();

61
final String emailString = email.getText().toString();
final String parolaString = parola.getText().toString();
final String apartamentString = apartament .getText().toString();

//Facem verificarile
verificareValiditateApartament(apartamentString);
verificareValiditateParola(parolaString);
verificareValiditat eEmail(emailString);
verificareValiditatePrenume(prenumeString);
verificareValiditateNume(numeString);

// Verificare completa
if(verificareValiditateNume(numeString) && verificareValiditateP renume(prenumeString)
&& verificareValiditateEmail(emailString) &&
verificareValiditateParola(parolaString)
&& verificareValiditateApartament(apartamentString))
{

//Afisam un popup pe durata incarcarii datelor
ProgressDialog progressDialog = new ProgressDialog(getActivity());
progressDialog.setMessage( "Încărcare…" );
progressDialog.show();
progressDialog.setCancelable( false);

//Afisam bara de progres
//inregistrareProgres.setVisibility(rootView.VISIBLE);

//Adaugam utilizatorul in baza de date

mAuth.createUserWithEm ailAndPassword(emailString,parolaString).addOnCompleteListener( new
OnCompleteListener<AuthResult>() {
@Override
public void onComplete( @NonNull Task<AuthResult> task) {

//Verifica m daca introducerea utilizatorului in baza de date a avut succes
if(task.isSuccessful()) {

//Daca inregistrarea userului a avut succes vom trimite un mail de
confirmare si vom
// continua cu adaugarea datelor suplimentare in baza de date

FirebaseUser utilizator = mAuth.getCurrentUser();
final String idString = utilizator.getUid();

//Trimitem mail de confirmare
utilizator.sendEmailVerification().addOnCompleteListener( new
OnCompleteListener<Void>() {
@Override
public void onComplete( @NonNull Task<Void> task) {

if (task.isSuccessful()) {
//Grupam toate datele pe care dorim sa le salvam
Map<String, String> utilizatorMap = new HashMap<>();
utilizatorMap.put( "nume", numeString );
utilizatorMap.put( "prenume" , prenumeString );
utilizatorMap.put( "apartament" , apartamentString );

//Salvam datele in baza de date

firebaseFirestore .collection( "Utilizatori" ).document( idString ).set(util izatorMap).addOnCompleteListener
(new OnCompleteListener<Void>() {
@Override
public void onComplete( @NonNull Task<Void> task) {
if (task.isSuccessful()) {

//Codul pentru modul in care arata pop -up-ul ce
apare dupa ce a fost trimis mailul de confirmare
dialog.setContentView(R.layout. popup);
ImageView popupImagine = dialog.findViewById(R.id. popupImagine );
ImageView popupFundal = dialog.findViewById(R.id. popupFundal );
TextView popupText = dialog.findViewById(R.id. popupText );
Button popupButon = dialog.findViewById(R.id. popupButo ;
popupImagine.setImageResource(R.drawable. popupmail );

62
popupFundal.setImageResource(R.color. popupsucces );
popupText.setText( "A fost trimis un mail de
confirmare la adresa: " + emailString + "\n Vă rugam verificați!" );

dialog.setCancelable( false);

popupButon.setOnClickListener( new
View.OnClickListener() {
@Override
public void onClick(View v) {
//La apasarea butonului ok inchidem
popup-ul si redirectionam utilizatorul catre pagina de logare
dialog.dismiss();

Intent intent = new
Intent(getActivity(), MainActivity. class);
startActivity(intent);
}
});
dialog.show();
} else {
String eroare =
task.getException().getMessage();
Toast.makeText (getActivity(), eroare,
Toast.LENGTH_SHORT ).show();
}
//Facem bara de progres invizibila
inregistrareProgres .setVisibility( rootView .INVISIBLE );
}
});
}
}

});
}else{
String eroare = task.getException().getMessage();
switch (eroare) {
case "The email address is already in use by another account." :
seteazaEroareEmail( "E-mail deja folosit" );
break;
case "The email address is badly formatted." :
seteazaEroareEmail( "Format necorespunzător" );
break;
case "The given password is invalid. [ Password should be at least
6 characters ]" :
seteazaEroareParola( "Parolă prea scurtă (min 6 caractere)" );
break;
default:
Toast.makeText (getActivity(), eroare,
Toast.LENGTH_SHORT ).show();
break;
}
}
}
});
progressDialog.cancel();
}
//Ascundem bara de progres
//inregistrareProgres.setVisibility(View.INVISIBLE);
}
});
return rootView;
}
// Verificam daca campul pentru nume este gol
private boolean verificareValiditateNume(String numeString) {
if(!numeString.isEmpty()){
return true ;
}else {
//Setam un mesaj de eroare pentru campul nume
seteazaEroareNume( "Câmp necompletat!" );
return false ;
}
}
// Verificam daca campul pentru prenume este gol

63
private boolean verificareValiditatePrenume(String prenumeString) {
if(!prenumeString.isEmpty()){
return true ;
}else {
//Setam un mesaj de eroare pentru campul prenume
seteazaEroarePrenume( "Câmp necompletat!" );
return false ;
}
}
// Verificam daca campul pentru e -mail este gol
private boolean verificareValiditateEmail(String emailString) {
if (!emailString.isEmpty()) {
return true ;
}else {
//Setam un mesaj de eroare pentru campul e -mail
seteazaEroareEmail( "Câmp necompletat!" );
return false ;
}
}
// Verificam daca campul pentru parola ne este gol
private boolean verificareValiditateParola(String parolaString) {
if(!parolaString.isEmpty()){
return true ;
}else {
//Setam un mesaj de eroare pentru campul parola
seteazaEroareParola( "Câmp necompletat!" );
return false ;
}
}
// Verificam daca campul pentru apartament ne este gol
private boolean verificareValiditateApartament(S tring apartamentString) {
if(!apartamentString.isEmpty()){

int apartamentInt = Integer. parseInt (apartamentString);
int maxApartament = 30;
if(apartamentInt>maxApartament){
seteazaEroareApartament( "Apartamentul nu există" );
return false ;
}else {
return true ;
}
}else {
//Setam un mesaj de eroare pentru campul apartament
seteazaEroareApartament( "Câmp necompletat!" );
return false ;
}
}
//Functie ce seteaza eroarea pentru campul nume
public void seteazaEroareNume(String eroare){
nume.setError(eroare);
nume.requestFocus();
}
//Functie ce seteaza eroarea pentru campul prenume
public void seteazaEroarePrenume(String eroare){
prenume.setError(eroare);
prenume.requestFocus();
}
//Functie ce seteaza eroarea pentru campul e -mail
public void seteazaEro areEmail(String eroare){
email.setError(eroare);
email.requestFocus();
}
//Functie ce seteaza eroarea pentru campul parola
public void seteazaEroareParola(String eroare){
parolaTextInputLayout .setPasswordVisibilityToggle Enabled( false);
parola.setError(eroare);
parola.requestFocus();
}
//Functie ce seteaza eroarea pentru campul apartament
public void seteazaEroareApartament(String eroare){
apartament .setError(eroare);
apartament .requestFocus();
}

64
Anexa 2

public class Setari extends AppCompatActivity {

private CircleImageView setariImagineUtilizator ;
private Uri imagineUri = null;
private EditText nume;
private EditText prenume;
private EditText apartament ;
private EditText parolaNoua ;
private EditText emailNou ;
private TextInputLayout parolaNouaLayout ;
private Button salveazaButon ;
private ProgressBar baraProgres ;
private String idString ;
private FirebaseUser utilizato r;
private boolean schimbare = false;
private Uri download_uri ;
// private String downloadUriString;
private int esecuri;
private Dialog dialog;

//Pe langa variabila care referentiaza stocarea mai avem nevoie si de cea pentru autentificare
// deoarece avem nevoie sa stim pentru ce utilizator salvam imaginea
//si sa modificam emilul si parola
private StorageReference storageReference ;
private FirebaseAuth firebaseAuth ;
private FirebaseFirestore firebaseFirest ore;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_setari );

//Initializam campurile utilizate
setariImagineUtilizator = findView ById(R.id. veziutilizatorImagine );
nume = findViewById(R.id. setariNumeEditText );
prenume = findViewById(R.id. setariPrenumeEditText );
apartament = findViewById(R.id. setariApartamentEditText );
parolaNoua = findViewById(R.id. setariParolaEditText );
emailNou = findViewById(R.id. setariEmailEditText );
salveazaButon = findViewById(R.id. setariButon );
baraProgres = findViewById(R.id. setariProgres );
dialog = new Dialog(Setari. this);

//Instante utilizate pentru a salva datele
firebaseAuth = FirebaseAuth. getInstance ();
storageReference = FirebaseStorage. getInstance ().getReference();
firebaseFirestore = FirebaseFirestore. getInstance ();
utilizator = firebaseAuth .getCurrentUser();

baraProgres .setVisibility(View. VISIBLE);
salveazaButon .setEnabled( false);

//Instanta utilizate pentru a afisa datele din baza de date
idString = utilizator .getUid();

firebaseFirestore .collection( "Utilizatori" ).document( idString ).get().addOnCompleteListener( new
OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete( @NonNull Task<DocumentSnapshot> task) {
if(task.isSuccessful()){
//Verificam daca datele exista deja
if(task.getResult().exists()){
//Daca exista le extragem din baza de date
String numeExtrasString = task.getResult().getString( "nume");
String prenumeExtrasString = task.getResult().getString( "prenume" );
String apartamentExtrasString = task.getResult().getString( "apartament" );
String imagineExtrasString = task.getResult().get String("imagine" );

65

nume.setText(numeExtrasString);
prenume.setText(prenumeExtrasString);
apartament .setText(apartamentExtrasString);

//Daca exista o imagine s alvata in baza de date o incarcam
if(imagineExtrasString != null &&
!imagineExtrasString.isEmpty()&&!imagineExtrasString.equals( " ")) {
imagineUri = Uri.parse(imagineExtrasString);
RequestOptions placeholderRequest = new RequestOptions();
placeholderRequest.placeholder(R.drawable. inlocuitorprofilmare );

Glide.with(Setari. this).setDefaultRequestOptions( placeholderRequest).load(imagineExtrasString).into( set
ariImagineUtilizator );
}
}
}else{
String eroare = task.getException().getMessage();
Toast.makeText (Setari.this, eroare, Toast. LENGTH_SHORT ).show();
}
baraProgres .setVisibility( INVISIBLE );
salveazaButon .setEnabled( true);
}
});

//La apasarea butonului de salveaza:
salveazaB uton.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {

esecuri = 0;

final String numeString = nume.getText().toString();
final String prenumeString = prenume.getText().toString();
final String apartamentString = apartament .getText().toString();
final String parolaNouaString = parolaNoua .getText().toString();
final String emailNouStri ng = emailNou .getText().toString();

baraProgres .setVisibility(View. VISIBLE);

if (!parolaNouaString.isEmpty()) {
//Schimbam parola
utilizator .updatePassword(parolaNouaString).addOnC ompleteListener( new
OnCompleteListener<Void>() {
@Override
public void onComplete( @NonNull Task<Void> task) {
if (!task.isSuccessful()) {

esecuri = esecuri + 1;

String eroare = task.getException().toString();
switch (eroare) {
case "This operation is sensitive and requires recent
authentication. Log in again before retrying this request.." :
seteazaEroareParola( "Necesită o autentificare recentă" );
break;
case "The given password is invali d. [ Password should be at least
6 characters ]" :
seteazaEroareParola( "Parolă prea scurtă (min 6 caractere)" );
break;
default:
Toast.makeText (Setari. this, eroare, Toast. LENGTH_SHORT ).show();
break;
}
}
}
});
}
//Schimbarea emailului
if (!emailNouString.isEmpty()) {
utilizator .updateEmail(emailNouString).addOnCompleteListener( new

66
OnCompleteListener<Void>() {
@Override
public void onComplete( @NonNull Task<Void> task) {
if (!task.isSuccessful()) {

esecuri = esecuri +1;

String eroare = task.getException().get Message();
switch (eroare) {
case "The email address is already in use by another account." :
seteazaEroareEmail( "E-mail deja folosit" );
break;
case "The email address is badly formatted." :
seteazaEroareEmail( "Format necorespunzător" );
break;
case "This operation is sensitive and requires recent
authentication. Log in again before retrying this request.." :
seteazaEroareEmail( "Necesită o autentificare recentă" );
break;
default:
Toast.makeText (Setari. this, eroare, Toast. LENGTH_SHORT ).show();
break;
}
}
}
});
}

download_uri = imagineUri ;

//Salvam imaginea de profil daca ea fost schimbata
if (schimbare == true) {

idString = firebaseAuth .getCurrentUser().getUid();

final StorageReference imaginePath =
storageReference .child("imaginiProfil" ).child( idString + ",jpg");
imaginePath.putFile (imagineUri ).continueWithTask( new
Continuation<UploadTask.TaskSnapshot, Task<Uri>>() {
@Override
public Task<Uri> then( @NonNull Task<UploadTask.TaskSnapshot> task) throws
Exception {
if (!task.isSuccessful()) {
throw task.getException();
}

// Continue with the task to get the download URL
return imaginePath .getDownloadUrl();
}
}).addOnCompleteListener( new OnCompleteListener<Uri>() {
@Override
public void onComplete( @NonNull Task<Uri> task) {
if (task.isSuccessful()) {
download_uri = task.getResult();

firebaseFirestore .collection( "Utilizatori" ).document( idString ).update( "imagine" ,download_uri .toString()
);
Log.d("aici",download_uri .toString());
} else {
// Handle failures
// …
}
}
});
}

if(verificareValiditateNume(numeString)&&verificareValiditatePrenume(prenumeString)&&verificareValidita
teApartament(apartamentString)) {
//Daca nu s -a schimbat imaginea de profil actualizam doar restu l datelor
//Grupam toate datele pe care dorim sa le salvam
Map<String,String> utilizatorMap = new HashMap<>();

67
utilizatorMap.put( "nume",numeString);
utilizatorMap.put( "prenume" ,prenumeString);
utilizatorMap.put( "apartament" ,apartamentString);

//Salvam datele in baza de date
firebaseFirestore .collection( "Utilizatori" ).document( idString )
.update("nume",numeString,
"prenume" ,prenumeString,
"apartament" ,apartamentString).addOnCompleteListener( new
OnCompleteListener<Void>() {
@Override
public void onComplete( @NonNull Task<Void> task) {
if(task.isSuccessful()){
}else{

String eroare = task.getException().getMessage();
Toast.makeText (Setari. this, eroare, Toast. LENGTH_SHORT ).show();
}
}
});
}
baraProgres .setVisibility( INVISIBLE );
if(esecuri==0){
//Codul pentru modul in care arata pop -up-ul ce apare dupa ce a fost trimis mailul
de confirmare
dialog.setContentView(R.layout. popup);
ImageView popupImagine = dialog.findViewById(R.id. popupImagi ne);
ImageView popupFundal = dialog.findViewById(R.id. popupFundal );
TextView popupText = dialog.findViewById(R.id. popupText );
Button popupButon = dialog.findViewById(R.id. popupButon );
popupImagine.setImageResource(R.drawable. popupsucces );
popupFundal.setImageResource(R.color. popupsucces );
popupText.setText( "Datele au fost schimbare cu succes!" );

dialog.setCancelable( false);

popupButon.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
//La apasarea butonului ok inchidem popup -ul si redirectionam utilizatorul
catre pagina de logare
dialog.dismiss();

Intent intent = new Intent(Setari. this, Setari. class);
startActivity(intent);
finish();
}
});
dialog.show();

}
}
});
//La apasarea imaginii de profil sunt intreprinse urmatoarele actiuni
setariImagineUtilizat or.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
//Daca utilizatorul foloseste un telefon cu API mai mare sau egal cu 23(Marshmello)
avem nevoie de permisiunea acestuia pentru a accesa galeria
if (Build.VERSION. SDK_INT >= Build.VERSION_CODES. M) {
//Verificam daca permisiunea a fost deja acordata
// Daca nu a fost acordata cerem permisiunea
if(ContextCompat. checkSelfPermission (Setari. this,
Manifest.permission. READ_EXTERNAL_STORAGE )!= PackageManager. PERMISSION_GRANTED ){
ActivityCompat. requestPermissions (Setari. this, new
String[]{Manifest.permission. READ_EXTERNAL_STORAGE },1);
}else if(ContextCompat. checkSelfPermission (Setari. this,
Manifest.permission. WRITE_EXTERNAL_STORAGE )!= PackageManager. PERMISSION_GRANTED ){
ActivityCompat. requestPermissions (Setari. this, new
String[]{Manifest.permis sion.WRITE_EXTERNAL_STORAGE },1);
}else{
AlegeImaginea();
}
}else{

68
AlegeImaginea();
}
}
});
}

//Metoda ce permite salvarea restului de date
private void salveazaFirestoredata(String numeString, String prenumeString, String
apartamentString) {

//Grupam toate datele pe care dorim sa le salvam
Map<String,String> utilizatorMap = new HashMap<>();
utilizatorMap.put( "nume",numeString);
utilizatorMap.put( "prenume" ,prenumeString);
utilizatorMap.put( "apartament" ,apartamentString);
//Salvam datele in baza de date

firebaseFirestore .collection( "Utilizatori" ).document( idString).set(utilizatorMap).addOnCompleteListener
(new OnCompleteListener<Void>() {
@Override
public void onComplete( @NonNull Task<Void> task) {
if(task.isSuccessful()){
Toast.makeText (Setari. this, "Am salvat datele!" , Toast. LENGTH_SHORT ).show();

}else{
String eroare = task.getException().getMessage();
Toast.makeText (Setari. this, eroare, Toast. LENGTH_SHORT ).show();
}
}
});
}
//Metoda ce ne permite accesarea imaginii modificate
@Override
protected void onActivityResult( int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CropImage. CROP_IMAGE_ACTIVITY_REQUEST_CODE ) {
CropImage.ActivityResult result = CropImage. getActivityResult (data);
if (resultCode == RESULT_OK ) {
imagineUri = result.getUri();
setariImagineUtilizator .setImageURI( imagineUri );

//Cand imaginea este schimbata modificam valoarea variabilei schimbata astfel incat sa
nu fie nevoie sa o salvam iar
schimbare =true;
} else if (resultCode == Crop Image.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE ) {
Exception error = result.getError();
}
}
}
//Metoda ce ne permite sa trimitem utilizatorul la activitatea CropImage unde va putea sa isi
redimensioneze imaginea dupa cum doreste
private void AlegeImaginea() {

CropImage. activity ().setGuidelines(CropImageView.Guidelines. ON)
.setAspectRatio( 1, 1)
.start(Setari. this);
}
// Verificam daca campul pentru nume este gol
private boolean verificareValiditateNume(String numeString) {
if(!numeString.isEmpty()){
return true ;
}else {
//Setam un mesaj de eroare pentru campul nume
seteazaEroareNume( "Câmp necompletat!" );
esecuri = esecuri+1;
return false ;
}
}
// Verificam daca campul pentru prenume este gol
private boolean verificareValiditatePrenume(String prenumeString) {
if(!prenumeString.isEmpty()){
return tr ue;
}else {
//Setam un mesaj de eroare pentru campul prenume

69
seteazaEroarePrenume( "Câmp necompletat!" );
esecuri = esecuri+1;
return false ;
}
}
// Verificam daca campul pentru apartamen t ne este gol
private boolean verificareValiditateApartament(String apartamentString) {
if(!apartamentString.isEmpty()){
int apartamentInt = Integer. parseInt (apartamentString);
int maxApartament = 30;
if(apartamentInt>maxApartament){
seteazaEroareApartament( "Apartamentul nu există" );
esecuri = esecuri+1;
return false ;
}else {
return true ;
}
}else {
//Setam un mesaj de eroare pentru campul apartament
seteazaEroareApartament( "Câmp necompletat!" );
esecuri = esecuri+1;
return false ;
}
}

//Functie ce seteaza eroarea pentru campul parola
public void seteazaEroareParola(String eroare){
parolaNouaLayout .setPasswordVisibilityToggleEnabled( false);
parolaNoua .setError(eroare);
parolaNoua .requestFocus();
}
//Functie ce seteaza eroarea pentru campul Email
public void seteazaEroareEmail(String eroare){
emailNou .setError(eroare);
emailNou .requestFocus();
}

//Functie ce seteaza eroarea pentru campul nume
public void seteazaEroareNume(String eroare){
nume.setError(eroare);
nume.requestFocus();
}

//Functie ce seteaza eroarea pentru campul prenume
public void seteazaEroarePrenume(String eroare){
prenume.setError(eroare);
prenume.requestFocus();
}
//Functie ce seteaza eroarea pentru campul apartament
public void seteazaEroareApartament(String eroare){
apartament .setError(eroare);
apartament .requestFocus();
}
}

70
Anexa 3

public class Conversatie extends AppCompatActivity {
private Toolbar conversatieToolbar ;
private ListView listaMesaje ;
private Button trimiteMesaj ;
private TextView mesajtext ;
private ImageButton adaugaImagine ;
private TextView dataMesaj ;
private MesajeAdapter mesajeAdapter ;

private FirebaseFirestore firebaseFirestore ;
private CollectionReference firebaseReference1 ;
private CollectionReference firebaseReference2 ;
private FirebaseAuth firebaseAuth ;
private UserInfo utilizatorInfo ;
private String utilizatorCurentId ;
private String utilizatorNume ;
private String celalaltUtilizatorId ;
private static final int RC_PHOTO_PICKER = 2;
private FirebaseStorage firebaseStorage ;
private StorageReference storageReference ;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_conversatie );

conversatieToolbar = findViewById(R.id. conversatieToolBar );
listaMesaje = findViewById(R.id. conversatieMesajeListView );
trimiteMesaj = findViewById(R.id. conversatieTrimiteButon );
mesajtext = findViewById(R.id. conversatieMesaj );
dataMesaj = findViewById(R.id. mesajData );
adaugaImagine = findViewById(R.id. conversatieAdaugaPoza );
firebaseStorage = FirebaseSto rage.getInstance ();
storageReference = firebaseStorage .getReference().child( "ImaginiConversatii" );
Intent intent = getIntent();
celalaltUtilizatorId = intent.getExtras().getString( "idCelalaltUtilizator" );
firebaseAuth = FirebaseAuth. getInstance ();
utilizatorInfo =firebaseAuth .getCurrentUser();
utilizatorCurentId = utilizatorInfo .getUid();
firebaseFirestore = FirebaseFirestore. getInstance ();
firebaseReference1 =
firebaseFirestore .collection( "Conversatii/" +utilizatorCurentId +"/"+celalaltUtilizatorId );
firebaseReference2 =
firebaseFirestore .collection( "Conversatii/" +celalaltUtilizatorId +"/"+utilizatorCurentId );

firebaseFirestore .collection( "Utilizatori" ).document( celalaltUtiliz atorId).get().addOnSuccessListener( n
ew OnSuccessListener<DocumentSnapshot>() {
@Override
public void onSuccess(DocumentSnapshot documentSnapshot) {
String numeCelalaltUtilizator = " "+documentSnapshot.get( "nume")+"
"+documentSnapshot.get( "prenume" );
conversatieToolbar .setTitle(numeCelalaltUtilizator);
}
});
//Initializam pentru mesaje ListView si adapterul
final List<Mesaj> mesajArrayList = new ArrayList<>();
mesajeAdapter = new MesajeAdapter( this, R.layout. vizualizare_un_mesaj , mesajArrayList);
listaMesaje .setAdapter( mesajeAdapter );
//La apasarea butonului de adaugare imagine
adaugaImagine .setOnClickListener( new View.OnClickList ener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Intent. ACTION_GET_CONTENT );
intent.setType( "image/jpeg" );
intent.putExtra(Intent. EXTRA_LOCAL_ONLY , true);
startActivityForResult(Intent. createChooser (intent, "Complete action using" ),
RC_PHOTO_PICKER );

71
}
});
//La apasarea butonului de trimite
trimiteMesaj .setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {

Mesaj mesajDetTrimis = new Mesaj(mesajtext .getText().toString(),
utilizatorCurentId ,null, new Date());
firebaseReference1 .document().set(mesajDetTrimis);
firebaseReference2 .document().set(mesajDetTrimis);

//Dupa trimitere stergem mesajul din EditText
mesajtext .setText( "");
}
});
//Permitem apasarea butonului de trimitere doar daca este ceva scris
// Enable Send button when there's text to send
mesajtext .addTextChangedListener( new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}

@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
if (charSequence.toString().trim().length() > 0) {
trimiteMesaj .setEnabled( true);
} else {
trimiteMesaj .setEnabled( false);
}
}

@Override
public void afterTextChanged(Editable editable) {
}
});

//conexiunea cu baza de date
firebaseRefer ence1.orderBy( "data", Query.Direction. ASCENDING ).addSnapshotListener( new
EventListener<QuerySnapshot>() {
@Override
public void onEvent(QuerySnapshot documentSnapshots, FirebaseFirestoreException e) {
for(DocumentCha nge doc:documentSnapshots.getDocumentChanges()){
//Ce facem in cazul in care au fost adaugate mesaje
if(doc.getType() == DocumentChange.Type. ADDED){
//Vom incarca datele din baza de date in cl asa model creata pentru fiecare
postare
Mesaj mesaj = doc.getDocument().toObject(Mesaj. class);
mesajArrayList .add(mesaj);
mesajeAdapter .notifyDataSetChanged();
listaMesaje .setSelection( mesajeAdapter .getCount() – 1);
}
}

}
});
}
//Metoda ce ne permite accesarea imaginii modificate
@Override
protected void onActivityResult( int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == RC_PHOTO_PICKER && resultCode == RESULT_OK ){
Uri imagineSelectataUri = data.getData();
final String sirIntamplator = UUID.randomUUID ().toString();
final StorageReference pozaRef = storageReference .child(sirIntamplator+ ".jpg");
UploadTask uploadTask = pozaRef.putFile(imagineSelectataUri);

Task<Uri> urlTask = uploadTask.continueWithTask( new Continuation<UploadTask.TaskSnapshot,
Task<Uri>>() {
@Override
public Task<Uri> then( @NonNull Task<UploadTask.TaskSnapshot> task) throws Exception {
if(!task.isSuccessful()) {
throw task.getException();
}

72
return pozaRef.getDownloadUrl();
}
}).addOnCompleteListener( new OnCompleteListener<Uri>() {
@Override
public void onComplete( @NonNull Task<Uri> task) {
if (task.isSuccessful()) {
Uri downloadUri = task.getResult();

Mesaj mesajDeTrimis = new Mesaj(null,
utilizatorCurentId ,downloadUri.toString(), new Date());
firebaseReference1 .document().set(mesajDeTrimis);
firebaseReference2 .document().set(mesajDeTrimis);
}
}
});
}
}
}
Adapterul utilizat

public class MesajeAdapter extends ArrayAdapter<Mesaj> {
public MesajeAdapter( @NonNull Context context, int resource, @NonNull List<Mesaj> objects) {
super(context, resource, objects);
}
private FirebaseFirestore firebaseFirestore ;
private FirebaseAuth firebaseAuth ;
private FirebaseUser firebaseUser ;
private String idUtilizatorCurent ;
private String idUtilizatorMesaj ;
@Override
public View getView( int position, View convertView, ViewGroup parent) {
firebaseAuth =FirebaseAuth. getInstance ();
firebaseUser =firebaseAuth .getCurrentUser();
idUtilizatorCurent =firebaseUser .getUid();
Mesaj mesaj = getItem(position);
idUtiliz atorMesaj = mesaj.getIdUtilizator();

if(idUtilizatorCurent .equals( idUtilizatorMesaj )){
convertView = ((Activity)
getContext()).getLayoutInflater().inflate(R.layout. vizualizare_un_mesaj , parent, false);
}else{
convertView = ((Activity)
getContext()).getLayoutInflater().inflate(R.layout. vizualizare_un_mesaj2 , parent, false);
}
ImageView imagine = convertView.findViewById(R.id. mesajImagine );
TextView textmesaj = convertView.fi ndViewById(R.id. mesajText );
TextView dataTextView = convertView.findViewById(R.id. mesajData );
boolean avemPoza = mesaj.getPozaUrl() != null;
//daca avem poza facem sa dispara textul
if(avemPoza){
textmesaj.setVisibility(View. GONE);
imagine.setVisibility(View. VISIBLE);
Glide.with(imagine.getContext())
.load(mesaj.getPozaUrl())
.into(imagine);
//Daca nu avem poza facem sa dispara campul pentru imagine
} else {
textmesaj.setVisibility(View. VISIBLE);
imagine.setVisibility(View. GONE);
textmesaj.setText(mesaj.getText());
}
Date data = mesaj.getData();
String dataString;
long milisecunde = data.getTime();
SimpleDateFormat format1 = new SimpleDateFormat( "dd/MM/yyyy" );
dataString = format1.format( new Date(milisecunde));
dataTextView.setText(dataString);

return convertView;
}
}

73
Anexa 4

public class Apa extends Fragment {

private Spinner spinnerAn ;
private Spinner spinnerLuna ;
private EditText contorVechi ;
private EditText contorNou ;
private EditText consum;
private EditText total;
private List<String> listaAn;
private List<String> listaLuna ;
private String anCurent ;
private String lunaCurenta ;
private String anSelectat ;
private String lunaSelectata ;
private Dialog dialog;
private FirebaseFirestore firestore;
private FirebaseAuth firebaseAuth ;
private String utilizatorCurentId ;
private float pretUnitate ;
private float consumFloat ;
private float plata;
private float consumAnteriorFloat ;
private float consumNouFloat ;

public Apa() {
// Required empty public constructor
}

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View rootView = inflater.inflate(R .layout. fragment_apa , container, false);

spinnerAn = rootView.findViewById(R.id. apaAn);
spinnerLuna = rootView.findViewById(R.id. apaLuna);
contorVechi = rootView.findViewById(R.id. apaContorVechi );
contorNou = rootView.findViewById(R.id. apaContorNou );
consum = rootView.findViewById(R.id. apaConsum );
total = rootView.findViewById(R.id. apaPlata );
Button salveaza = rootView.findViewById(R.id. apaSalveaza );

dialog = new Dialog(getActiv ity());

firestore = FirebaseFirestore. getInstance ();
firebaseAuth = FirebaseAuth. getInstance ();
utilizatorCurentId = firebaseAuth .getCurrentUser().getUid();

//Obtinem data curenta
DateFormat dateFormatAn = new SimpleDateFormat( "YYYY");
DateFormat dateFormat = new SimpleDateFormat( "MM");
Date date = new Date();
anCurent = dateFormatAn.format(date);
lunaCurenta = String. valueOf(Integer. parseInt (dateFormat.format(date)));

//Declaram listele pentru ani si luni
listaAn = new ArrayList<>();
listaLuna = new ArrayList<>();

extrageListaAni();

//Populam lista cu lini
listaLuna .add("Ianuarie" );
listaLuna .add("Februarie" );
listaLuna.add("Martie" );
listaLuna .add("Aprilie" );
listaLuna .add("Mai");

74
listaLuna .add("Iunie");
listaLuna .add("Iulie");
listaLuna .add("August" );
listaLuna .add("Septembrie " );
listaLuna .add("Octombrie" );
listaLuna .add("Noiembrie" );
listaLuna .add("Decembrie" );

//Afisam lista
ArrayAdapter<String> adapterLuna = new ArrayAdapter<String>(getActivity(),
android.R.layout. simple_spinner_item ,listaLuna );
adapterLuna.setDropDownViewResource(android.R.layout. simple_spinner_dropdown_item );
spinnerLuna .setAdapter(adapterLuna);

//Setam luna pe care o vedem initial ca fiind luan curenta
spinnerLuna .setSelection(Integer. parseInt (lunaCurenta )-1);

anSelectat = anCurent ;
lunaSelectata = String. valueOf(Integer. parseInt (lunaCurenta ));

extrageDate();

//Cand selectam un an din spinner
spinnerAn .setOnItemSelectedListener( new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
anSelectat = parent.getItemAtPosition(position).toString();
extrageDate();
//Daca anul selectat si luna selectata sunt cele curente este permisa scriere in
contorNou
if(anSelectat .equals( anCurent ) && lunaSelectata .equals( lunaCurenta )){
contorNou .setEnabled( true);
}
}

@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});

//Cand selectam o luna din spinner
spinnerLuna .setOnItemSelectedListener( new AdapterView.OnItem SelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
//Daca se selecteaza o luna viitoare afisam un popup cu eroarea
if(anSelectat .equals(anCurent ) && position> Integer. parseInt (lunaCurenta )){
dialog.setContentView(R.layout. popup);
ImageView popupImagine = dialog.findViewById(R.id. popupImagine );
ImageView popupFundal = dialog.findViewById(R.id. popupFundal );
TextView popupText = dialog.findViewById(R.id. popupText );
Button popupButon = dialog.findViewById(R.id. popupButon );
ImageView popupInchide = dialog.findViewById(R. id.popupInchide );

popupImagine.setImageResource(R.drawable. popupesec );
popupFundal.setImageResource(R.color. popupesec );
popupText.setText( "Nu se poate selecta o lună viitoare!" );
popupButon.setTextColor(ContextCompat. getColor (getActivity(), R.color. popupesec ));
popupButon.setText( "OK");

dialog.setCancelable( false);

popupButon.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
dialog.dismiss();
}
});
dialog.show();
spinnerLuna .setSelection(Integer. parseInt (lunaCurenta )-1);

}else {
lunaSelectata = String. valueOf(position+ 1);
extrageDate();

75
if(anSelectat .equals( anCurent ) && lunaSelecta ta.equals( lunaCurenta )){
//contorNou.setEnabled(false);
}else {
//
}
}
extrageDate();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {

}
});
//Cand apasam pe butonul salveaza adaugam datele scrise in baza de date
salveaza.setOnClickListener( new View.OnClickListener() {
@Override
public void onClick(View v) {
if(contorVechi .getText()== null){
contorVechi .setError( "Trebuie completat!" );
}else if(contorNou .getText()== null){
contorNou .setError( "Trebuie complet at!");
}else{
adaugaDate();
}
}
});

return rootView;
}
//Functie ce permite vizualizarea anilor
public void extrageListaAni(){
//Populam lista cu anii posibili (extrasi din baza de date)
firestore .collection( "Administratie" )
.get()
.addOnCompleteListener( new OnCompleteListener<QuerySnapshot>() {
@Override
public void onComple te(@NonNull Task<QuerySnapshot> task) {
if(task.isSuccessful()){
for (QueryDocumentSnapshot document : task.getResult()) {
listaAn.add(document.getId());
}

ArrayAdapter<String> adapterAn = new ArrayAdapter<String>(getActivity(),
android.R.layout. simple_spinner_item ,listaAn);

adapterAn.setDropDownViewResource(android.R.layout. simple_spinner_d ropdown_item );
spinnerAn .setAdapter(adapterAn);
spinnerAn .setSelection( listaAn.indexOf( anCurent ));
} else {
Log.w("Eroare" , "Error getting documents." , task.getException());
}
}
});
}
//Functie ce permite afisarea informatiilor daca acestea exista
public void extrageDate(){

consum.setText( null);
contorNou .setText( null);
contorVechi .setText( null);
total.setText( null);
//Verificam daca exista documentul pe care il cautam
final DocumentReference documentReference =
firestore .document( "Administratie/" +anSelectat +"/"+lunaSelectata +"/"+utilizatorCurentId );

documentReference.get().addOnCompleteListener( new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete( @NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
//Daca documentul exista citim informatiile idn el
if (document.exists()) {

//Daca exista informatii t recute in contorul vechi le vom afisa

76
if(document.get( "apa.contorVechi" ) != null) {
BigDecimal x = new BigDecimal(document.get( "apa.contorVechi" ).toString());
x = x.setScale( 2,BigDecimal. ROUND_HALF_UP );
contorVechi .setText(String. valueOf(x));
}else{
//Daca nu exista nu vom afisa nimic si vom permite scrierea informatiilor
contorVechi .setEnabled( true);
}
//Daca exista informatii in contorul nou le afisam
if(document.get( "apa.contorNou" ) !=null) {
BigDecimal x = new BigDecimal(document.get( "apa.contorNou" ).toString());
x = x.setScale( 2,BigDecimal. ROUND_HALF_UP );
contorNou .setText(String. valueOf(x));
}else{
//Daca nu exista nu vom afisa nimic si vom permite scrierea
contorNou .setEnabled( true);
}
//Daca exista informatii despre cat trebuie platit le vom afisa
if(document.get( "apa.plata" ) != null) {
BigDecimal x = new BigDecimal(document.get( "apa.plata" ).toString());
x = x.setScale( 2,BigDecimal. ROUND_HALF_UP );
total.setText(String. valueOf(x)+"RON");
}
if(document.get( "apa.consum" )!=null){
BigDecimal x = new BigDecimal(document.get( "apa.consum" ).toString());
x = x.setScale( 2,BigDecimal. ROUND_HALF_UP );
consum.setText(String. valueOf(x));
}
//Daca nu exista documentul il vom crea adaugand date irelevante ce vor fi
suprascrise ulterior
} else {
Map<String, Object> apaMap = new HashMap<>();
apaMap.put( "apa",1);
documentReference .set(apaMap);
}
}
}
});
//Facem acelasi lucru si pentru documentul de luna viitore in care va trebui sa trecem consumul
vechi
//Stabilim care va fi luna viitore
String lunaSelectata1;
String anSelectat1;
if (lunaSelectata .equals( "12")) {
lunaSelectata1 = "1";
anSelectat1 = String. valueOf(Integer. parseInt (anSelectat ) + 1);
} else {
lunaSelectata1 = String. valueOf(Integer. parseInt (lunaSelectata ) + 1);
anSelectat1 = anSelectat ;
}
final DocumentReference documentReference2 = firestore .document( "Administratie/" + anSelectat1
+ "/" + lunaSelectata1 + "/" + utilizatorCurentId );
//Daca documentul nu exista il creem
documentReference2.get().addOnCompleteListener( new OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete( @NonNull Task<DocumentSnapshot> task) {
if(task.isSuccessful()){
DocumentSnapshot document = task.getResult();
if (!document.exists()) {
Map<String, Object> apaMap = new HashMap<>();
apaMap.put( "apa",1);
documentReference2 .set(apaMap);
}
}
}
});
}

//Functie ce adauga datele
public void adaugaDate() {
//Daca a fost introdusa o valoare mai mica decat valoarea anterioara
consumAnteriorFloat = Float. parseFloat (contorVechi .getText().toString());
consumNouFloat = Float. parseFloat (contorNou .getText().toString());
consumFloat = consumNouFloat -consumAnteriorFloat ;

77

if (consumNouFloat < consumAnteriorFloat ) {
contorNou .setError( "Valoare prea mică" );
} else {
DocumentReference documentReferenceTotal = firestore .document( "Administratie/" + anSelectat
+ "/" + lunaSelectata + "/Total" );
documentReferenceTotal.get().addOnCompleteListener( new
OnCompleteListener<DocumentSnapshot>() {
@Override
public void onComplete( @NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if(document.exists()){
pretUnitate = Float. parseFloat (document.get( "apa.costUnitar" ).toString());
plata = pretUnitate *consumFloat ;
}else {
plata = -1;
}
}
DocumentReference documentReference1 = firestore .document( "Administratie/" +
anSelectat + "/" + lunaSelectata + "/" + utilizatorCurentId );
//Calculam care va fi luna viitoare
String lunaSelectata1;
String anSelectat1;
if (lunaSelectata .equals( "12")) {
lunaSelectata1 = "1";
anSelectat1 = String. valueOf(Integer. parseInt (anSelectat) + 1);
} else {
lunaSelectata1 = String. valueOf(Integer. parseInt (lunaSelectata ) + 1);
anSelectat1 = anSelectat ;
}
DocumentReference documentReference2 = firestore .document( "Administratie/" +
anSelectat1 + "/" + lunaSelectata1 + "/" + utilizatorCurentId );

if(plata==-1) {
documentReference1.update(
"apa.contorNou" , consumNouFloat ,
"apa.contorVechi" , consumAnteriorFloat ,
"apa.consum" , consumFloat ,
"apa.plata" , null);
}else{
documentReference1.update(
"apa.contorNou" , consumNouFloat ,
"apa.contorVechi" , consumAnteriorFloat ,
"apa.consum" , consumFloat ,
"apa.plata" , plata);
}
documentReference2.update(
"apa.contorVechi" , consumNouFloat );
extrageDate();
}
});

}
}

Similar Posts