Download as pdf or txt
Download as pdf or txt
You are on page 1of 6

Perzistencija podataka u Android aplikacijama

Poput svake aplikacije kod koje je potrebno da se podaci koji su prikupljeni tokom njenog
rada ostanu dostupni i prilikom sljedećeg pokretanja te aplikacije i Android aplikacije imaju
mehanizam za perzistenciju podataka.
Na ovoj vježbi radit će se SQLite baza podataka koja omogućava da se na struktuiran
način sačuvaju podaci, na isti način kako ste radili i sa bilo kojom drugom SQL bazom.
SQLite baza podataka je relaciona baza podataka čiji fajlovi su privatni i dostupni samo za
onu aplikaciju koja ih je kreirala.
Nakon što se kreira baza moguće je kreirati ContentProvidera kako bi se aplikacijska
logika i struktura podataka odvojila. Ovo je dobra praksa jer tada aplikacija ne mora brinuti
o tome kakav je izvor podataka već koristi standardizovani interfejs koji ContentProvideri
pružaju. A putem njih je moguće učiniti podatke iz naše aplikacije dostupne drugim
aplikacijama.
Postoje ContentProvideri za različite izvore podataka poput: kalendara, imenika, media
store-a i sl. Moguće je takođe implementirati i vlastiti ContentProvider kako bi uključili i
druge podatke u vašu aplikaciju. O ContentProviderima više na sljedećim vježbama.

SQLite baza podataka


SQLite je relaciona baza podataka koja zadovoljava sve standarde, koja je otvorenog koda
i čije izvršavanje nije zahtjevno. Ona je sastavni dio aplikacije i sastavni dio softverskog
steka zbog čega je: olakšana sinhronizacija i zaljučavanje transakcija, minimizovano
kašnjenje i eliminisan je problem vanjskih zavisnosti.
Razlika između standardnih SQL baza je ta što tip vrijednosti u jednoj koloni ne mora biti
isti, što znači da svaki red može imati drugačiji tip podataka.

ContentValues i Cursors
Za ubacivanje novog reda u tabelu koriste se objekti tipa ContentValues. Oni sadrže sve
vrijednosti koje se trebaju ubaciti u odgovarajući red u obliku ključa vrijednosti. Sa
ContentValues možemo lakše pripremiti podatke za upite tipa insert i update.
Svi upiti prema bazi rezultuju u odgovoru koji se smješta u objekat tipa Cursor. Cursor je
privremeni buffer koji čuva rezultate nakon upita. Oni omogućavaju više načina za
pomjeranje kroz rezultujući skup. To je realizovano putem sljedećih metoda Cursor klase:
moveToFirst​ – pomjeranje na prvi red rezultujućeg skupa,
moveToNext​ – pomjeranje za jednu poziciju unaprijed (tj. sljedeći red),
moveToPrevious​ – pomjeranje za jednu poziciju unazad (tj. prethodni red),
moveToPosition​ – pomjeranje na specificiranu poziciju ...
Postoje i metode unutar Cursor klase za dobijanje određenih informacija o rezultujućem
skupu:
getCount​ – broj redova u rezultujućem skupu,
getColumnIndexOrThrow​ – vraća indeks kolone počevši od nule ukoliko postoji
kolona sa specificiranim imenom, inače baca izuzetak,
getColumnName​ – daje naziv kolone sa zadanim indeksom,
getColumnNames​ – daje nazive svih kolona u vidu niza stringova,
getPosition​ – daje trenutnu poziciju kursora.
Svaki upit nad bazom se vraća kao objekat tipa ​Cursor​. Na ovaj način Android upravlja sa
resursima tako što dobavlja i oslobađa redove i kolone po potrebi.
Jedan upit nad bazom obično sadrži:
● naziv tabele nad kojom vršimo upit,lista kolona koje se trebaju dobaviti sa upitom,
● WHERE ​dio upita gdje se dodatno definiše koji se redovi traže​, ​u ​WHERE ​dijelu
upita mogu stajati i argumenti iz promjenjljivih
● GROUP BY –​ grupisanje redova
● HAVING – ​koji redovi se trebaju grupisati
● poredak sortiranja
● limit – najveći broj redova koji će se vratiti
Primjer jednog upita:

