1 of 69

Tehnici avansate pentru

dezvoltarea aplicațiilor mobile

15 - Compose - WorkManager

2 of 69

WorkManager

2

3 of 69

WorkManager

  • Parte din Android Jetpack

  • Activități de background
    • execuție oportunistă
      • face activitatea cât mai repede posibil
    • execuție garantată
      • garantează pornirea activității chiar și în anumite situații nefavorabile

15 - Compose - WorkManager

3

4 of 69

Avantaje

  • Suport pentru task-uri asincrone
    • ocazionale
    • periodice

  • Suport pentru constrângeri
    • condiții de rețea
    • spațiu de stocare
    • starea de încărcare a bateriei

15 - Compose - WorkManager

4

5 of 69

Avantaje

  • Înlănțuirea cererilor de execuție complexe
    • rularea task-urilor în paralel

  • Pipeline
    • ieșirea de la o cerere de execuție = intrarea pentru altă cerere

  • Afișarea stării cererilor de execuție în UI

15 - Compose - WorkManager

5

6 of 69

WorkManager

  • Se bazează pe alte API-uri
    • JobScheduler
    • AlarmManager

  • Alege API-ul potrivit pe baza condițiilor din timpul rulării
    • Nivelul API de pe dispozitiv

15 - Compose - WorkManager

6

7 of 69

Când folosim WorkManager?

  • Pentru task-urile care trebuie neapărat finalizate

  • Task-ul nu depinde de rularea aplicației
    • task-ul va rula chiar dacă aplicația este închisă
    • sau userul se întoarce la HomeScreen

  • Când vrem să rulăm un task în afara thread-ului principal

15 - Compose - WorkManager

7

8 of 69

Exemple de task-uri

  • Interogarea periodică pentru obținerea ultimelor știri

  • Aplicarea filtrelor asupra unei imagini și salvarea ei

  • Sincronizarea periodică a datelor locale prin rețea

15 - Compose - WorkManager

8

9 of 69

Clasa Worker/CoroutineWorker

  • Clasa Worker
    • task-uri sincron pe thread-uri de background

  • Clasa CoroutineWorker
    • task-uri asincrone pe thread-uri de background
    • corutine din Kotlin

15 - Compose - WorkManager

9

10 of 69

CoroutineWorker

  • Extindem clasa CoroutineWorker

  • Suprascriem metoda doWork()
    • codul rulat în background

15 - Compose - WorkManager

10

11 of 69

Clasa WorkRequest

  • Cerere de a efectua un anumit task

  • Rulare o singură dată sau periodic
  • Constrângeri
    • condiții îndeplinite înainte ca task-ul să fie executat

  • Transmitem CoroutineWorker la crearea WorkRequest-ului

15 - Compose - WorkManager

11

12 of 69

Clasa WorkManager

  • Planifică WorkRequest-ul și îl face să ruleze
    • îl crează și îl adaugă în coadă

  • Distribuie încărcarea pe resursele sistemului

  • Respectă constrângerile specificate

15 - Compose - WorkManager

12

13 of 69

Exemplu de implementare

13

14 of 69

CoroutineWorker - exemplu

  • Clasa extinde CoroutineWorker
    • suprascriem funcția suspendabilă doWork()
    • rulează asincron în background
    • recomandată dacă folosim Kotlin

15 - Compose - WorkManager

14

15 of 69

CoroutineWorker - exemplu

  • Funcția makeStatusNotification din WorkerUtil
    • afișarea unui banner de notificare pe ecran

15 - Compose - WorkManager

15

16 of 69

CoroutineWorker - exemplu

  • CoroutineWorker rulează cu Dispatchers.Default implicit
    • configurăm Dispatchers.IO folosind withContext()
      • thread pool pentru operații IO blocante
  • return@withContext pentru a returna din lambda

15 - Compose - WorkManager

16

17 of 69

CoroutineWorker - exemplu

  • Munca efectivă
    • decodificăm și încărcăm imaginea ca bitmap
    • aplicăm blur pe imagine
    • salvăm imaginea într-un fișier temporar

15 - Compose - WorkManager

17

18 of 69

CoroutineWorker - exemplu

  • Dacă munca este executată cu succes
    • returnăm Result.success()
  • În caz de excepție (nu este executată cu succes)
    • returnăm Result.failure()

15 - Compose - WorkManager

18

19 of 69

Repository - interacțiune cu WorkManager

  • În repository vom lucra cu WorkManager
    • vom crea WorkRequest-uri
    • vom instrui WorkManager să le ruleze

  • 2 tipuri de WorkRequest-uri
    • OneTimeWorkRequest - rulează o singură dată
    • PeriodicWorkRequest - rulează periodic

15 - Compose - WorkManager

19

20 of 69

Repository - exemplu

  • Instanța WorkManager

15 - Compose - WorkManager

20

21 of 69

Repository - exemplu

  • Creăm un OneTimeWorkRequest folosind OneTimeWorkRequestBuilder
    • specificăm tipul de Worker
  • Instruim WorkManager să pornească acel WorkRequest

