Uvod u MVVM arhitekturu

MVVM pattern

MVVM (Model-View-ViewModel) je patern koji razdvaja aplikaciju na više komponenti tako da svaka komponenta ima svoje specifične odgovornosti. Pri korišćenju MVVM paterna “kod” aplikacije je razdvojen na tri dela: View, ViewModel i Model, ova arhitektura je preporučena od strane Google-a kao jedan od najboljih načina strukture koda Android aplikacija. Osnovne karakteristike ovog pristupa su:

  • Komponente korisničkog interfejsa UI se drže podalje od poslovne logike
  • Poslovna logika se drži podalje od operacija vezanih za bazu podataka
  • Manje briga sa “lifecycle” događajima (jer se koriste komponente koje su svesne životnog ciklusa drugih komponenti aplikacije)

MVVM diagram

Preporučeni način za komunikaciju izmedju dva susedna sloja je tzv. “Observer patern” (najčešće koristeći “LiveData” ili neku drugu biblioteku).

observe_patern

LiveData

LiveData je tzv. observable data holder zadužen da čuva tj. ima referencu na niži nivo o podacima i da obavesti sve zainteresovane posmatrače ako dodje do promena. Najvažnija karakteristika LiveData je ta što je svestan životnog ciklusa drugih komponenti aplikacije (aktivnosti, fragmenti…). Upravo zbog te karakteristike LiveData ažurira samo observers (posmatrače) koji su u aktivnom stanju (ako je životni ciklus u STARTED ili RESUMED stanju). LiveData samo obaveštava aktivne posmatrače o ažuriranjima, dok neaktivni (destroyed) posmatrači iako registrovani nisu obavešteni o promenama. Kada koristimo LiveData ne treba brinemo kada se završava (destroy) životni ciklus aktivnosti/fragmenta jer se automatski odjavjljuju čim komponenta završi svoj životni ciklus. Više o LiveData pročitajte u članku: “LiveData & MVVM”

Kao što se vidi na slici ispod, jedan sloj ima referencu samo na sloj ispod njega, ali ne i obratno (tj. sloj nema pojama o komoponenti iznad), pa tako: “View” zavisi od “ViewModel”-a, a “ViewModel” zavisi od “Model-a”

mvvm diagram

MVVM je sličan MVP pattern-u, stom razlikom što kod “MVP” pattern-a “Presenter” ima referencu na “View” i direktno “govori” View-u koje podatke i promene da prikaže, dok “ViewModel” ne drži referencu na View i ne može da direktno utiče na “View”.

“View”

“View” sekcija (sadrži klase: Aktivnosti i Fragmente) je zadužena za prikaz interfejsa i prihvatanje akcija korisnika. Pošto ova sekcija ima referencu na nivo ispod (ViewModel) ona može da osluškuje promene u ViewModelu, i ako ima promena da pozove neku metodu iz ViewModel-a i preuzme te nove podatke. Zbog ove odgovornosti je potrebno da u okviru View-a postoji deo koda sa kojim se “View” prijavljuje da posmatra dogadjaje koje emituje “ViewModel” (streams of events). Pročitajte više o tome u članku: “LiveData & MVVM”

“Keep the logic in Activities and Fragments to a minimum”

Nakon preuzimanja novih podataka “View” ima obavezu da ažurira prikaz koji vidi krajnji korisnik. Još jedna bitna odlika vezana za “View” kada se koristi MVVM arhitektura je da aktivnosti ili fragmenti više ne moraju imati odgovornost za čuvanje stanja jer je tu obavezu preuzeo “ViewModel”

Napomena:
Conditional statements i loops ne treba da se nalaze u aktivnostima ili fragmentima taj deo treba da se nalazi u ViewModelima ili drugim slojevima aplikacije.

“ViewModel”

“ViewModel” takodje ima dve uloge:

  1. Pošto ima referencu na nivo ispod (Model klase), ViewModel može da osluškuje “vesti” o promenama koje emituje Model.
  2. Kada dodje do promena podataka da (nakon obrade podataka) dalje emituje vesti o tome (ViewModel ne interesuje ko će te informaciji o promenama iskoristi, njemu je samo važno da obaveštava o tome).

    NAPOMENA: “ViewModel” ne drži referencu na View te stoga ne može direktno da utiče na “View”

“Instead of pushing data to the UI, let the UI observe changes to it.”

“ViewModel” je klasa koja je dizajnirana da preživi konfiguracione promene (npr. rotacija ekrana) i sačuva informacije koje su neophodne za View (a to znači da naša aktivnost/fragment više ne moraju imati tu odgovornost). Kada se “View” (tj. fragmentom/aktivnosti) uništi promenom konfiguracije ili rotacije uredjaja, njegov “ViewModel” neće biti uništen a nova instanca View-a će se ponovo povezati na isti “ViewModel”.

NAPOMENA:
Iako ViewModel može da preživi promenu konfiguracije (npr. rotacija ekrana), ipak ne živi beskonačno!
ViewModel ne može da preživi ubijanje aktivnosti od strane opertativnog sistema ili korisnika (npr. “back” dugme). Ako android uništi aplikaciju/aktivnost to će uništiti i ViewModel, a onda samo onSavedInstance() ili baza podataka pružaju mehanizam za čuvanje podataka. Što vodi do zaključka da ViewModel klasa nije zamena za “trajno čuvanje podataka” ili čuvanje podataka koristeći onSaveInstanceState()!

“Avoid references to Views in ViewModels.”

“ViewModel” se takodje često koristi kao komunikacioni sloj između fragmenata u okviru jedne aktivnosti. Svaki Fragment ima pristup “ViewModel-u” preko svoje Aktivnosti što omogućava komunikaciju između Fragmenta bez direktnog medjusobnog kontakta. Oba fragmenta mogu pristupiti VievModel-u putem njihove aktivnosti u kojoj se nalaze. Prvi fragment može ažurirati neke podatke pozivajući metodu iz ViewModel-a, nakon čega će ViewModel te promene emitovati (pomoću LiveData), a ukoliko drugi fragment “posmatra” LiveData on će to opaziti, i na taj način dobiti informaciju iz prvog fragmenta.
Više o ovome u članku: “Komunikacija izmedju fragmenata koristeći ViewModel i LiveData”

NAPOMENA:
Preporuka je da “ViewModel” klase ne koriste android framework klase tj. da nema android.* imports u okviru klase.

“Don’t let ViewModels know about Android framework classes”

Prethodna izjava da “ViewModel ne treba da ima referencu na View” je iz razloga što to može izazvati probleme u slučaju da se View instanca uništi (zbog rotacije…) u trenuku kada ViewModel ima i dalje referencu na nju (npr. kada “ViewModel” traži online podatke sa mreže). Upravo zbog ove preporuke a da bi omogućili View-u pristup podacima se koristi Observer patern. ViewModel emituje dogadjaje vezane za podatke (najčešće koristeći LiveData biblioteku), a View se prijavi da posmatra te promene.

“Instead of pushing data to the UI, let the UI observe changes to it.”

Kada se kreira ViewModel klasa potrebno je da ekstendujemo ili “ViewModel” ili “AndroidViewModel” klasu (koristite “AndroidViewModel” ako vam treba i application context u okviru ViewModel-a).

“Model”

“Model” sekcija sadrži klase zadužene za direktan pristup podacima sa ciljem da abstrahuje i pojednostavi pristup tim podacima. Pošto često podaci mogu dolaziti iz više izvora (baza, webservice…), iz tog razloga je korisno da iz ViewModel-a imamo samo jednu ulaznu tačku ka izvorima podataka, a ta tačka se zove “Repository” čija je uloga da apstrahuje više izvora podataka u jedan API. Repository nije neka specijalna arhitektonska komponenta već obična klasa koja ima pristup svim izvorima podataka. ViewModel praktično ima direktan pristup samo do repository preko tog API-ja i na jednostavan nači kad god mu zatreba može da dobija podatke (ne znajući iz kog izvora stvarno potiču ti podaci: web ili baza podataka i na koji način je došlo do njih).

“Data repository as the single-point entry to your data”

Repository diagram


Popunjavanje ViewPager-a koristeći PagerAdapter

Šta je ViewPager?

ViewPager je Layout Manager koji omogućava korisniku da se kreće kroz stranice podataka pokretima na levo ili desno, dok se pri promeni stranica izvršava i ugradjena animacija. Najčešće su te strane sa podacima su fragmenti, mada mogu da budu i nešto drugo kao npr. slike koje se slajduju…
ViewPager se popunjava podacima koristeći svoj adapter tzv. PagerAdapter. Ukoliko koristimo fragmente za njih se koriste specijalizovani adapteri:

  • FragmentPagerAdapter (kešira fragmente pa se koristi za manji broj fragmenata obično u saradnji sa tab-ovima)
  • FragmentStatePagerAdapter (koristi se kod većeg broja fragmenata)

ViewPager se ubacuje u layout kao kontejner u kome će se prikazivati sve stranice.

TabLayout

ViewPager se najčešće integriše u layout zajedno sa TabLayout-om, koji predstavlja navigaciju.

tabLayout

Pre ubacivanja TabLayouta potrebno je da se doda novi dependecies “design”:
implementation 'com.android.support:design:28.0.0'

Tek nakon ubacivanja dependencies možemo da dodamo novi widget TabLayout sa TabItem-ima:

Korišćenje PagerAdapter-a

PagerAdapter se koristi da popuni ViewPager kontejner sa odgovarajućim stranicama. U ovom članku stranice će predstavljati fragmenti, te je pre svega potrebno kreirati tri različita fragmenta (FragmentA, FragmentB, FragmentC).

a) Kreiranje adapter klase

