1 of 77

Tehnici avansate pentru

dezvoltarea aplicațiilor mobile

13 - Compose - Room

2 of 77

Room

  • Bibliotecă de persistență a datelor

  • Parte din Android Jetpack

  • Layer de abstractizare peste SQLite
    • set de funcții care ascund implementarea de bază
    • interfață pentru un set de funcționalități (SQLite)

11 - Compose - Obținerea și afișarea imaginilor folosind Coil

2

3 of 77

Room

  • Room simplifică configurarea și interacțiunea cu BD

  • Room verifică la compilare declarațiile SQLite

3

4 of 77

Room

  • Room este o sursă de date

4

5 of 77

Room - componente

  • Entities
    • reprezintă tabelele bazei de date
    • folosite cand inserăm sau actualizăm rânduri din tabele
  • DAO (Data Access Objects)
    • oferă metode pentru a prelua/actualiza/insera/șterge date
  • Clasa Database
    • clasa bazei de date
    • oferă instanțe ale DAO-urilor asociate bazei de date

5

6 of 77

Room - componente

6

7 of 77

Entity

7

8 of 77

Entity

  • O clasă Entity definește un tabel din BD
    • O instanță a clasei = un rând în tabel

  • Pentru fiecare clasă Entity => se crează un tabel

  • Folosim adnotarea @Entity pentru a marca o clasă Entity

8

9 of 77

Entity

9

10 of 77

Entity

  • Fiecare câmp al clasei Entity = o coloană în tabelă
  • Fiecare instanță a clasei Entity = un rând în tabelă

  • Fiecare instanță are o cheie primară
    • identifică în mod unic o intrare în tabelă
    • odată atribuită nu mai poate fi modificată
    • identifică obiectul Entity

10

11 of 77

Entity - exemplu

  • data class
  • Adnotare @Entity
  • Numele tabelului diferit de numele clasei => parametru

11

12 of 77

Entity - exemplu

  • Adnotare @PrimaryKey
    • generare automată a cheii primare
    • valoare default = 0

12

13 of 77

DAO

13

14 of 77

Data Access Object (DAO)

  • Interfață abstractă
    • separă layer-ului de persistență de restul aplicației
    • principiul responsabilității unice

  • Ascunde complexitatea operațiilor cu BD în layer-ul de persistență

14

15 of 77

DAO

  • Implementăm DAO ca o interfață custom
    • metode pentru interogarea, inserarea, actualizarea și ștergerea intrărilor în BD
    • Room generează implementarea clasei la compilare

15

16 of 77

DAO - adnotări

  • Adnotări @Insert, @Delete, @Update
    • inserare, ștergere, actualizare
    • fără să necesite nicio instrucțiune SQL

  • Adnotarea @Query
    • interogare
    • operații complexe de inserare, ștergere, actualizare
    • necesită instrucțiunea SQL

16

17 of 77

DAO - exemplu

  • Operații de implementat:
    • Inserarea unui element nou
    • Actualizarea unui element existent
    • Ștergerea unei intrări
    • Obținerea unui element pe baza cheii primare (id)
    • Obținerea tuturor elementelor

17

18 of 77

DAO - exemplu

18

19 of 77

DAO - inserare - exemplu

  • Operațiile cu BD pot dura mult
    • Room nu permite accesul BD pe main thread
    • trebuie executate pe alt thread => funcție suspendabilă

19

20 of 77

DAO - inserare - exemplu

  • La inserare pot apărea conflicte
    • Locuri diferite din cod -> inserari cu aceeași cheie primară
    • Dacă inserăm dintr-un singur loc
      • onConflict = OnConflictStrategy.IGNORE

20

21 of 77

DAO - generare cod

  • Este suficient să declarăm funcția cu @Insert
    • Room va genera codul pentru inserarea elementului în BD

  • Când sunt apelate funcțiile adnotate
    • => Room execută interogarea SQL asociată

21

22 of 77

DAO - update și delete - exemplu

  • update - actualizează elementul cu aceeași cheie primară
    • se pot actualiza toate proprietățile sau doar unele
  • delete - șterge un element sau mai multe
  • funcții suspendabile

22

23 of 77

DAO - interogare - exemplu

  • Returnează un rândul din BD cu acel id
  • Trebuie scrisă instrucțiunea SQL
  • :id pentru a se referi la argumentul funcției
  • Returnează un Flow<Item>

23

24 of 77

Flow

  • Flow ca tip de retur
    • notificări când se modifică datele în BD
    • Room menține Flow-ul actualizat
    • cerem datele explicit o singură dată
    • observăm datele și actualizăm UI-ului
      • => actualizarea automată a UI-ului

24

25 of 77

Flow

  • Room face interogarea pe un thread în background
    • nu e nevoie de funcție suspendabilă
    • nu e nevoie să rulăm într-o corutină

  • Recomandare: folosiți Flow în layer-ul de persistență

25

26 of 77