// Specificiramo sve kolone koje ćemo koristiti u upitu


// Sve nazive kolona je poželjno definisati kao stringovne konstante, a onda
// ih dodati u niz koji predstavlja sve kolone koje se trebaju naći u upitu
String​[]​ koloneRezulat ​=​ ​new​ String​[]{​ MUZICAR_ID​,​ MUZICAR_IME​,​ MUZICAR_ZANR​}

// Specificiramo WHERE dio upita


String where ​=​ MUZICAR_ZANR ​+​ ​"=pop"​;

// Definišemo argumente u where upitu, group by, having i order po potrebi


String whereArgs​[]​ ​=​ ​null​;
String groupBy ​=​ ​null​;
String having ​=​ ​null​;
String order ​=​ ​null​;

// Dohvatimo referencu na bazu (poslije ćemo opisati kako se implementira helper)


SQLiteDatabase db ​=​ muzicarDBOpenHelper​.​getWritableDatabase​();

// Izvršimo upit
Cursor cursor ​=​ db​.​query​(​MuzicarDBOpenHelper​.​MUZICAR_TABLE​,
kolone_rezultat​,​ where​,
whereArgs​,​ groupBy​,​ having​,​ order​);

Ukoliko bi iz prethodnog kursora željeli proći kroz imena muzičara tada trebamo dohvatiti
odgovarajuću kolonu (​MUZICAR_IME​) i proći kroz sve redove.

int​ INDEX_KOLONE_IME ​=​ cursor​.​getColumnIndexOrThrow​(​MUZICAR_IME​);