Kada pravimo naš custom adapter potrebno je da ekstendujemo našu klasu ili sa PagerAdapter klasom ili sa jednom od već pomenutih klasa: FragmentPagerAdapter ili FragmentStatePagerAdapter koje se koriste za rad sa fragmenti-ma.

MojViewPagerAdapter.java

Pošto u ovome primeru imamo mali broj fragmenta, koristićemo klasu koja kešira fragmente (brza ali dobra samo sa malim brojem fragmenata):

Čim ekstendujemo klasu AndroidStudio zahteva da se generiše konstruktor i implementriraju dve metode:

Metoda getItem(int position) vraća odgovarajući fragment u zavisnosti od pozicije (tj. int parametra koji predstavlja poziciju fragmenta):

Metoda getCount() je planirana da vraća ukupan broj stranica koji treba da budu prikazani:

b) Instanciranje adaptera u aktivnosti

Kada smo napravili Adpter klasu potrebno je da u okviru aktivnosti napravimo njegovu instancu. Nova instanca se pravi koristeći konstruktorsku funkciju kojoj se prosledjuje instanca framentManager-a:

Aktivnost

c) Povezivanje adaptera i ViewPager kontejnera

Kada targetiramo ViewPager onda jednostavno možemo da ga povežemo sa adapterom koristeći metodu setAdapter():

Aktivnost

d) Sinhronizovanje ViewPager-a i TabLayout-a

Već sada je ViewPagerAdapter popunio ViewPager sa fragmentima, tako da možemo slajdovati fragmenate jednostavnim “swipe” pokretima levo ili desno. Medjutim iako se uspešno menjaju fragmenti, ne dolazi do promena kod TabLayout-a. Potrebno je povezati swipe dogadjaj sa promenom u Tablayoutu i obrnuto. Za interakciju izmedju ova dva View-a se koriste listeneri i tzv. listener pattern (više o listener pattern-u možete pogledati ovde).

ViewPager listener

ViewPager ima interfejs “ViewPager.OnPageChangeListener” čije se callback metode (onPageScrollStateChanged, onPageScrolled i onPageSelected) pozivaju kada dodje do promene strane slajdovanjem u ViewPager-u. Da bi listener radio svoj posao potrebno je da setujemo osluškivač dogadjaja, što se vrši sa setter metodom addOnPageChangeListener(). Ovu metodu poziva objekat koji implementira ovaj interfejs a to je bilo koji ViewPager.

Objekat koji je zainteresovan da postane osluškivač promene stranica u ViewPager-u je ustvari TabLayout objekat. Za ovu ulogu u androidu postoji specifična klasa koja implementira sve callback metode i tako sinhronizuje TabLayout nakon promene stranice u ViewPager-u pod nazivom “TabLayout.TabLayoutOnPageChangeListener”.
Ova klasa u konstruktoru prihvata TabLayout kao parametar, pa ćemo nju iskoristiti da napravimo “on the fly” kao anonimni objekat :

TabLayout listener

TabLayout ima interfejs “TabLayout.OnTabSelectedListener” čije se callback metode ( onTabSelected(), onTabReselected(), onTabUnselected()) pozivaju kada dodje do promene u selekciji tabova.
Da bi listener patern radio svoj posao potrebno je da setujemo osluškivača ovih dogadjaja, a to se vrši sa setter metodom addOnTabSelectedListener(). Ovu metodu poziva objekat koji implementira ovaj interfejs a to je bilo koji tabLayout.

Objekat koji je zainteresovan da postane osluškivač promene tabova je ustvari ViewPager objekat. Za ovu ulogu u androidu postoji specifična klasa koja implementira sve callback metode i tako sinhronizuje ViewPager nakon promene tabova pod nazivom “TabLayout.OnTabSelectedListener”.
Ova klasa u konstruktoru prihvata ViewPager kao parametar, pa ćemo nju iskoristiti da napravimo “on the fly” kao anonimni objekat :

Pogledajte ceo kod




NAPOMENA:
Za brzu integraciju TabLayout-a i ViewPager-a možemo koristi već predvidjenu “Tabbed Activity” sa ugradjenom navigacijom (Actions bar Tabs with ViewPager).

Viewpager boilerplate

Sa ovom aktivnosti dolazi već odradjen veći deo posla, pa uz par manjih izmena sve može da se pripremi veoma brzo.
Pre svega potrebno je napraviti par fragment layouta (npr. fragment_a.xml, fragment_b.xml, fragment_c.xml) i prilagoditi getItem() metodu:

Deo koji sa sigurnošću možemo da obrišemo je vezan za privremeni placeholder fragment :

Ukoliko nam ne treba FloatingActionButton onda možemo obrisati njegov deo koda:

Ako nam ne treba “options meni” onda možemo da obrišemo deo vezan za to:

Ceo kod “pročišćene” aktivnosti možete da pogledate ovde.

×

Aktivnost


Adapter za RecyclerView

Uvod

RecyclerView

Adapter je most između izvora podataka (Array objekat, List objekat…) i korisničkog interfejsa. Uloga adapter objekta (implementira Adapter interfejs) je da čita podatke iz različitih izvora podataka, i na osnovu njih popunjava View objekte članove nekog ViewGroup-a.
Do sada smo koristili ListView ali preporuka je da se umesto listView-a koristi RecyclerView, a naručito kada god imate kolekcije podataka čiji se elementi menjaju tokom izvršavanja aplikacije kao reakcija na aktivnost korisnika ili mrežnih dogadjaja. RecyclerView je naslednik ListView i GridView-a, i namenjen je da efikasno renderuje adapter-based view.
RecyclerView ima svoj adapter “RecyclerView.adapter” koji implementira ViewHolder patern, integriše “convertView” i ima pripremljene metode koji su zamena za sve što smo uradili kod “CustomArrayAdapter-a” a poboljšava efikasnost prikaza pri skrolovanju liste.

Postupak kreiranja RecyclerView.Adapter-a

a) Podaci za listu

U ovome primeru jedan član liste prihvata više podataka, stoga je potrebno napraviti klasu koja će da bude “modla” za kreiranje objekata koji čuvaju podatke jednog člana nekog AdapterView-a:

Definisali smo da se kroz konstruktor ubacuju podaci za svaki član liste, te stoga možemo da generišemo pocetnu listu podataka u okviru Aktivnosti (Fragmenta):

b) Kreiranje AdapterView-a

Potrebno je u sklopu layout-a Aktivnosti (fragmenta) definisati mesto gde će biti smeštena lista podataka. Ovaj deo je sličan kao kod običnog adaptera, stim što se kreira se umesto ListView koristi RecyclerView.

c) Kreiranje custom layout-a za jedan elementa liste

custom-adapter-row

Sada je potrebno napraviti custom layout, koji će da ih prikaže više podataka nemenjenih za jedan element liste. Ovaj deo nije bio potreban kod “običnog” adaptera ali je sada potreban zbog viška podataka, jer ne možemo da koristimo već predefinisane androidove layout-e koji prihvataju samo jedan podatak. U ovome primeru imamo za svaki row po tri podatka: slika i dva teksta.

NAPOMENA:
Klikom na row element RecyclerView-a se ne dešava očekivani “ripple” efekat. Da bi se takav efekat aktivirao potrebno je root View-u row layout-a dodati atribut: android:background=”?android:attr/selectableItemBackground”

Pogledajte ceo primer koda ovde.

d) Kreiranje Custom Adapter klase

Za kreiranja klase customAdaptera je potrebno da naša klasa ekstenduje RecyclerView.Adapter klasu. Da bi smo definisali koga tipa su podaci u adapter-u, potrebno je pre svega da unutar adaptera napravimo unutašnju statičnu ViewHolder klasu koja ekstenduje RecyclerView.ViewHolder klasu. Kada smo kreirali statičnu ViewHolder potrebnoje da unutar nje definišemo njenu spostvenu konstruktor metodu.

Sada možemo da okviru uglasitih zagrada ubacimo naziv našeg ViewHolder-a i tako definišemo koji tip prihvata adapter.

Tek nakon ovoga možemo da implementiramo sve metode koje zahteva RecyclerView.Adapter klasa, i da definišemo konstruktor koji prihvata ArrayList-u podataka kao parametar. Posle svega klasa bi trebalo ovako da izgleda:

Sada kada smo napravili kostur potrebno je definišemo i detalje.

Definisanje ViewHolder klase

Kao prvo potrebno je da u okviru ViewHolder klase targetiramo sve sub elemente iz example_item.XML layout-a:

Kreiranje instance ViewHolder-a

Prvo je potrebno izvršimo parsiranje row layout-a u View objekat, a to se vrši u okviru onCreateViewHolder() metode, pa se zatim taj objekat prosledi kao parametar konstruktoru nove ViewHolder instance:

Ubacivanje podataka u listu se vrši u sklopu onBindViewHolder() metode:

Definisanje veličine liste

Veličina liste se definiš u okviru getItemCount()

Cela Custom Adapter klasa sada izgleda ovako.

e) Ubacivanje RecyclerView-a u Aktivnost

Neophodne stvari koji definišu RecyclerView su:

  • View koji predstavlja RecylerView
  • RecyclerView.LayoutManager koji se koristi da definiše raspored elemenata u okviru RecyclerView-a (LinearLayoutManager, GridLayoutManager, StaggeredGridLayoutManager)
  • Adapter koji se koristi za popunjavanje RecyclerView-a

NAPOMENA:
RecyclerView se uglavnom definiše tako da ne zavisi od child elemenata nego uglavnom od parent View-a u kome je smešten, te se njegova visina i šira generalno ne menje tokom vremena. Ali taj podatak moramo da naglasimo da android to ne bi svaki put proveravo kada ubacujemo novi ili izbacujemo već postojeći element. Iz tog razloga da bi poboljšali efiasnost RecyclerVie-a je dobro da definišemo naš RecyclerView kao da je fiksne veličine sa setHasFixedSize(true).