DAO - interogare - exemplu

  • Obținem toate intrările din tabel
    • ordonate după nume
  • Returnează un Flow<List<Item>>
  • Room actualizează acest Flow
    • obținem datele o singură dată

26

27 of 77

Instanța Database

27

28 of 77

Instanța Database

  • Clasă abstractă care extinde RoomDatabase
    • abstractă => Room va creea implementarea

  • Container pentru BD

  • Adnotată cu @Database
    • argumentele adnotării: entitățile din BD, versiune

28

29 of 77

Instanța Database

  • Metodă abstractă care returnează o instanță Dao
    • Room creează implementarea

  • O singură instanță de RoomDatabase => singleton

  • Room.databaseBuilder
    • creează BD dacă nu există
    • altfel returnează pe cea existentă

29

30 of 77

Instanța Database - exemplu

  • Clasă abstractă care extinde RoomDatabase
  • Adnotare @Database cu parametrii pentru crearea BD
    • lista de entități, versiunea
  • Funcție abstractă care returnează o instanță ItemDao

30

31 of 77

Instanța Database - exemplu

  • În cadrul clasei abstracte
    • obiect companion
    • permite accesul la metodele de creare/obținere a BD

31

32 of 77

Instanța Database - exemplu

  • Variabilă privată nullable Instance
    • referă BD dacă e creată
    • o singură instanță per aplicație
    • Adnotare @Volatile
      • modificările făcute de un thread sunt vizibile imediat pentru celelalte thread-uri

32

33 of 77

Instanța Database - exemplu

  • În obiectul companion
  • Metodă care primește contextul și returnează instanța BD
    • Inițializează BD
  • Bloc synchronized pentru a evita race conditions
    • BD e inițializată o singură dată

33

34 of 77

Instanța Database - exemplu

  • Constructorul Room.databaseBuilder()
    • inițializează/obține BD
    • context, clasa BD, numele BD
    • build() - crează instanța BD
    • blocul also - salvează referința la instanța în Instance

34

35 of 77

Instanța Database - exemplu

35

36 of 77

Repository

36

37 of 77

Repository

  • Adăugăm un Repository
    • operații de interogare, inserare, actualizare, ștergere
    • a unui element dintr-o sursă de date

  • Definim o interfață mapată pe implementarea DAO
  • Creăm o clasă care implementează interfața
    • apelează funcțiile din DAO

37

38 of 77

Repository - interfață - exemplu

38

39 of 77

Repository - clasă - exemplu

  • Apelează funcțiile corespunzătoare din DAO
  • Pentru insert, delete, update - funcții suspendabile
  • Funcții interogare - rulate pe un thread de background

39

40 of 77

Repository - ID - exemplu

  • Interfață container
  • Proprietate repository

40

41 of 77

Repository - ID - exemplu

  • În clasa container
    • inițializăm/obținem instanța Database
    • obținem instanța DAO de la instanța Database
    • instanțiem repo-ul cu DAO ca argument => injectarea dependenței

41

42 of 77

ViewModel

42

43 of 77

ViewModel

  • ViewModel interacționează cu BD prin DAO
    • ViewModel => repository => DAO => BD
  • ViewModel furnizează date către UI
    • include starea UI-ului => actualizare automată a UI-ului

43

44 of 77

Corutine

  • Operațiile cu BD nu trebuie rulate pe main thread
    • rulate asincron
    • thread-uri de background

  • 2 soluții
    • corutine pornite din viewModelScope (din ViewModel)
    • sau din coroutineScope (din composable)

44

45 of 77

Corutine

  • 2 variante de implementare folosind corutine:
    • 1. corutina pornită dintr-un composable
        • folosind coroutineScope.launch()
        • composable iese din Compoziție => corutina este anulată
    • 2. corutina lansată în ViewModel
        • folosind viewModelScope.launch()
        • ViewModel este anulat => corutina este anulată

45

46 of 77

ViewModel - exemplu

  • Folosim clase data pentru a stoca starea UI pentru un Item

46

47 of 77

ViewModel - exemplu

  • Starea UI-ului
    • obiect observabil

47

48 of 77

UI Layer - inserarea datelor

48

49 of 77

ViewModel - insert - exemplu

  • Convertim starea UI-ului într-un Item
  • Salvăm Item-ul în BD
    • insertItem() din repository => apel DAO => BD

49

50 of 77

UI - insert - exemplu

  • În UI, într-o funcție composable
    • folosim rememberCoroutineScope()
    • returnează un CoroutineScope asociat cu acel composable
      • corutina este anulată dacă CoroutineScope părăsește Compoziția

50

51 of 77

UI - insert - exemplu

  • În UI, când apăsăm pe buton => salvează Item-ul

51

52 of 77

UI - insert - exemplu

  • Corutină lansată folosind coroutineScope.launch()
    • pentru că saveItem() este o funcție suspendabilă
      • apelată doar dintr-o corutină sau altă funcție suspendabilă

  • Apelăm saveItem() din ViewModel
    • UI => ViewModel => repo => DAO => BD