15 - Compose - WorkManager

21

22 of 69

Tipul Data

22

23 of 69

Tipul Data

  • Inputul și outputul unui Worker
    • obiecte de tip Data

  • Data = container lightweight pentru perechi cheie/valoare

  • Stochează o mică cantitate de date = input sau output pentru Worker

15 - Compose - WorkManager

23

24 of 69

Repository - Data - exemplu

  • În repository
    • funcție helper care crează un obiect de tip Data
    • creează un obiect Data.Builder
    • adaugă perechile chei-valoare în builder
    • crează și returnează obiectul Data

15 - Compose - WorkManager

24

25 of 69

Repository - Data - exemplu

  • În repository:
    • creăm un input Data ce include 2 valori (nivel blur, URI)
    • setăm inputul pentru WorkRequest
      • folosind setInputData()

15 - Compose - WorkManager

25

26 of 69

Worker - Data - exemplu

  • În Worker, în doWork()
    • obținem valorile din input Data

15 - Compose - WorkManager

26

27 of 69

Worker - Data - exemplu

  • În Worker, în doWork()
    • folosim un ContentResolver
      • obținem imaginea asociată cu acel URI
    • decodificăm imaginea

15 - Compose - WorkManager

27

28 of 69

Worker - Data - exemplu

  • În Worker, în doWork()
    • aplicăm blur pe imagine
    • scriem imaginea într-un fișier temporar
    • punem în output Data URI-ul imaginii rezultate
    • returnăm acest output Data din Worker
      • util pentru lanțul de work-eri

15 - Compose - WorkManager

28

29 of 69

Lanț de workeri

29

30 of 69

Rularea workerilor

  • Rularea unor WorkRequest-uri secvențial sau în paralel

15 - Compose - WorkManager

30

31 of 69

Lanț de WorkRequest-uri

  • Lanț de WorkRequest-uri
    • rulare secvențială
    • outputul unui WorkRequest poate deveni inputul următorului WorkRequest

15 - Compose - WorkManager

31

32 of 69

Lanț de WorkRequest-uri

15 - Compose - WorkManager

32

33 of 69

Repository - lanț de WorkRequest-uri

  • În repository, creăm lanțul
  • beginWith() returnează un obiect WorkContinuation
    • punctul de început pentru lanț - primul WorkRequest
  • OneTimeWorkRequest.from() = creăm un WorkRequest ce rulează o singură dată

15 - Compose - WorkManager

33

34 of 69

Repository - lanț de WorkRequest-uri

  • Creăm un WorkRequest cu un input Data
    • folosind un OneTimeWorkRequestBuilder
  • Folosim continuation.then() pentru a adăuga WorkRequest-ul la lanț

15 - Compose - WorkManager

34

35 of 69

Repository - lanț de WorkRequest-uri

  • Creăm ultimul WorkRequest folosind un builder
  • Adăugăm WorkRequest-ul la lanț
  • Instruim WorkManager să execute lanțul folosind enqueue()

15 - Compose - WorkManager

35

36 of 69

Lanț unic de workeri

36

37 of 69

Lanț unic de workeri

  • Dorim să rulăm un singur lanț de workeri la un moment dat
    • => lanț unic de wokeri

  • Folosim beginUniqueWork() în loc de beginWith()
    • nume unic pentru lanț

15 - Compose - WorkManager

37

38 of 69

Politica

  • ExistingWorkPolicy
    • specifică ce se întâmplă dacă există deja un work în execuție
    • REPLACE, KEEP, APPEND, APPEND_OR_REPLACE
    • REPLACE înlocuiește workul anterior cu cel nou

15 - Compose - WorkManager

38

39 of 69

Repository - lanț unic de workeri

  • beginUniqueWork() - pornim un lanț unic de workeri
    • numele lanțului
    • politica REPLACE
    • WorkRequest

15 - Compose - WorkManager

39

40 of 69

Afișarea informațiilor în UI

40

41 of 69

Informații despre WorkRequest-uri

  • Obținem informații despre WorkRequest-urile care rulează și actualizăm UI-ului

  • Funcții pentru obținerea informațiilor
    • getWorkInfoByIdLiveData()
      • obținem pe baza id-ului unui WorkRequest
      • returnează LiveData<WorkInfo> pentru un WorkRequest specific

15 - Compose - WorkManager

41

42 of 69

Informații despre WorkRequest-uri

  • Funcții pentru obținerea informațiilor
    • getWorkInfosForUniqueWorkLiveData()
      • obținem pe baza numelui lanțului
      • returnează un LiveData<List<WorkInfo>> pentru un lanț unic
    • getWorkInfosByTagLiveData()
      • obținem pe baza tag-ului
      • returnează un LiveData<List<WorkInfo>> pentru un tag

15 - Compose - WorkManager

42

43 of 69

WorkInfo

  • WorkInfo
    • starea curentă a unui WorkRequest:
      • BLOCKED, CANCELLED, ENQUEUED
      • FAILED, RUNNING, SUCCEEDED
    • dacă WorkRequest-ul este finalizat
    • ID, tags
    • date de ieșire