Pre svega je potrebno da definišemo polja koja će biti kasnije dostupna drugim delovima koda:

U sklopu onCreate() metode ćemo setovati napravljena polja:

Ceo kod koji se nalazi u Aktivnosti možete da pogledate ovde.

NAPOMENA
Ukoliko dodajemo ili uklanjamo element iz RecyclerView-a potrebno je posle svakog dodavanja obavestiti sistem da je došlo do promene pozivajući metodu notifyItemInserted(position):

Takodje treba i posle svakog uklanjanja postojećeg elelementa obavestiti sistem da je došlo do promene pozivaju’i metodu notifyItemRemoved(position):

Pa bi metode za ubacivanje i izbacivanje elemenata ovako izgledale:

Sve metode koje mogu da se korisite za obaveštavanje sisteme o premenama pogledajte ovde.

f) Definisanje click listener-a

Zajedno sa standardnim ListView-om stiže i onItemClick interfejs, medjutim toga nema kod RecyclerView-a stoga moramo da kreiramo naš interfejs i customClickListener (pogledaj kako se kreiraju customListener-i ovde).

Kreiranje novog interfejsa

Prvo je potrebno da u sklopu našeg Custom Adapter-a definišemo novi interfejs i jednu callback metodu:

Setter metoda

A zatim je potrebno da definišemo polje i setter metodu, čijim pozivanjem u Aktivnosti definišemo objekat koji osluškuje event:

Okidanje dogadjaja

Potrebno je “okinuti” dogadjaj klikom na neki element RecyclerView liste koji je definisan u ViewHolder klasi, a to ćemo uraditi tako što ćemo pozovati callback metodu našeg interfejsa:

Ovu metodu ćemo da pozovemo u trenutku kada se klikne na element “itemView” (predstavljen kao parametar konstruktorske metode). Iz tog razloga poziv metode će biti smešten u okviru onClick() metode:

Sigurno ste primetili da android studio prijavljuje grešku da ne može da nadje promenjivu listener pošto je naša klasa statična. Da bi smo napravili ovu promenjivu dostupnom, prosledićemo je kao parametar konstruktorskoj funkciji Custom ViewHolder klase. Ubacivanje listenera kao parametra klase je najlakše postići ako se u AndroidStudiu označi listener koji je problem i sa ALT + ENTER pozove pomoćni meni, gde se izabere opcija “Create parameter listener”, nakon čega će Android Studio odraditi sve sam.
Pa bi cela ViewHolder klasa sada ovako izgledala

NAPOMENA:
Pri svakom kliku na neki element liste je potrebno da znamo na koji element je kliknuto, to se jednostavno dobija koristeći metodu getAdapterPosition()

Definisanje objekta koji osluškuje i sadržaja callback metode

U okviru Aktivnosti se nadje objekat koji implementira interfejs a to je u našem slučaju instanca samog adaptera “mAdapter”. Zatim taj objekat poziva setter metodu setOnItemClickListener() da bi kroz prosledjeni parametar definisao objekat koji osluškuje event. U našem slučaju će to da bude anonimni objekat koji implementira interfejs “on the fly”. Akciju koja treba da se izvede nakon okidanja dogadjaja ćemo definisati tako što “pregazimo” (override) njegovu callback metodu onItemClick:

Pogledajte ceo kod Aktivnosti ovde.

Ovo je jednostavniji način i sve se definiše u okviru adapter klase. Prvo je potrebno da naša ViewHolder klasa implementira View.OnClickListener:

A nakon toga će AndroidStudio prijaviti grešku i zahtevati da se implementira i njegova callback metoda onClick(). U okviru ove metode se definiše akcija koja se dešava kada se okine dogadjaj tj. kada se klikne na neki član liste.

Da bi se definisao objekat koji osluškuje potrebno je da ga prosledimo kao parametar setOnClickListener() metodi u okviru ViewHolder konstruktora. U našem slučaju to je sam ViewHolder pa ćemo da prosledimo “this”:

Pa bi ceo ViewHolder ovako izgledao:

×

×

×

×

×

Method Description
notifyItemChanged(int pos) Notify that item at position has changed.
notifyItemInserted(int pos) Notify that item reflected at position has been newly inserted.
notifyItemRemoved(int pos) Notify that items previously located at position has been removed from the data set.
notifyDataSetChanged() Notify that the dataset has changed. Use only as last resort.


Custom ArrayAdapter u Androidu

Uvod

custom-adapter-row

U osnovnoj verziji adaptera se koristi samo jedan String podatak koji se smešta u neki od predefinisanih android layouta sa jednim TextView-om.
Custom adapter se razlikuje od “običnog” po tome što je kod njega jedan element liste predstavljen sa više podataka te je potrebno definisati “složeniji layout” koji bi prihvatio i prikazao te podatke. Takav customLayout row liste može da sadrži više subView-ova i widgeta: sliku, tekstualne podatke (rasporedjene na svojim specifičnim pozicijama)…

Postupak kreiranja Custom ArrayAdapter-a

a) Custom model (prihvata više podataka za jedan član liste)

U ovome primeru jedan član liste sadrži dva podatka, stoga je potrebno napraviti klasu koja će da bude “modla” za kreiranje objekata koji čuvaju podatke jednog člana nekog AdapterView-a:

User.java

Sada u okviru Aktivnosti (Fragmenta) možemo na sledeći način da definišemo inicijalni niz podataka kreirajući nove objekte:

Aktivnost

ili da na osnovu njega generišemo ArrayList User objekata:

User.java

Za generisanje podataka iz JSON-a koristimo sledeći kod koji konvertuje JSON u ArrayList User objekata:

Aktivnost

b) Kreiranje AdapterView-a

Potrebno je u sklopu layout-a Aktivnosti (fragmenta) definisati mesto gde će biti smeštena lista podataka. Ovaj deo je isti kao kod običnog adaptera, kreira se jedan ListView koji će da prihvati po jedan custom row svakog člana.

activity_main.xml

c) Kreiranje custom layout-a za jedan elementa liste

Sada je potrebno napraviti custom layout, koji će se koristiti da prikaže više podataka jednog elementa liste. Ovaj deo nije bio potreban kod “običnog” adaptera ali je sada potreban zbog viška podataka, jer ne možemo da koristimo već predefinisane androidove layout-e koji prihvataju samo jedan podatak.

item_user.xml

d) Kreiranje Custom Adapter klase

U nastavku aplikacije je potrebno da definišemo Adapter klasu na osnovu koje će biti kreirana nova instanca adaptera u sklopu Aktivnosti (Fragmenta). U ovoj klasi treba da bude smešten ceo proces konvertovanja Java objekta u View popunjen podacima.
Pravimo custom adapter tako što kreiramo novu klasu koja ekstenduje ArrayAdapter klasu. Ekstendovanjm klase dobijamo metodu getView() koju možemo da “pregazimo” (override) tako da za svaki element liste uzme podatke iz modela i da sa njima popuni prethodno definisani Custom layout elementa.

NEekonomični ArrayAdapter

Ovo je primer ArrayAdaptera koji troši veliku količinu resursa pri skrolovanju ekrana, što se naručito oseti kod velikih lista podataka.

Sve mane ovog postupka kao i sam poboljšani postupak koji rešava te manjkovasti je prikazan u narednom primeru.

Poboljšani ArrayAdapter (sa primenjenim ViewHolder pattern-om)

Prva stvar na koju trebamo da obratimo pažnju je ta da se u NEekonomičnom primeru pri svakom pozivanju metode getView() kreira novi “rowView”.

Ukoliko naša lista ima veći broj elemenata koji ne mogu da stanu na jedan ekran, pri svakom novom skrolovanju se generišu novi View-i (svaki View je 1-2kB), što stalno povećava opterećenje memorije.

convertView

Upravo zbog ovog problema Android prosledjuje metodi getVIew() parametar “convertView”. Ovaj parametar se koristi da u njega “skladištimo” View (npr. naš jedan rowView), koji ćemo kasnije koristiti iznova i iznova. Da bi smo sprečili “inflating uvek istog XML-a” (koja je skupa operacija) za svaki novi element liste, jednom napravljen View ćemo sačuvati u okviru convertView promenjive.

U prethodnom kodu proveravamo da li već postoji neki sačuvan View u promenjivoj, a ukoliko ga nema, mi ćemo ga kreirati (samo prvi put) kao naš rowView, jer će nam tako definisan convertView biti uvek dostupan kao parametar getView() metode.

ViewHolder pattern

Sledeći problem koji se javlja je učestalo pozivanje metode findViewById() za svaku podstavku (subView) convertView-a. Često pozivanje metoda findViewById() opterećuje sistem i smanjuje performance aplikacije (naručito ako ima mnogo podataka).

Za rešavanje ovoga problema nam u pomoć nam priskače tzv. “ViewHolder pattern” koji se oslanja na to da već koristimo convertView, i da možemo jednostavno da targetiramo sve njegove subView-ove (samo jednom) i tako izbegnemo stalno pozivanje metode findViewById(). Stoga kreiramo jednu unutrašnju statičnu klasu koja se često naziva ViewHolder.

Članove klase možemo da povežemo sa odgovrajućim View-om tako što ćemo pozivati findViewById(), ali uz napomenu da ćemo to uraditi samo jednom i to u trenutku kada prvi put definišemo convertView-a:

U prethodnom primeru smo koristili i metodu setTag() da bi smo sačuvali ViewHolder objekat ako smo ga već jednom napravili i onda smo ga kasnije pozvali metodu getTag() i tako dobili nazad sačuvani objekat.
Korišćenje ViewHolder pattern-a ubrzava populaciju ListView-a, te sa njim dobijamo glatko i brzo učitavanje stavki. Njegova implementacija omogućava da se izbegne korišćenje “skupog” metoda findViewById() u okviru adapter-a. Ceo primer custom adapter-a:

×

×

×

×

×

Method Description
notifyItemChanged(int pos) Notify that item at position has changed.
notifyItemInserted(int pos) Notify that item reflected at position has been newly inserted.
notifyItemRemoved(int pos) Notify that items previously located at position has been removed from the data set.
notifyDataSetChanged() Notify that the dataset has changed. Use only as last resort.


ArrayAdapter (osnovni android adapter)

Uvod

adapter

Adapter je most između izvora podataka (Array objekat, List objekat…) i korisničkog interfejsa. Uloga adapter objekta (implementira Adapter interfejs) je da čita podatke iz različitih izvora podataka, i na osnovu njih popunjava View objekte članove nekog ViewGroup-a.
ViewGroup: ListView, RecyclerView, GridView, Spinner, Gallery čiji sadržaj se popunjava korišćenjem adaptera se nazivaju zajedničkim imenom “AdapterView”.

Korišćenje adaptera kao medijatora izmedju “model-a” (podataka) i View-a, predstavlja varijaciju MVC patern-a pod nazivom MVA (ModelViewAdapter). To znači da Model i View nikada ne komuniciraju međusobno, već komuniciraju preko Adapter-a (koji je zapravo samo “event-driven Controller”). Prednost ovog patern-a je da View ne mora da zna ništa o modelu, te se na taj način postiže bolje razdvajanje odgovornosti.

adapter hijerarhija

Postoji više različitih adaptera, i njihov izbor zavisi od AdapterView-a za koji se koristi. Najjednostavniji adapter je “ArrayAdapter”, koji uzima podatke u vidu “ArrayList” i sa njima popunjava sadržaj View elementa koji su deo kontejner-a (“ListView”, GridView, Spinner). Za “RecyclerView” se koristi njegov specijalizovani “RecyclerView.adapter”, dok se za “ViewPager” koristi “PagerAdapter”.

Postupak kreiranja ArrayAdapter-a

Osnovni defaultni Android Adapter predstavlja “ArrayAdapter” koji koristi neki od default-nih layout-a koji su ugradjeni u Android.

a) Definisanje podataka koji treba da se prikažu

Prvo su nam potrebni podaci koji bi se smeštali u neki View, ArrayAdapter ima različite konstruktore, te stoga može da primi različite tipove podataka:


ili

Desni klik na res > values zatim se izabere New > Values resource file nakon čega se izabere ime fajla npr. “imena_ljudi_array”

Ovome se kasnije možemo pristupiti:

NAPOMENA:
Kada su podaci statična lista onda nije neophodno koristiti adaptere, možemo povezati podatke (resourse string-array) i View jednostavanije i bez korišćenja adaptera samo koristeći View XML atribut: android:entries.

b) Kreiranje AdapterView-a koji treba da prihvati podatke

Već je pomenuto da je AdapterView ustvari neki ViewGroup (ListView, RecyclerView…) čiji se sadržaj popunjava podacima uz pomoć adaptera, te je potrebno definisati neki AdapterView u okviru XML-a.

Primer

NAPOMENA:
Ako ekstendujemo aktivnost sa ListActivity (ili kod fragmenata sa ListFragment), i tada ne moramo da definišemo nikakav layout, jer u tom slučaju aktivnost (fragment) već sadrži podrazumevano kao root view jedan ListView koji može da primi podatke. Pored toga ListActivity i ListFragment nam takođe dozvoljavaju da “pregazimo” (override) metodu onListItemClick() i tako definišemo akciju koja će da se izvrši klikom na neki od članova liste. Pogledajte objedinjen kod iz primera ovde.

c) Kreiranje instance adaptera

Postoji više “ArrayAdapter” konstruktora koji mogu da prihvate različite tipove podataka, mada generalizovani konstruktor bi mogao ovako da izgleda:

Legenda:

simple

×

simple

  • Context
  • Neki predefinisani layout koji dolazi sa androidom (npr. “R.layout.simple_list_item_1” pogledaj sliku) ili neki custom layout. Listu predefinisanih layout-a možete da pogledate ovde.
  • Podaci koji se ubacuju da (lista, niz….)
Primer

ArrayAdapter zahteva da se tip elementa niza deklariše kao View (u ovom primeru kao String).

NAPOMENA:
ArrayAdapter po default-u u pozadini za svaku stavku niza poziva metodu “toString()” i tako generiše TextView u koji stavlja sadržaj člana niza. Ako želimo komplikovaniji layout koji ima više elemenata (npr. ImageView), onda je potrebno da napravimo custom ArrayAdapter što će biti objašnjeno u nekom drugom članku.

Pored ovog načina za kreiranje adaptera gde se koristi konstruktor metoda, za kreiranje adaptera možemo da koristimo i njegovu
metodu createFromResource(). Pogledajte sve metode ArrayAdapter-a i RecyclerView.adapter-a ovde.

d) Povezivanje adapter instance sa AdapterView-om

Povezivanje adaptera sa odgovarajućim View-om se vrši metodom setAdapter() (setListAdapter() kod “ListActivity”).

Sa ovim smo omogućili da adapter pokupljene podatke iz nekog resursa i ubaci kao String u svaki element AdapterView-a koji je stilizovan sa izabranim predefinisanim android layoutom (u primeru: “android.R.layout.simple_list_item_1”)

f) Definisanje click listener-a

Da bi se aktivirala akcija nakon klika na neki od elemenata liste je potrebno implementirati interfejs AdapterView.OnItemClickListener i override-ovati onItemClick() callback metodu. Metoda prihvata četri parametra od kojih prvi predstavlja parent view (u ovom primeru ListView).

Pogledajte objedinjen kod iz primera ovde.

×

Lista predefinisanih layout-a u sklopu androida.
  • activity_list_item
  • browser_link_context_header
  • expandable_list_content
  • list_content
  • preference_category
  • select_dialog_item
  • select_dialog_multichoice
  • select_dialog_singlechoice
  • simple_dropdown_item_1line
  • simple_expandable_list_item_1
  • simple_expandable_list_item_2
  • simple_gallery_item
  • simple_list_item_1
  • simple_list_item_2
  • simple_list_item_activated_1
  • simple_list_item_activated_2
  • simple_list_item_checked
  • simple_list_item_multiple_choice
  • simple_list_item_single_choice
  • simple_selectable_list_item
  • simple_spinner_dropdown_item
  • simple_spinner_item
  • test_list_item
  • two_line_list_item

Ovim layoutima se pristupa sa R.layout.nazivLayouta a njihov XML može da se pogleda ovde.

×


Konvertovanje XML resursa u View objekat (“Layout Inflating”)

Uvod

“Layout inflation” je termin koji označava postupak sa kojim se XML resurs parsira i konvertuje u View objekat.
Aktivnost ima svoju metodu setContentView() sa kojom inflate-uje svoj root view:

Primer

Ali konvertovanje drugih layout-a u View objekt (čime omogućavamo da elementi layout-a budu dostupni u kodu aktivnosti) možemo da uradimo na sledeće načine:

  1. Koristići inflate() metodu inflater objekta
  2. Koristeći statičnu metodu inflate() View klase

Metoda LayoutInflater.inflate()

Ovaj postupak se sastoji iz dva koraka:

  1. Pravljenje Inflater objekat
  2. Pozivanje inflate() metode

Pravljenje Inflater objekta

U aktivnosti

a) u okviru onCreate()

Ako smo u aktivnosti u sklopu onCreate() metode možemo da pristupimo ovom objektu jednostavno jer je dat kao parametrar metode onCreate():

b) van onCreate() metode

Medjutim ako smo van onCreate() metode moramo mu pristipiti na drugačiji način koristeći getLayoutInflater() metodu:

Van aktivnosti

a) Preko aktivnosti

Pa čak ako smo i van aktivnosti možemo mu pristupiti pozivajući aktivnost:

b) Preko context-a

Koristeći metodu context.getSystemService(Class)

Primer

ili na drugi način koristeći from() metod:

Pozivanje inflate() metode

Pozivanjem “inflate()” metode se vrši konverzija:

Legenda:
  • resource int: ID resursa
  • root ViewGroup: Opciona vrednost koja predstavlja neki View group koji će da bude roditelj ovom view-u (ako je treći parametar “attachToRoot” setovan na true). Ova vrednost može pri pozivu funkcije da bude null!
  • attachToRoot boolean: Kroz ovaj parametar se definiše šta će biti vraćeno iz metode. Ako je attachToRoot postavljen na true, onda je layout file navedena u prvom parametru inflate-ovana i priključena na ViewGroup definisan u drugom parametru, pa tada metod vraća ovaj kombinovani prikaz, sa ViewGroup kao root. Kada je attachToRoot false, layout file iz prvog parametra se inflate i vraća kao prikaz.
Primer

U ovome primeru treći parametar je “false” pa naš view nije ubačen u parent view (ovde container). Ako želimo da view bude ubačen u neki parent view potrebno je da stavimo “true” ili da to naknadno uradimo sa dodatnim kodom koristeć metodu addView().

Primer

NAPOMENA:
Postoji verzija i sa dva parametra (treći parametar je uvek true)

Statična metoda inflate() View klase

Za istu namenu može da se koristi i STATIČNA metoda View objekta inflate(). Ona u pozadini takodje koristi wrap-ovani inflater objekat čiji kod izgleda ovako:

Primer

Ova statićna metoda se najčešće koristi kod slučaja kada se definiše custom VIew:

Što bi bilo poptuno jednako sledećem kodu uradjenom preko inflator objekta:

Jedna od razlika je što u ovoj statičnoj metodi nemamo opciju sa tri parametra (to znači da je treći parametar uvek true)


Fragment u okviru androida

Uvod

fragment