while​(​cursor​.​moveToNext​()){
Log​.​d​(​“​Ime:​ ​”​,​cursor​.​getString​(I​ NDEX_KOLONE_IME​)​”​;
}

//kada završimo sa kursorom potrebno ga je zatvoriti


cursor​.​close​();
Kako upisati, update-ovati i obrisati red
Da bi upisali novi red u bazu trebamo kreirati instancu klase ContentValues i u nju dodati
parove naziv kolone i nova vrijednost.

ContentValues novi ​=​ ​new​ ContentValues​();


noveVrijednosti​.​put​(​MUZICAR_IME​,​muzicarevoIme​);
noveVrijednosti​.​put​(​MUZICAR_ZANR​,​muzicarevZanr​);
// ... itd

SQLiteDatabase db ​=​ muzicarDBOpenHelper​.​getWritableDatabase​();


db​.​insert​(​MuzicarDBOpenHelper​.​MUZICAR_TABLE​,​null​,​noveVrijednosti​);

Da bi update-ovali redove potrebno je kreirati ContentValues na isti način i pored njega


specificirati i where dio upita.

// kreiranje ContentValues instance


ContentValues updatedValues ​=​ ​new​ ContentValues​();
// Dodjeljivanje novih vrijednosti
updatedValues​.​put​(​MUZICAR_ZANR​,​ noviZanr​);

// Specificiramo koje redove treba izmjeniti


String where ​=​ MUZICAR_ID ​+​ ​“​=​”​ ​+​ muzicarId​;
String whereArgs​[]​ ​=​ ​null​;
// Update reda sa novim vrijednostima
SQLiteDatabase db ​=​ muzicarDBOpenHelper​.​getWritableDatabase​();
db​.​update​(​MuzicarDBOpenHelper​.​MUZICAR_TABLE​,​ updatedValues​,
where​,​ whereArgs​);

Kod brisanja redova trebate specificirati where dio i pozvati metodu delete.

Sada imamo sve potrebno za pravljenje upita nad bazom osim helper objekta
muzicarDBOpenHelper​. Ovaj helper se implementira na način da se nasljedi klasa
SQLiteDBOpenHelper. U klasi se trebaju implementirati dvije metode:
1. Metoda za kreiranje baze ako ne postoji na disku - ​onCreate()
2. Metoda za ažuriranje baze na disku ako se ne poklapa sa trenutnom -
onUpgrade()

Pored ove dvije metode obično se definišu i stringovne kontrante za nazive kolona, tabela,
baze i konstanta za verziju baze.
public​ ​class​ ​MuzicarDBOpenHelper​ ​extends​ SQLiteOpenHelper ​{
public​ ​static​ ​final​ String DATABASE_NAME ​=​ ​“​mojaBaza​.​db​”​;
public​ ​static​ ​final​ String DATABASE_TABLE ​=​ ​“​Muzicari​”​;
public​ ​static​ ​final​ ​int​ DATABASE_VERSION ​=​ ​1​;
public​ ​static​ ​final​ String MUZICAR_ID ​=​"_id"​;
public​ ​static​ ​final​ String MUZICAR_IME ​=​"ime"​;
public​ ​static​ ​final​ String MUZICAR_ZANR ​=​"zanr"​;
//... ostale potrebne kolone

// SQL upit za kreiranje baze


private​ ​static​ ​final​ String DATABASE_CREATE ​=​ ​"create table "​ ​+
DATABASE_TABLE ​+​ ​" ("​ ​+​ MUZICAR_ID ​+
​" integer primary key autoincrement, "​ ​+
MUZICAR_IME ​+​ ​" text not null, "​ ​+
MUZICAR_ZANR ​+​ ​" text not null);"​;

public​ ​MuzicarDBOpenHelper​(​Context context​,​ String name​,


CursorFactory factory​,​ ​int​ version​)​ ​{
​super​(​context​,​ name​,​ factory​,​ version​);
​}

//Poziva se kada ne postoji baza


@Override
public​ ​void​ ​onCreate​(​SQLiteDatabase db​)​ ​{
db​.​execSQL​(​DATABASE_CREATE​);
​}
// Poziva se kada se ne poklapaju verzije baze na disku i trenutne baze
@Override
public​ ​void​ ​onUpgrade​(​SQLiteDatabase db​,​ ​int​ oldVersion​,​ ​int​ newVersion​)​ ​{
​// Brisanje stare verzije
db​.​execSQL​(​“​DROP TABLE IF IT EXISTS ​“​ ​+​ DATABASE_TABLE​);
​// Kreiranje nove
onCreate​(​db​);
​}
}

Šta se dešava u ovoj klasi? Ako ne postoji baza helper će pozvati onCreate metodu i
kreirati novu, ako je baza promjenjena tada se poziva onUpgrade. U svakom slučaju
rezultat ovog helpera je keširana baza koju možete koristiti za vaše upite.
U ranijem kodu kada smo spominjali kako se prave upiti nad bazom vidjeli smo da se
poziva metoda ​getWritableDatabase()​, ova metoda može da se ne izvrši uspješno pa je
dobra praksa pozvati ​getReadableDatabase() ​ukoliko se to desi. U tom slučaju imate
bazu koja je dostupna samo za čitanje.
Kako SQLiteOpenHelper kešira bazu možete prije svakog upita zatražiti instancu baze sa
navedenim metodama.
NAPOMENA: Insancu baze zatvorite u slučaju kada ste sigurni da neće više biti
upita nad njom (pri gašenju aktivnosti ili servisa)!

ResourceCursorAdapter
Često nam treba da rezultate upita prikažemo u nekom ListView elementu. Da bi rezultate
iz cursor-a na najjednostavniji način prikazali u listi koristit ćemo ResourceCursorAdapter.
Ova klasa služi poput ArrayAdaptera iz prve vježbe stim da sada kao izvor podataka
imamo Cursor.
Da bi implementirali ResourceCursorAdapter trebamo mu definisati layout, za ovo
možemo iskoristiti isti layout liste iz prve vježbe.
Dalje je potrebno implementirati klasu adaptera i tu navesti konstruktor i implementaciju
metode ​bindView​. Ova metoda se poziva i koristi se kako bi povezali pojedine vrijednosti iz
Cursora u polja layouta liste.
Primjer:

public​ ​class​ ​MuzicarCursorAdapter​ ​extends​ ResourceCursorAdapter ​{

​public​ ​MuzicarCursorAdapter​(​Context context​,​ ​int​ layout​,​ Cursor c​,​ ​int​ flags​)​ ​{


​super​(​context​,​ layout​,​ c​,​ flags​);
​}

​@Override
​public​ ​void​ ​bindView​(​View view​,​ Context context​,​ Cursor cursor​)​ ​{
TextView ime ​=​ ​(​TextView​)​ view​.​findViewById​(​R​.​id​.​ime​);
ime​.​setText​(​cursor​.​getString​(​cursor​.​getColumnIndex​(​MUZICAR_IME​)));

TextView zanr ​=​ ​(​TextView​)​ view​.​findViewById​(​R​.​id​.​zanr​);


zanr​.​setText​(​cursor​.​getString​(​cursor​.​getColumnIndex​(​MUZICAR_ZANR​)));
​}
}

U implementaciji konstruktora našeg adaptera primjetite četri parametra:


context – kontekst aplikacije
layout – id layouta koji će se koristiti za prikaz jednog elementa liste
cursor – cursor iz kojeg uzimamo podatke za sve elemente liste
flags – Mogu se postaviti dva flag-a
◦ FLAG_AUTO_REQUERY – kada god dođe do obavjesti da je sadržaj izmjenjen
pozvat će se requery() nad kursorom, pošto je ovo često vremenski zahtjevno, a
izvršava se na UI niti ovaj flag je proglašen zastarjelim od API 11 verzije.
◦ FLAG_REGISTER_CONTENT_OBSERVER – ukoliko je postavljen ovaj flag
tada će se registrovati content observer nad kursorom i u slučaju promjene
sadržaja pozvat će se metoda ​onContentChanged().
◦ FLAG_NONE – ukoliko ne želite koristiti niti jedan od prethodna dva
Napomene za rad sa ResourceCursorAdapter-om:
U cursoru kojeg prosljeđujete mora postojati jedna kolona koja ima naziv "_id", ova
kolona predstavlja redni broj reda u tabeli koji je cijeli broj
◦ Rješenje: kreirajte tabelu tako da sadrži kolonu sa navedenim imenom ili
napravite upit koji kolonu koja predstavlja redni broj reda predstavlja sa imenom
"_id" (​http://www.w3schools.com/sql/sql_alias.asp​).
Kada napravite novi upit i kada želite ažurirati prikaz liste tada možete iskoristiti
metodu:
◦ adapter.changeCursor(noviCursor); - ova metoda mjenja stari cursor sa novim i
zatvara stari, postoji i swapCursor metoda koja samo mjenja dva cursor-a ali ne
zatvara stari

Zadaci za vježbu:
Zadatak 1:
Napravite da se pri paljenju vaše aplikacije pokrene upit koji će upisati nekoliko muzičara u
bazu. U listu muzičara upišite muzičare iz baze pri čemu trebate koristiti
ResourceCursorAdapter. Pri gašenju aplikacije pokrenite upit koji će obrisati sve unose iz
baze.
Zadatak 2:
Pri pokretanju aplikacije potrebno je da se prikaže 5 muzičara koje je korisnik ranije
pretraživao.
Zadatak 3:
Muzičar se dodaje u bazu u slučaju kada korisnik klikne na njega (tj. kada se otvori
fragment detalji) (on ulazi u onih 5 muzičara iz prethodnog zadatka). Tada je potrebno
zapisati sve podatke o njemu u bazu uključujući i top 5 pjesama i albuma.
Zadatak 4:
U slučaju da korisnik unese u polje za pretragu naziv nekog muzičara i klikne na dugme, a
internet nije dostupan, korisniku se treba ispisati poruka da pretraživanje nije moguće i dati
unos trebate smjestiti u tabelu prethodne pretrage. Ova tabela treba da sadrži listu
pretraga na način da se čuvaju podaci o stringu pretrage, vremenu i tipu pretrage. Tip
pretrage može biti (uspješna ili neuspješna). Korisniku u fragmentu liste muzičara trebate
ponuditi opciju da vidi prethodne pretrage (dodatno dugme npr.) i u slučaju da korisnik
odabere opciju pregleda prethodnih pretraga lista muzičara se treba zamjeniti sa listom
pretraga (možete iskoristiti fragmente za ovo). Lista prethodnih pretraga treba da se
prikazuje na način da se prikaže string pretrage i vrijeme, a u zavisnosti od tipa potrebno
je obojiti element u zelenu ili crvenu boju. Ukoliko korisnik klikne na neku od pretraga tada
se treba pozvati web servis na isti način kako bi se pozvao da korisnik unese istu pretragu
u edit text i pritisne dugme pretraži. Tada se umjesto liste prethodnih pretraga prikazuju
rezultati za navedenu pretragu kako ste radili i na prethodnoj vježbi.

You might also like