52

53 of 77

UI Layer - preluarea și afișarea datelor

53

54 of 77

Flow

  • Funcțiile de interogare din DAO returnează Flow
    • gestionarea fluxului de date generic

  • Room - actualizează datele în mod asincron prin Flow
    • apelarea metodelor din DAO o singură dată în ciclul de viață

54

55 of 77

Flow

  • În UI colectăm datele din Flow

  • Problema:
    • schimbările de configurare pot duce la recrearea activității
    • => recompoziție și colectarea din nou din Flow

55

56 of 77

Flow

  • Dorim să evităm pierderea datelor în caz de reconfigurare
    • => valorile păstrate ca stare

  • Flow-urile ar trebui anulate dacă nu le mai observă nimeni
    • dacă s-a terminat ciclul de viață al unui composable

56

57 of 77

StateFlow

  • Expunem Flow-ul din ViewModel folosind un StateFlow
    • starea UI-ului

  • StateFlow permite salvarea și observarea datelor
    • indiferent de ciclul de viață al UI-ului
    • permite actualizarea automată a UI-ului

57

58 of 77

StateFlow

  • stateIn = convertește Flow în StateFlow

  • collectAsState = colectează datele din StateFlow
    • convertește din StateFlow în State

  • StateFlow = API-ul observabil pentru starea UI-ului
    • Datele din BD Room se schimbă => starea UI-ului se schimbă => se actualizează UI-ul automat

58

59 of 77

ViewModel - StateFlow - exemplu

  • În ViewModel
    • Obținem toate intrările din baza de date sub formă de Flow
    • Transformăm în StateFlow
    • => starea UI-ului pentru ecranul Home

59

60 of 77

ViewModel - StateFlow - exemplu

  • homeUiState = starea UI-ului
    • tipul StateFlow<HomeUiState>
  • getAllItemsStream()din repo
    • => returnează un Flow<List<Item>>

60

61 of 77

ViewModel - StateFlow - exemplu

  • map() transformă din List<Item> în HomeUIState
  • stateIn() transformă din Flow în StateFlow
    • viewModelScope definește ciclul de viață al StateFlow
    • scope anulat => StateFlow anulat

61

62 of 77

ViewModel - StateFlow - exemplu

  • started => Flow-ul este activ doar când UI-ul este vizibil
  • valoare inițială a fluxului de stare = HomeUiState()

62

63 of 77

UI - Colectarea datelor din StateFlow

  • În UI, instanțiem ViewModel folosind Factory
  • Folosim collectAsState() pentru a colecta ultimele valori din StateFlow sub forma de State
    • starea UI-ului

63

64 of 77

Afișarea datelor

  • În UI preluăm lista de elemente din starea UI-ului și o afișăm

64

65 of 77

UI Layer - actualizarea datelor

65

66 of 77

ViewModel - update - exemplu 1

  • În ViewModel
    • folosim viewModelScope.launch pentru a lansa o corutină
    • obținem elementul curent din starea UI
    • itemsRepository.updateItem pentru a actualiza elementul
    • lucrăm cu o copie a elementului pe care o modificăm

66

67 of 77

UI - update - exemplu 1

  • În UI
    • Apelăm funcția din ViewModel atunci când se apasă un buton

67

68 of 77

ViewModel - update - exemplu 2

  • În ViewModel
    • funcție suspendabilă
    • obținem elementul din starea UI
    • itemsRepository.updateItem pentru actualizarea elementului

68

69 of 77

UI - update - exemplu 2

  • În UI, lansăm o corutină folosind coroutineScope.launch
  • Apelăm funcția din ViewModel pentru actualizare

69

70 of 77

UI Layer - ștergerea datelor

70

71 of 77

ViewModel - delete - exemplu

  • În ViewModel
    • funcție suspendabilă
    • elementul este obținut din starea UI
    • itemsRepository.deleteItem pentru ștergerea elementului

71

72 of 77

UI - delete - exemplu

  • În UI
    • lansăm o corutină folosind coroutineScope.launch
    • apelăm funcția din ViewModel pentru ștergerea elementului

72

73 of 77

Database Inspector

  • În Android Studio
  • Inspectăm, interogăm, modificăm BD în timp ce aplicația rulează
  • Depanarea BD

73

74 of 77

Ordinea apelurilor

74

75 of 77

Bibliografie

11 - Compose - Obținerea și afișarea imaginilor folosind Coil

75

76 of 77

Bibliografie

11 - Compose - Obținerea și afișarea imaginilor folosind Coil

76

77 of 77

Cuvinte cheie

  • Room
  • Entity
  • DAO
  • Flow
  • StateFlow
  • RoomDatabase
  • Repository
  • ViewModel
  • UI State

11 - Compose - Obținerea și afișarea imaginilor folosind Coil

77

77