Fragment je modularni deo aktivnosti, koji ima svoj životni ciklus, prima sopstvene ulazne događaje, možete ga dodati ili ukloniti dok se aktivnost pokreće. Fragment objedinje View i logiku tako da se može jednostavno višekratno koristiti unutar jedne ili više različitih aktivnosti. U aktivnostima može biti više od jednog fragmenta tako da svaki fragment može da predstavlja neki View unutar jedne aktivnosti.
Prednost arhitekture koja koristi fragmenate je što nam fragmenti omogućavaju ponovnu upotrebu koda, sa jednostavnim postupkom pravljenja različitih prikaza za tablete (landscape) i mobilne uredjaje.
Komunikacija izmedju dva fragmenata je prilično komplikovana i može izvesti na dva načina:

Fragmenat vs. Aktivnost

U aplikacijama koje koriste fragmente deo zaduženja aktivnosti se delegira u fragmente, pa bi prema toj podeli zaduženja koja ostaju u aktivnosti bi bila sledeća:

  • Da sadrži navigaciju do drugih aktivnosti putem intent-a ili navigacijske komponente (“NavigationDrawer”,“ViewPager”…)
  • Da skriva i prikazuje fragmenate (pomoću menadžera fragmenata)
  • Da prima podatake iz drugih aktivnosti (intent)
  • Da kumunicira sa fragmentima i posreduje komunikaciji između njih

Dok fragmenti preuzimaju obavezu da:

  • Prikazuju odgovarajući sadržaj
  • Event handling
  • Pokretanje network request-a
  • Preuzimanje i čuvanje podataka

Životni ciklus fragmenta direktno je pod uticajem životnog ciklusa aktivnosti. Kada je aktivnost pauzirana, onda su i svi su fragmenti u njoj pauzirani, a kada je aktivnost uništena, onda su i svi njeni fragmenti uništeni. Međutim, dok je aktivnost “živa”, možemo manipulisati sa svakim fragmentom nezavisno. Detaljan prikaz lifecycle fragmenta i aktivnosti možete pogledati ovde.

fragment lifecycle

NAPOMENA:
Kada implementirate neku metodu životnog ciklusa fragmenta uvek treba da pozovete nadklasu (npr. super.onStart();):

Primer

Kreiranje fragmenta

Extendovanje fragment klase

Kreiranje fragmenta se sastoji iz kreiranja odgovarajuće klase zadužene za logiku i pridodajući joj odgovarajući layout. Klasa zadužena za logiku mora da extenduje neku od sledećih klasa:

  • Fragment je glavna klasa dok su ostale njegove podklase (pogledajte kako izgleda boilerplate kod koji generiše android studio).
  • DialogFragment – Fragment koji prikazuje prozor za dijalog, koji lebdi na vrhu prozora njegove aktivnosti. Ovo se obično koristi za prikazivanje dijaloga upozorenja, dijaloga za potvrdu ili za traženje informacija od korisnika u okviru bez potrebe za prebacivanjem na drugu aktivnost, dozvoljavajući korisniku da se vrati na prethodni fragment.
  • PreferenceFragmentCompat se koristi za kreiranje settings liste za našu aplikaciju odkle korisnici imaju mogućnost da promene funkcionalnost i ponašanje aplikacije. (pročitajte više u dokumentaciji).
  • ListFragment se koristi za prikazivanje liste nekih podataka i ima već implementiran event listener na clik nekog člana iz liste, tako da je potrebno samo da definišemo metod onListItemClick() (primer).

Povezivanje fragmenta sa njegovim layout-om

Da biste obezbedili layout za fragment, morate da implementirate “onCreateView()” metodu, koju Android poziva kada dođe vreme da fragment iscrta svoj layout. Implementacija ovog metoda mora da vrati prikaz koji je root layout vašeg fragmenta. Povezivanje fragment klase i njenog view-a se vrši korišćenjenm inflate() metode u sklopu lifecycle metode onCreateViewa:

Pogledajte više o postupku ubacivanja view-a iz xml-a u klasu “Layout inflation” u članku “Konvertovanje XML resursa u View objekat”.

Pored ovog načina možemo da kreiramo fragment na osnovu androidovog template-a pod nazivom Fragment(Blank) u čijem sklopu dolazi dosta pripremljeno boilerplate koda. Više o ovome pogledajte ovde.

Targetiranje elemenata u okviru layout-a

Da bi u okviru fragmenta mogli da koristimo metodu findViewById() potrebno je da prvo targetiramo njegov layout. To možemo uraditi na dva načina u zavisnosti gde nam u kodu treba:

  1. U okviru metoda onCreate() i onCreateView() prvo je potrebno da inflate-ujemo layout, nakon čega možemo da koristimo metodu findViewById():

    Fragment za razliku od aktivnosti nije podklasa klase Context, što znači da nema pristup globalnim informacijama o okruženju aplikacije. To znači da fragment ne može da koristi this za dobijanje context-a. Iz tog razloga u sklopu onCreateView() metode je kao parametar prosledjen objekat LayoutInflater koji ima pristup kontekstu, te stoga ako nam je potreban context možemo njega da iskoristimo inflater.getContext().

  2. U okviru metode onViewCreated() targetiramo fragmentov layout sa metodom getView(). Ova metoda je dostupna kada ekstendujemo našu klasu sa klasom Fragment i može da se pozove samo nakon kreiranja view-a, te je stoga ne možemo koristiti unutar onCreate() ili onCreateView() metode.

Embendovanje fragmenta u aktivnostima

Aktvnost koja sadrži fragment mora da ekstenduje ili FragmentActivity ili njenu potklasu AppCompatActivity. Fragmente u aktivnost možemo da dodamo na dva načina:

a) Statično ubacivanje fragmenta direktno u layout aktivnosti

Statično ubacivanje fragmenta u aktivnost podrazumeva da se fragment ubaci u layout aktivnosti kao view.

Fragment ubačen na ovaj način mora da ima definisan id u okviru XML-a, jer se preko njega targetira u okviru aktivnosti koristeći metodu findFragmentById():

Kada imamo referencu na fragment onda možemo da pozivamo njegove public metode ili properties-e.

b) Programirano ubacivanje fragmenta u aktivnost

Za programirano ubacivanje fragmenta u aktivnost je postupak sledeći:

1.) Kreiranje fragment kontejnera u XML-u

Ako vaša aktivnost dozvoljava da se fragmenti uklone i zamene, trebalo bi da dodate početni fragment (tzv. fragmentKontejner) u “onCreate()”. Taj kontejner se koristi da u njega možemo kasnije da ubacimo neki drugi fragment.

2.) Kreiranje fragmentManager-a

Fragment manager se instancira pozivajući metodu getSupportFragmentManager()

Fragment manager

Fragment manager je objekat koji zadužen za rad sa fragmentima za navigaciju izmedju njih kao i referenciranje fragmenta u okviru aktivnosti:

Metod Opis
addOnBackStackChangedListener Dodaje listener kada dodje do promene back stack-a (više o ovome pogledajte ovde)
beginTransaction() Kreira novu transakciju.
findFragmentById(int id) Nalazi fragment koji je inflated-ovan direktno u XML layout aktivnosti.
findFragmentByTag(String tag) Nalazi fragment preko tag-a
popBackStack() Uklanja fragment sa backstack-a.
executePendingTransactions() Forsira izvršenje transakcije.
3.) Kreiranje instance fragmenta

Kreiranje instance fragmenta je standardno korišćenjem new operatora:

Ukoliko želimo da prosledimo i neke podatke pri instanciranju onda oni moraju biti definisani u okviru konstruktorske metode:

Pa ih onda prosledjujemo kao parametar:

Preporuka je da se koristi tzv. newInstance pristup, kada se u okviru fragmenta definiše factory metoda koja služi za instaciranje fragmenta:

Kasnije u fragmentu okviru metode onCreate() možemo da zatražimo podatke generisane pri instanciranju fragmenta upravo preko naziva argumenta:

Drugi argument u get-eru je defaultna vrednost u slučaju da ne nadje argument.

4.) Izvršavanje neke od transakcija sa fragmentima (add, remove, replace)

Potoji API za rad sa fragmentima u aktivnosti (add, remove, or replace a fragment) i zove se FragmentTransaction. Instancu FragmentTransaction dobijamo koristeći fragmentManager.

Jedna od metoda koju može da izvrši FragmentTransaction je i add(). Ova metoda služi za ubacivanje našeg fragmenta u sklop nekog ViewGroup-a (kontejner). Taj ViewGroup se definiše kroz prvi parametar, dok se kroz drugi parametar definiše fragment koji dodajemo, a kroz treći parametar definišemo TAG koji će da obeleži dodati fragment (na osnovu taga kasnije možemo da ga targetiramo sa fragmentManager.findFragmentByTag(“TAGFRAGMENTA”);).

Da bi se klikom na “back” dugme vratili na prethodno stanje (tj. da ne bi smo zatvorili aktivnost što je podrazumevano) potrebno je da dadamo naš fragment na stack tzv. “backStack” sa metodom addToBackStack().

Po definisanju svih naredbi (može da ih bude više od jedne) je potrebno potvrditi akciju (commit-ovati), nakon čega će one biti i stvarno izvršene:

Akcije koje smo commit-ovali se ne izvršavaju odmah već ih stavljaju na čekanje za izvršavanje na glavnoj niti (main thread). Akcije će se izvršiti tek kada nit bude spremna. Pogledajte primere transakcija ovde.

Targetiranje fragmenta iz aktivnosti

Za targetiranje fragmenta u okviru aktivnost koji je ubačen na ovaj način se koristi metoda findFragmentByTag() kojoj se prosledjuje parametar TAG (definisan kroz treći parametar metode replace()).

Navigacija izmedju fragmenata