15 - Compose - WorkManager

43

44 of 69

LiveData

  • Metodele returnează LiveData

  • LiveData
    • container de date observabil
    • conștient de ciclul de viață

  • Convertim LiveData într-un Flow de obiecte WorkInfo
    • apelăm .asFlow()

15 - Compose - WorkManager

44

45 of 69

Repository - Tag - exemplu

  • În repository
  • Adăugăm un tag când creăm WorkRequest-ul pe care vrem să-l urmărim

15 - Compose - WorkManager

45

46 of 69

Repository - Tag - obținerea informațiilor

  • În repository
    • cerere de a obține informațiile pe baza tag-ului
    • getWorkInfosByTagLiveData()
    • returnează LiveData
    • conversie în Flow folosind .asFlow()

15 - Compose - WorkManager

46

47 of 69

Repository - Tag - obținerea informațiilor

  • Transformare mapNotNull()
    • din List<WorkInfo> în WorkInfo
  • Informațiile WorkInfo emise ca Flow din repo
    • consumate de ViewModel

15 - Compose - WorkManager

47

48 of 69

ViewModel - stare UI - exemplu

15 - Compose - WorkManager

48

49 of 69

ViewModel - stare UI - exemplu

  • Flow-ul outputWorkInfo din repository
    • => starea UI-ului, de tip StateFlow
  • map - maparea valorilor WorkInfo la stările BlurUiState

15 - Compose - WorkManager

49

50 of 69

ViewModel - stare UI - exemplu

  • Funcția stateIn() - transformă Flow în StateFlow
    • Flow = cold flow
    • StateFlow = hot flow
  • ViewModel expune informația către UI
    • prin starea UI-ului de tipul StateFlow

15 - Compose - WorkManager

50

51 of 69

UI - colectăm starea UI-ului - exemplu

  • În UI, colectăm ultima valoare din StateFlow ca State
    • collectAsStateWithLifecycle()
    • conștientă de ciclul de viață
    • conservă resursele aplicației

15 - Compose - WorkManager

51

52 of 69

UI - folosirea informațiilor - exemplu

  • În UI, folosim starea UI-ului (de tip State)
    • în funcție de stare vom afișa elemente UI diferite
    • ex: dacă starea este Default => afișăm un buton Start

15 - Compose - WorkManager

52

53 of 69

Anularea unui work

53

54 of 69

Anularea unui work

  • Putem anula un work folosind:
    • id
    • tag
    • numele lanțului unic

  • Dacă vrem să anulăm un lanț întreg => folosim numele lanțului

15 - Compose - WorkManager

54

55 of 69

Repository - anularea unui lanț - exemplu

  • În repository
    • apelăm funcția cancelUniqueWork()
    • numele lanțului

15 - Compose - WorkManager

55

56 of 69

ViewModel - anularea unui lanț - exemplu

  • În ViewModel
    • funcție care apelează funcția asociată din repo

15 - Compose - WorkManager

56

57 of 69

UI - anularea unui lanț - exemplu

  • În UI
    • folosim funcția din ViewModel

15 - Compose - WorkManager

57

58 of 69

Constrângeri

58

59 of 69

Constrângeri

  • WorkManager oferă suport pentru constrângeri
    • cerințe ce trebuie îndeplinite înainte de rularea unui WorkRequest

  • Exemple de constrângeri:
    • requiresDeviceIdle() - dacă dispozitivul este inactiv
    • requiresStorageNotLow() - dacă spațiul de stocare nu este redus

15 - Compose - WorkManager

59

60 of 69

Repository - constrângere - exemplu

  • Constrângere: nivelul bateriei să nu fie scăzut pentru a rula un WorkRequest specific
  • Adăugăm constrângerea la WorkRequestBuilder

15 - Compose - WorkManager

60

61 of 69

Background Task Inspector

61

62 of 69

Background Task Inspector

  • Tool din Android Studio

  • Vizualizarea, monitorizarea, depanarea Worker-ilor
    • în timp real

  • Dispozitive emulate sau reale

  • API peste 26 (Android 8)

15 - Compose - WorkManager

62

63 of 69

Background Task Inspector

  • Putem vedea Workerii, starea lor, constrângerile
  • Nu poate opri Workerii din execuție

63

64 of 69

Background Task Inspector

  • Putem vedea dependențele între Workeri sub formă de graf

64

65 of 69

Bibliografie

15 - Compose - WorkManager

65

66 of 69

Bibliografie

15 - Compose - WorkManager

66

67 of 69

Bibliografie

15 - Compose - WorkManager

67

68 of 69

Bibliografie

68

69 of 69

Cuvinte cheie

  • WorkManager
  • Worker / CoroutineWorker
  • WorkRequest
  • Data
  • Lanț de WorkRequest-uri
  • Lanț unic
  • LiveData
  • Flow / StateFlow
  • Constrângeri

15 - Compose - WorkManager

69

69