Navigacija izmedju fragmenata može biti definisana u sklopu aktivnosti koristeći samo FragmentManager, medjutim ništa nas ne sprečava da koristimo neki od sledećih pristupa:

  • TabLayout (tabovi, pogledajte više u članku)
  • Fragment Navigation Drawer (meni sa strane pogledajte više u članaku)
  • ViewPager (prebacivanje slajdovanjem izmedju fragmenata, pogledajte više u članku)

×

Kreiranje od pripremljnog boilerplate “Fragment(Blank)”

Factory statička metoda newInstance() se koristi pri instanciranju fragmenta u okviru aktivnosi i omogućava jednostavno prosledjivanje parametara pri instanciranju fragmenta:

Sada u okviru fragmenta možemo da koristimo prosledjene parametre koristeći promenjive mParam1 i mParam2 a instanciranje Fragmenta u aktivnosti se vrši sa sledećim kodom:

NAPOMENA:
Ako je fragment podklasa ListFragment-a, kod njega se po default-u vraća ListView u metodi onCreateView(), tako da ne mora da se implementira deo vezan za povezivanje logike sa layout-om osim ako ne koristimo custom layout.

U sklopu boilerplate koda dolazi deo vezan za CustomListener koji se koriste za prosledjivanje podataka iz fragmenta u aktvinost (drugi fragment).

Više o custom listener-ima pogledaj te u članku “Kreiranje custom listenera”.

×

Primer – ListFragment

×

Primer

U ovome primeru je prikazano definisanje početnog fragmenta u aktivnost. U prazan tag kontejnera se dodajemo novi view naš fragment.

Primer

U ovome primeru pozivajući metodu showFragmentA dolazi do zamene fragmenta

×

full fragment lifecycle

×

×

Back Stack

Stack je tip memorije u koji se smeštaju elementi jedan na drugi, tako da poslednji dodati element je na vrhu (analogija sa gomilom tanjira). Elementi se uklanjaju sa stacka obrnutim redosledom, tako što se poslednje dodati element uklanja prvi. U okviru Androida postoji stack u koji se smeštaju sve aktivnosti prema redosledu pozivanja. Pritiskanjem “back” dugmenta poslednja aktivnost sa stacka se briše. Medjutim u slučaju korišćenja fragmenta to nije defaultno ponašanje pa kada korisnik pritisne back sa stacka ne ukloni poslednje dodati fragment već cela aktivnost. To nije očekivano ponašanje te je potrebno je da tu funkcionalnost programer “ručno” doda.

backstack

addToBackStack

Da bi se i fragmenti ubacili na backStack potrebno je da se dodaju koristeći addToBackStack() metodu. Ovu metodu je potrebno dodati pre svakog commit-a. Tekst koji se prosledjuje je opcioni i koristi se ako kasnije želimo da prepoznamo tu transakciju, medjutim sasvim je ok da se prosledi i null kao parametar.

FragmentManager.OnBackStackChangedListener

Ako aplikacija sa promenom fragmenata treba da ažurira i druge elemente korisničkog interfejsa (npr. actionBar) to znači da bi trebalo se reaguje nakon promene backStack-a. U tom slučaju je potrebno da se iskoristi interfejs addOnBackStackChangedListener i da se definiše callback metoda onBackStackChanged() koja treba da izvrši akciju nakon okidanja dogadjaja

Primer kada aktivnost implementira interfejs

Primer kada se callback metoda onBackStackChanged() definie “on the fly”:


Uvod u asinhrone operacije i multithreading Androida

Šta je Thread i multithreading?

Thread (srp. nit) je niz uputstava u okviru programa koji se može izvršiti nezavisno od drugog koda. Ako kompjuter ima više procesora i operativni sistem koji podržava tzv. multithreading, to omogućava programerima da dizajniraju programe čiji se thread-ovi mogu izvršavati istovremeno.

thread diagram

Thread mehanizmi

“Looper” i “MessageQueue” su mehanizmi koje koristi Thred pri izvršavanju zadataka. “Message” je zadatak (Runnable objekt) koji treba da se izvrši na Thread-u. MessageQueue se sa jedne strane puni zadacima koji trebaju da se izvrše (message), dok sa druge strane “looper” uzima jedan po jedan zadatak sve dok ne isprazni MessageQueue.

looper

Kod androida MainThread (glavna nit) je zauzeta bavljenjem stvarima kao što je crtanje korisničkog interfejsa (UI), odgovaranje na interakcije korisnika i generalno, po defaultu, izvršavanje (većine) koda koje pišemo. Stoga pošto andriod podržava multithreading, pametno je iskoristiti ovu mogućnost i pri programiranju radnje koje mogu privremeno da blokiraju rad UI-a izmestiti na drugi Thread. Kada izmestimo zahtevne radnje koje se sporo izvršavaju sa glavne niti, potrebno je da se vratimo sa tim podacima na nju jer samo glavna nit može da ažurira UI.

Primer pogrešnog koda koji blokira UI

Klikom na dugme počinje da se download-uje slika sa mreže (akcija može da potraje), interfejs je sve vreme zamrznut dok traje download i dok se slika ne prikaže u svom ImageView:

OBJAŠNJENJE:
“Caller Thread”: je thread koja poziva “worker Thread” za obavljanje nekog zadatka. Često je “Caller Thread” ustvari nit koji crta UI tzv. “Main Thread”, pa se tada “Worker Thread” još i zove “background Thread”.

Kreiranje novog Thread-a

Kreiranje novog Thread-a se može izvršiti na dva načina.

a) Ekstendovanjem Thread klase

Prvi način je da naša klasa ekstenduje klasu Thread i tako nasledi sve metode ove klase. Metoda koja je bitna za izvršavanje zadataka na Thread-u je run().

Nakon kreiranja klase je dovoljno u okviru aktivnosti kreirati njenu instancu i pokrenuti metodu start():

b) Implementiranjem Runnable interfejsa

Za ovaj pristup je potrebno kreirati klasu koja implementira Runnale interfejs. Runnable interfejs sadrži samo jednu metodu run(). U ovu metodu se ubacuje kod koji treba da se izvrši. Objekat koji nastaje instanciranjem ovakve klase se koristi kao parametar pri kreiranju Thread-a. Jedan objekat se može koristiti za više Thread-ova.

Da bi kreirali Thread na ovaj način potrebno je u okviru aktivnosti kreirati instancu klase Thread koja prihvata Runnable objekat kao parametar i zatim pokrenuti metodu start():

NAPOMENA:
U praksi se instanca klase koja implementira “Runnable” interfejs nejčešće kreira “on the fly” bez definisanja klase:

Koja je razlika izmedju ova dva pristupa?

Ako proširimo klasu sa Thread, naša klasa ne može više proširiti neku drugu klasu jer Java ne podržava višestruko nasledjivanje. Ali, ako implementiramo Runnable interfejs, naša klasa se može i dalje proširivati sa drugim klasama. Medjutim treba naglasiti da proširivanjem klase interfejsom Runnable ipak ne dobijamo sve metode koje se dobijaju sa proširivanjem klase sa Thread (npr. yield(), interrupt()…). Stoga ukoliko Vam trebaju te metode interfejs nije opcija, ali ako vam je dovoljna metoda run() Runnable interfejs je preporučen način stim što vam preostaje opcija da možete proširite vašu klasu sa nekom drugom klasom.

Ažuriranje UI sa sporednog Thread-a

Kod Androida radnje koje se odvijaju na drugoj niti (background thread) ne mogu da ažuriraju UI, jer UI može da ažurira samo Main Thread. Stoga potrebno je da sporedna nit na neki način komunicira sa glavnom niti i pošalje joj zadatke u MessageQueue, nakon čijeg izvršenja bi glavna nit ažurirala UI.

a) View.post() / View.postDelayed()

Ovaj pristup koristi metodu post() i na taj način obezbedjuje da Runnable bude dodat u message queue gde čeka na red da se pokrene na glavnom threadu.

Primer

Ovo je najlakši način ali nije prigodan za složenije slučajeve, jer je teško održavanje i debugovanje koda. Tako da je preporuka da se za kompleksnije interakcije koristimo ili AsyncTask i Handler.

b) Handler

Handler se koristi da olakša komunikaciju izmedju “caller Thread-a” i “worker Thread-a”. Ovaj pristup je odličan upravo kada se poveća kompleksnost kao ili kada izvodimo više ponovljajućih zadataka (npr. preuzimanje više slika koje će biti prikazane u ImageView-u…).

Primer

U ovome primeru se u novom worker Thread-u u okviru run() metode izvršava izvršava dugačka radnja
final Bitmap b = loadImageFromNetwork();
a zatim po završetku Handlerova metoda post() koja ažurira UI:

Activity.runOnUiThread()

Ovo je ustvari nadogradjena verzija Handler-a. Metoda runOnUiThread() proverava da li je trenutni Thread ustvari UiThread, ako jeste odmah izvršava odredjeni kod na UI Thread-u. Tek ako trenutni Thread nije Ui Thread, onda se ponaša kao Handler koji prosledjuje zadatak u MessageQueue. Radi lakšeg shvatanja u sledećem delu je prikazan kako bi izgledao sadržaj ove metode:

A koristi se tako što joj se prosledi Runnable objekat sa zadatkom za izvršenje.

Primer

b) AsyncTask

AsyncTask klasa takodje omogućava da se zadaci izvršavaju na sporednom Threadu a da se njihovi rezultati publikuju na main Thread-u. Za izvršavanje asinhronih zadataka je dovoljno ekstendovati našu klasu sa AsyncTask klasom i primeniti njene metode:

  • onPreExecute()
  • doInBackground()
  • onProgressUpdate()
  • onProgressUpdate()
  • onPostExecute()

Ovo je jednostavan način za koji nije potrebno znanje Thread modela, jer klasa enkapsulira kreiranje Thread-ova i korišćenje Handler-a. Problem sa AsyncTask-om je što klasa nije “svesna” životnog ciklusa (lifecycle) aktivnosti ili fragmenta, pa je na programeru da reši šta da radi sa zadacima definisam kroz AsyncTasks kada se aktivnost uništi. Stoga ovo nije baš najbolja opcija za dugačke operacije, jer ako aplikaciju uništi Android radi oslobadjanja memorije, uništiće i naš pozadinski proces.
AsyncTasks je idealan kada je CallerThread ustvari UI Thread, i akcije ne traju dugo, kao što je:

  • Prikupljanje podataka sa web servisa i njihov prikaz
  • Upit bazi podataka (database query)
  • Čitanje i pisanje na hard disk I/O
Primer

×

Anonimna klasa je klasa koja nema ime, stoga kada bi hteli da instanciramo objekat od ovakve klase to bi izgledalo ovako:

Nama je potrebna specifična anonimna klasa, ona koja implementira odredjeni interfejs, stoga iako ne moramo da definišemo ime klase moramo da definišemo koji interfejs da implementiramo. Ako je interfejs definisan u svome fajlu to se postiže ubacivanjem naziva interfejsa ispred zagrada:

Pored ovoga je potrebno da implementiramo sve abstraktne metode ovog interfejsa:


Kreiranje menija kod android aplikacija

Kreiranje sadržaja menija

Ovaj specifični meni se prikazuje u sklopu

Gde se nalaze meniji?

Za svaki meni je potrebno definisati stavke koje se nalaze u njemu. To se definiše u XML formatu u fajlu koje se nalazi u specifičnom folderu pod nazivom “menu” (nalazi se u sklopu “res” foldera). Ovaj fajl generišemo desnim klikom na folder “menu” u project sekciji i izborom New / “Menu resurse file” opcije.

Atributi item elementa menija

Stavke menija se definišu kroz element <item/> koji može imati definisane mnoge atribute, najčešće korišćeni atributi su sledeći:

  • id (android:id=”@+id/delete”)
  • title (android:title=”@string/delete”)
  • icon (android:icon=”@drawable/ic_delete”)
  • enabled (android:enabled=”false”)
  • checkable (android:checkable=”true”)
  • showAsAction (Ovi atributi su vezani za prikazivanje elemenata “options menija”u okviru action bar-a )
    • ifRoom (app:showAsAction=”ifRoom”)
    • withText (app:showAsAction=”withText”)
    • never (app:showAsAction=”never”)
    • always (app:showAsAction=”always”)
Primer

Grupe

Moguće je okupiti slične članove menija u grupe:

Podmeni

Pravljenje podmenija je jednostavno, u sklopu “item” elementa treba ugraditi novi “menu” element:

NAPOMENA:
Kod elemenata kod kojih se koristi atribut android:checkable=”true” je nakon klika na ovakav član menija potrebno da podesite stanje u polju za potvrdu, jer checkBox (ili radio dugme) ne menja automatski svoje stanje. Nakon klika moramo da upitamo kakvo je stanje stavke bile pre ovog klika (da li je bilo čekirano) koristeći isChecked().
Ako je prethodno bilo čekirano onda sada nakon klika više nije i moramo da stavimo stanje setChecked(false) i obrnuto ako nije bilo čekirano item.isChecked()=false sada posle klika jeste i moramo da stavimo item.setChecked(true);

Plivajući menu (floating context menu)

Kreiranje sadržaja menija

Prvo je potrebno definisati sadržaj menija u XML formatu.

Primer (neki_menu.xml)

U ovome primeru prvi element menija je tzv. checkbox.

Integrisanje menija u aktivnost

Nakon definisanja sadržaja je potrebno da se omogući da taj meni bude dostupan u aktivnosti.

Primer

Registrovanje dugmeta koje otvara meni

U sklopu aktivnosti je potrebno registrovati dugme (u onCreate() metodi) koje će biti povezano za taj meni:

A zatim zakačiti clickListener za njega koji će na klik da pozove metodu openContextMenu(view):

Definisanje šta će da se dogodi klikom na člana menija

U metodi onContextItemSelected() definišemo šta će da se desi klikom na neki od članova menija. Za selektovnanje člana menija se koristi “id” itema definisanog u XML-u (neki_menu.xml):

Options meni

options menu

Za options meni nije potrebno da se definiše dugme koje će da reaguje na klik i otvori ovaj meni, zato što ovo dugme android sam ubacuje u action bar i dodeljuje mu ikonicu sa tri vertikalne tačke.

Treba naglasiti da ovde imamo priliku definišemo koji će član menija u biti odmah prikazan u actionBar-u pored options ikone (tri vertikalne tačke). Ovo se obezbedjuje sa atributom app:showAsAction="ifRoom". Options meni se prikazuje baš na mestu kliknutog dugmeta. Ukoliko se neki član prikazuje odmah u action baru on se onda ne nalazi u samom meniju.

Kreiranje sadržaja menija

Pored ovih specifičnosti kao i kod svih drugih menija i ovde je neophodno da se definiše sadržaj menija u XML formatu.


open options menu

Integrisanje menija u aktivnost

Na sličan način kao kod Context Float menija se i ovde integriše meni:

Definisanje šta će da se dogodi klikom na člana menija

U metodi onOptionsItemSelected definišemo šta će da se desiti nakon klika na neki od članova menija:

PopUp meni

Ovaj meni je specifičan jer je vezan za mesto gde je kliknuto. Meni se prikazuje tamo gde ima prostora ili ispod kliknutog mesta, ili iznad kliknutog mesta. PopUp meni se sakriva sa ekrana kada korisnik klikne na neki član menija ili negde sa strane van menija.

popup

Definisanje šta će da se dogodi klikom na člana menija

Da bi klasa (npr. aktivnost) mogla da izvrši akcije nakon klika potrebno je da klasa implementira interfejs PopupMenu.OnMenuItemClickListener.
Nakon imeplementiranja interfejsa je potrebno da se implementira (Override) i njegova abstraktna metoda onMenuItemClick(), kroz koju nam je omogućeno da definišemo za svaki član menija koja će akcija biti preduzeta nakon klika na član.

Integrisanje menija u aktivnost

Da bi prikazali PopUp meni potrebno je da na okidaču (npr. neko dugme) definišemo onClick metodu (npr. shoPopup()) koja izgleda ovako:

Contextual Action Mode meni

Ovaj specifični meni se prikazuje u sklopu actionBar-a, tačnije preko actionBar-a se. Pored članova menija prikazanih ikonama u desnom kraju, nalazi se i naslov menija u sredini, i strelica za zatvaranje menija na levoj strani.

pre
pre

Kreiranje sadržaja menija

Kao i za svaki meni prvo je potrebno napraviti sadržaj menija u xml formatu (New “Menu resurse file”) u folderu “menu”

Primer (context_action_menu.xml)

Definisanje ActionModeCallback metode

Pre svega u sklopu aktivnosti je potrebno definisati promenjivu tipa ActionMode (napomena izabrari verziju v.7) jer se ona koristi u okviru callback metode:

A zatim i ActionModeCallback instancu u kojoj se vrši sav posao u vezi Contextual Action Mode menijem. Ima 4 metode, u prvoj onCreateActionMode() se povezuje predhodno definisan sadržaj menija sa Action Mode menijem. U metodi onActionItemClicked() se definišu akcije nakon klika na neki od članova. A ono šta se dešava kada se meni zatvori se definiše u metodi onDestroyActionMode().

Registrovanje dugmeta koje otvara meni

U sklopu aktivnosti je potrebno na odredjenom dugmetu definisati clickListener (u onCreate() metodi) sa kojim ćemo omogućiti da se na klik pozove prethodno definisana callback metoda mActionModeCallback():

Pa bi ceo kod za listener ovako izgledao:



Aktivnost u okviru android operativnog sistema

Šta je android aktivnost?

Android aplikacije su kolekcija zadataka, gde svaki zadatak ima svoju funkcionalnost. Te funkcionalnosti mogu se raspodeliti na četiri Androidove komponente:

Funkcionalnost Android klasa Primer
Interakcija sa korisnikom Activity Unošenje beleške, igranje računarske igre
Pozadinski proces koji dugo traje čak i kada korisnik nije direktno u interakciji sa aplikacijom. Servis se izvršva sve dok se sam ne isključi ili ga neko drugi ekspicitno ne stopira. Service Reprodukovanje muzike, ažuriranje izgleda ikonice za vremensku prognozu
Primanje poruka BroadcastReceiver Aktiviranje alarma pri određenom događaju
Unošenje i učitavanje podataka ContentProvider Otvaranje novog telefonskog kontakta

mozak aktivnosti

Aktivnost kao jedan od načina sa kojom android omogućava funkcionalnost aplikacije, ima za cilj da omogući interakciju sa korisnikom. Iz tog razloga svaka aktivnost ima korisnički interfejs. Za interaktivnost sa korisnikom aktivnost obezbedjuje prozor u kome se smešta korisnički interfejs. Ovaj prozor obično popunjava ceo ekran uredjaja, ali može biti manji ili da pliva preko drugih prozora. Najčešće jedna aktivnost implementira jedan ekran.

Da bi naša klasa predstavljala aktivnost u aplikaciji ona mora da ekstenduje klasu “Activity”. Najčešće se koristi klasa “AppCompatActivity” koja je naslednik klase Activity i pokriva nove funkcionalnosti kao što je actionBar… Ova klasa dolazi sa appcompat-v7 biblotekom.

Sama klasa “Activity” (AppCompatActivity) je podklasa klase Context, što znači da sve aktivnosti imaju pristup globalnim informacijama o okruženju aplikacije.
Ako aplikacija ima više aktivnosti, uvek je jedna aktivnost definisana kao glavna aktivnost (eng. MainActivity). Ova glavna aktivnost predstavlja prvi ekran koji se pojavljuje kada korisnik pokrene aplikaciju, nakon toga iz glavne aktivnost se može pokrenuti i druga aktivnost. Na ovaj način, aktivnost služi kao ulazna tačka za interakciju aplikacije sa korisnikom. Ta aktivnost može da pokrene drugu aktivnost, tada glavna aktivnost prelazi u stanje pauze dok je aktivna sekundarna aktivnost. Kada se završi sekundarna aktivnost, glavna aktivnost se vraća u prvi plan i nastavlja.

Osnovne metode životnog ciklusa

Od trenutka kada se aktivnost prikaže na ekranu do trenutka kada se sakrije, aktivnost prolazi veći broj faza u njenom tzv. “Životnom ciklusu” (eng.lifecycyle).

osnovni lifecycle

onCreate()

Ovo je prva metoda koja se pokreće po pokretanju aplikacije. Aplikacija pre izvršenja ove metode nije još nije vidljiva a tek postaje vidljiva kada se metoda onCreate() izvrši.

NAPOMENA:
U telu ove metode NIKADA ne pravite kod koji se predugo izvršava (npr. beskonačnu petlju) jer se Ui aplikacije prikazuje tek po zavšetku ove metode.

Metod onCreate() prihvata parametar savedInstanceState tipa Bundle. Bundle objekat omogućava da u jedan objekat prikupimo različite tipove podataka u vidu key/value. Ako se aktivnost pravi “od nule” onda je ovaj parametar null, medjutim ukoliko se ponovno stvara (nakon onDestroy()), onda taj parametar ima vrednost Bundle objekta upotrebljenog u metodi onSaveInstanceState().

U ovoj metodi se izvršava sve ono što je bitno pre prikazivanja aplikacije, kao što je npr. definisanje eventListener-a ili povezivanje aktivnosti sa odgovarajućim layout-om koji je definisan u XML-u:

Primer

onStart()

Po završetku metode onCreate(), UI aplikacije postaje vidljiv i moguća je interakcija aplikacije sa korisnikom pa se pokreće ova metoda. Ukoliko je aplikacija već bila pokrenuta pa je bila zaustavljena, ova metoda se pokreće i po završetku onRestart() metode.

onPause()

Ova metoda se poziva kada se aplikacija pauzira ali i pre nego što se aplikacija u potpunosti zaustavi. Ako se aplikacija samo pauzira nakon ove metode se poziva metoda onResume(), dok u slučaju zaustavljanja rada aplikacije po završetku ove metode se poziva metoda onStop().

onResume()

Pri prvom pokretanju aplikacije ova metoda se pokreće posle metode onStart(), a kasnije se poziva po završetku metode onPause() svaki put kada se aktivnost nastavlja.

onStop()

Ova metoda se pokreće nakon što aktivnost postaje nevidljiva. Razlog može da bude akcija koju izvrši korisnik/android tako što iz nekog razoga potisnu našu iz prvog plana, najčešće zbog neke druge aplikacija (npr. primamo poziv dok radi naša aplikacija). Aplikacija se u tom trenutku ne vidi ali i se i dalje izvršava u pozdini.

onRestart()

Ova metoda se poziva pri restartovanju aplikacije pre nego što aktivnost postane vidljiva. Ova metoda se poziva posle metode onStop(), i može se zaključiti da se ova metoda poziva svaki put kada i metoda onStart(), osim pri prvom pokretanju aplikacije.
Treba naglasiti da se ova metoda NE IZVRŠAVA kada aktivnost prvi put pokreće, kao i kada se rotira ekran (aktivnost uništava i ponovo pokreće).

onDestroy()

Ova metoda se poziva kada sistem odluči da uništi aplikaciju. Prethodi joj metoda onStop().

NAPOMENA:
U saradnji sa nabrojanim lifecycle metodama se veoma često koriste još dve metode:

  • onSaveInstanceState(Bundle outState)
  • onRestoreInstanceState(Bundle savedInstanceState)

Ove dve metode se koriste za čuvanje i ponovno iskorišćavanje podataka nakon prekida rada aplikacije. Kada se aplikacija zaustavlja android pokreće metodu onSaveInstanceState() pre metode onStop(). Na ovaj način andriod omogućava da se kroz nju sačuva state aplikacije.
Treba naglasiti da android ne poziva metodu onSaveInstanceState () kada korisnik eksplicitno zatvori aktivnost ili kada aplikacija pozove metodu finish().

Metoda onRestoreInstanceState() se koristi da povrati sačuvane podatke, pa se izvršava posle metode onStart() a pre metode onResume().

Metodi onCreate() se prosedjuje isti Bundle objekat pa se i ona može koristiti za povratak podataka (koristi se u ovu namenu kada je aplikacija uništena i ponovo se kreira).

Primena metoda životnog ciklusa aktivnosti

Kada aplikacija izgubi fokus ili postane nevidljiva, korisnik ne može da vrši interakciju sa aplikacijom, ali se aplikacija i dalje izvršava u pozadini, stoga je potrebno je napisati kod sa kojim bi trebali da zaustavimo sve operacije koje troše CPU, zaustavimo sve audio ili video reprodukcije kao i da sačuvamo trenutne vrednosti nekih podataka (npr. gde je stala reprodukcija videa, vrednost nekog proračuna…). Ako je igra u pitanju savet je da odmah po gubitku fokusa automatski zaustavimo igru, jer je korisnik izgubio mogućnost da kontroliše igru. Takođe, po povratku fokusa nikada ne bismo trebali da automatski da pokrenemo igru, jer bi korisnik mogao biti nepripremljen (npr. vožnja trke u trkačkoj igri), preporuka je da se pokaže ekran za pauzu i na taj način dozvolimo korisniku da sam izbere kada će da nastavi igru. Većina ove potrebne funkcionalnosti se piše u pomenutim metodama.
Slučajevi koji se mogu desiti u životu aplikacije i za koje moramo biti pripremljeni, su:

a) Gubitak i povratak fokusa

Gubitak fokusa (aplikacija je vidljiva!) se najčešće dešava kada se aktivira neki sistemski dijalog. Pre gubitka fokusa se poziva metoda onPause(). Ukoliko aplikacija povrati fokus izvršiće se metoda onResume().

b) Korisnik pauzira aplikaciju a zatim se vraća na nju

Aktivnost može biti pauzirana na više načina:

  • kada korisnik pritisne Home dugme (aplikacija će se restartovati ako korisnik ponovo izabere pauziranu aplikaciju u meniju (klikom na dugme pravougaonik) gde su sve privremeno zaustavljene aplikacije)
  • kada se isključi ekran (aplikacija će se restartovati kada korisnik uključi ekran)

U oba slučaja se aktivnost zaustavlja, ali se ne uništava. Metode koje su se do tada izvršile su: onPause(), onSaveInstanceState(), i onStop(). Nakon restarta se izvršava metoda onRestart() a zatim odmah i metoda onStart(), onRestoreInstanceState() i na kraju metoda onResume()

c) Operativni sistem uništava i ponovno pokreće aplikaciju

Najbolji primer za ovaj slučaj je rotiranje uredjaja. Android rotaciju posmatra kao promenu konfiguracije i sprema se da uništi aktivnost, a zatim da je ponovo startuje. Pre uništenja se poziva onStateInstanceState() koji je odličan za privremeno čuvanje podataka, da bi se kasnije iskoristili pri ponovnom pokretanju aktivnosti uz metodu onCreate() koja takodje ima Bundle objekat.

d) Operativni sistem uništava aplikaciju da oslobodi resurse

Ovo se dešava ako se aplikacija ne koristi dugo vremena (pauzirana je), pa je sistem nateran da je uništi kako bi oslobodio resurse zatvaranjem keširanih procesa. Po izvršenjeu metode onStop() aplikacija postaje nevidljiva, a pre samog uništenja android prvo poziva metodu onSavaInstanceState(). Uništenje aplikacije se dešava nakon metode onDestroy().

OBJAŠNJENJE:
Android “ubija” procese kada mu treba memorija prema odredjenom redosledu koji zavisi od važnosti procesa (“importance hierarchy”):

  1. keširan process
  2. servis
  3. vidljiv proces ali bez fokusa (visible process)
  4. aktivan proces (foreground process)

Treba naglasiti da u slučaju ponovnog pokretanje nove instance aplikacije, ovoj aplikaciji su dostupni svi sačuvani podaci kroz metodu onSaveInstanceState() pa kao i u prethodnom primeru možemo ih pokupiti iz Bundle objekta u metodi onCreate().

e) Korisnik uništi aplikaciju ili se aplikacija sama završi

Ovaj slučaj se može desiti:

  • kada korisnik stisne sistemsko dugme back
  • kada aktivnost pokreće sopstveno uništenje pozivom metode finish()

Postupak započinje sa metodama: onPause(), zatim onStop() kada aplikacija postaje nevidljiva, a zatim i metoda onDestroy(). Treba naglasiti da se u oba slučaja podaci ne čuvaju (ne poziva se metoda onSaveInstanceState()), te se aplikacija pri ponovnom pokretanju aplikacija pokreće od “nule” sa defaultnim podacima.

NAPOMENA:
Kada korisnik pređe sa jedne na drugu aktivnost pa se vrati na početnu, može doći do pokretanja više instanci iste aktivnosti na uređaju. Da bi se to izbeglo, programer treba da definiše ponašanje aktivnosti. Ovo se izvšava u okviru fajla AndroidManifest.xml. Da bi se na uređaju pokretala uvek samo jedna instanca aktivnosti, potrebno je u activity elementu activity koji sadrži (MAIN i LAUNCHER):

dodati i:

da krajnji izgleda ovako: