sqlite w systemie android. własny dostawca treści. · 1 sqlite w systemie android. własny...

23
1 SQLite w systemie Android. Własny dostawca treści. Materiał teoretyczny

Upload: nguyentu

Post on 05-Aug-2018

233 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

1

SQLite w systemie Android. Własny dostawca treści.

Materiał teoretyczny

Page 2: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

2

Spis treści I. Wprowadzenie ........................................................................................................................ 3

II. Tworzenie bazy danych .......................................................................................................... 5

III. Tworzenie dostawcy treści ................................................................................................. 6

IV. Tworzenie aktywności wykorzystujących bazę danych i zaimplementowanego

dostawcę treści ............................................................................................................................. 12

V. Bibliografia ............................................................................................................................ 22

Page 3: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

3

I. Wprowadzenie

Aplikacje (nie tylko) mobilne często korzystają ze źródeł danych, które są ulokowane albo

lokalnie w systemie, albo zdalnie na serwerach zewnętrznych. Udostępnianiem danych zawartych w

wymienionych źródłach zajmują się dostawcy treści. Dostawcy treści są wykorzystywani do

udostępniania danych na zewnątrz aplikacji lub do wymiany (współdzielenia) danych pomiędzy

aplikacjami. Najbardziej pospolitym przykładem źródła danych umieszczonego w dostawcy treści jest

baza danych SQLite („opakowana” przez dostawcę treści).

Android (podobnie jak iPhone OS czy Symbian) używa wbudowanej wersji sqlite3. Ta

uproszczona wersja bazy oferuje częściową obsługę wyzwalaczy (triggers) i pozwala na generowanie

większości złożonych zapytań (za wyjątkiem stosowania outer join; pozwala używać języka SQL w

standardzie SQL92). Wyzwalacze to procedury wykonywane w odpowiedzi na zdarzenia takie jak np.

dodanie czy usunięcie rekordu.

Silnik bazodanowy SQLite po kompilacji zajmuje nie więcej niż 275 KB. Jest stosunkowo szybki

(w porównaniu do popularnych baz danych opartych na modelu klient-serwer) oraz dostępny na

wiele platform programowych. SQLite obsługuje bazy danych o wielkości rzędu terabajtów a

kompletna baza przechowywana jest w pojedynczym pliku.

Istnieją jednak pewne „niedogodności” związane z niepełnym wsparciem standardu SQL-92,

które zostały przedstawione poniżej w postaci listy:

Brak zaimplementowanego pola FOREIGN KEY (klucz obcy), czyli pola, którego

wartość odpowiada kluczowi głównemu (PRIMARY KEY) w innej tabeli dla systemu

Android poniżej wersji 2.2;

Brak zaimplementowanych niektórych właściwości triggerów (wyzwalaczy) - w SQLite

pominięte zostały takie właściwości triggerów jak: FOR EACH STATEMENT (wszystkie

wyzwalacze muszą być FOR EACH ROW) i INSTEAD OF na tabelach (INSTEAD OF

możliwy tylko na widokach) ;

Brak niektórych wariantów polecenia ALTER TABLE - czyli polecenia zmieniającego

właściwości istniejącej tabeli. W SQLite wspierane są tylko dwa warianty tego

polecenia mianowicie zawierające atrybuty RENAME TABLE (zmiana nazwy tabeli) i

ADD COLUMN (dodanie kolumny). Pozostałe rodzaje operacji ALTER TABLE, takie jak

DROP COLUMN (usunięcie kolumny), czy ADD CONSTRAINT(dodanie ograniczenia)

zostały pominięte;

Brak obsługi transakcji zagnieżdżonych - w SQLite obecnie możliwe są tylko

pojedyncze transakcje;

Brak operacji łączenia prawostronnego (RIGHT OUTER JOIN) i pełnego (FULL OUTER

JOIN) - w SQLite obecnie można używać łączenia lewostronnego LEFT OUTER JOIN;

Page 4: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

4

Ograniczenia w operacjach na widokach - widoki (VIEWS) czyli wirtualne tabele w

SQLite są tylko do odczytu (nie można wykonywać na nich DELETE, UPDATE i INSERT);

Brak poleceń GRANT i REVOKE - komendy te służą do nadawania i odbierania

uprawnień użytkownikom. SQLite zapisuje i odczytuje dane bezpośrednio z pliku,

więc prawa dostępu nadawane są dla pliku z poziomu OS.

Baza danych SQLite udostępnia kilka typów podstawowych wymienionych poniżej:

INTEGER (1 do 8 bajtów) INT, INTEGER, TINYINT, SMALLINT, MEDIUMINT, BIGINT,

UNSIGNED BIG INT, INT2, INT8;

TEXT – typ tekstowy - VARCHAR(255), CLOB;

NONE – typ nieokreślony BLOB;

REAL – typ zmienno-przecinkowy – REAL, DOUBLE, DOUBLE PRECISION, FLOAT;

NUMERIC – typ stałoprzecinkowy – NUMERIC, DECIMAL(10,5), BOOLEAN, DATE,

DATETIME.

Bazy danych projektu aplikacji Android zapisywane są w katalogu:

/DATA/data/NAZWA_APLIKACJI/databases/NAZWA_BAZY

DATA jest ścieżką aplikacji, zwracaną po wywołaniu metody Environment.getDataDirectory().

NAZWA_APLIKACJI określa podaną w projekcie nazwę aplikacji. NAZWA_BAZY to nazwa pliku z

rozszerzeniem „.db”, w którym znajduje się baza danych aplikacji.

Pakiet android.database zawiera wszystkie klasy potrzebne do pracy z bazą danych,

natomiast pakiet android.database.sqlite zawiera klasy specyficzne dla SQLite.

Aby utworzyć lub zmodernizować bazą danych we własnej aplikacji Android, należy utworzyć

podklasę klasy SQLiteOpenHelper. W konstruktorze utworzonej podklasy należy wywołać metodę

super() dla klasy SQLiteOpenHelper, podając nazwę bazy danych i bieżącą jej wersję.

W podklasie rozszerzającej SQLiteOpenHelper należy przesłonić następujące metody, tak aby

istniała możliwość utworzenia i modernizacji bazy danych:

onCreate() – wywoływana przez framework, w przypadku gdy do bazy danych żądany

jest dostęp a sama baza nie została jeszcze utworzona;

onUpgrade() – wywoływana, jeżeli wersja bazy została inkrementowana w kodzie

aplikacji. Metoda ta pozwala na aktualizację bazy lub jej usunięcie a następnie

przywrócenie poprzez metodę onCreate().

Wymienione metody jako argument pobierają obiekt klasy SQLiteDatabase, będący

reprezentacją bazy danych w Javie. Klasa SQLiteOpenHelper dostarcza metody

Page 5: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

5

getReadableDatabase() i getWritableDatabase() służące do dostępu do obiektu klasy SQLiteDatabase

odpowiednio w trybie odczytu i zapisu.

Tabele bazy danych powinny używać identyfikatora „_id” jako klucza głównego (kilka funkcji

Android bazuje na tym standardzie). Dodatkowo dobrą praktyką jest tworzenie oddzielnej klasy dla

każdej z tabeli bazy danych. Klasa ta powinna mieć statyczne definicje metod onCreate() i

onUpgrade(), które są wywoływane jako odpowiednie metody podklasy SQLiteOpenHelper. W ten

sposób implementacja podklasy SQLiteOpenHelper pozostaje czytelna, nawet w przypadku kilku

tabel zawartych w bazie danych. W kolejnej części omówiono przykładową procedurę tworzenia bazy

danych oraz implementacji podklasy SQLiteOpenHelper.

II. Tworzenie bazy danych

Pierwszym krokiem w omawianej procedurze tworzenia i obsługi bazy danych jest

implementacja klasy odzwierciedlającej tabelę w bazie danych. W prezentowanym przykładzie

utworzona została aplikacja zarządzająca listą rzeczy do zrobienia, na podstawie [6].

Aplikacja składa się z dwóch aktywności: jednej widocznej jako lista wszystkich rzeczy do

zrobienia, druga – widoczna jako ekran edycji lub tworzenia nowego elementu listy. Obie aktywności

komunikują się za pomocą intencji. Do asynchronicznej pracy z bazą danych wykorzystano idee

kursora zawartości (klasa Cursor) oraz klasę Loader – służącą do asynchronicznej pracy z bazą danych.

Poniżej zaprezentowano implementację klasy TabelaNotatki, odzwierciedlającej tabelę w

bazie danych aplikacji.

public class TabelaNotatki { public static final String TABLE_NOTATKI = "notatki"; public static final String COLUMN_ID = "_id"; public static final String COLUMN_CATEGORY = "kategoria"; public static final String COLUMN_SUMMARY = "streszczenie"; public static final String COLUMN_DESCRIPTION = "opis"; // wyrażenie opisujące tworzenie bazy danych private static final String DATABASE_CREATE = "create table " + TABLE_NOTATKI + "(" + COLUMN_ID + " integer primary key autoincrement, " + COLUMN_CATEGORY + " text not null, " + COLUMN_SUMMARY + " text not null," + COLUMN_DESCRIPTION + " text not null" + ");"; public static void onCreate(SQLiteDatabase database) { database.execSQL(DATABASE_CREATE); } public static void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { Log.w(TabelaNotatki.class.getName(), "Aktualizuję bazę z wersji " + oldVersion + " do wersji " + newVersion + ", co spowoduje usunięcie wszystkich danych.");

Page 6: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

6

database.execSQL("DROP TABLE IF EXISTS " + TABLE_NOTATKI); onCreate(database); }

}

W klasie TabelaNotatki zdefiniowano publiczne statyczne pola opisujące tabelę „notatki”.

Następnie utworzona została klasa NotatkiDatabaseHelper będąca podklasą SQLiteOpenHelper. Klasa

NotatkiDatabaseHelper zawiera wywołania statycznych metod klasy TabelaNotatki. Implementacja

tej podklasy została przedstawiona poniżej.

public class NotatkiDatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "todotable.db"; private static final int DATABASE_VERSION = 1; public NotatkiDatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } // metoda wywoływana podczas tworzenia bazy @Override public void onCreate(SQLiteDatabase database) { TabelaNotatki.onCreate(database); } //metoda wywoływana podczas modernizacji bazy @Override public void onUpgrade(SQLiteDatabase database, int oldVersion, int newVersion) { TabelaNotatki.onUpgrade(database, oldVersion, newVersion); }

}

Jak widać na powyższym kodzie klasy, plik bazy danych nosi nazwę „todotable.db”.

Konstruktor klasy wywołuje w swoim ciele konstruktor nadklasy („super”), który pobiera jako

parametry: kontekst, nazwę pliku bazy danych, referencję do obiektu fabryki kursorów treści

(domyślna wartość: null) oraz numer wersji bazy danych. W ciałach metod onCreate oraz onUpgrade

znajdują się wywołania odpowiednich statycznych metod klasy TabelaNotatki, związanych z

tworzeniem oraz modernizacją bazy danych.

W kolejnych krokach dokonano implementacji dostawy treści oraz aktywności

wykorzystujących utworzoną bazę i dostawcę treści.

III. Tworzenie dostawcy treści

Baza danych SQLite jest przeznaczona do użytku prywatnego dla aplikacji, która ją stworzyła.

Aby udostępnić dane zawarte w tej bazie innym aplikacjom, można wykorzystać dostawcę treści.

Chociaż dostawca treści może być użyty wewnątrz aplikacji do dostępu do danych, to jego głównym

Page 7: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

7

celem jest współdzielenie danych z innymi aplikacjami. Dostawca treści przed wykorzystaniem musi

zostać zadeklarowany w deskryptorze aplikacji.

Dostęp do dostawcy treści odbywa się za pośrednictwem identyfikatora URI, który

jednoznacznie określa dostawcę. Struktura tego identyfikatora przypomina identyfikator URI

protokołu HTTP. Ogólna struktura identyfikatora URI przedstawia się następująco:

content://authority-name/path-segment/…

content – jest elementem określającym dostawcę treści,

authority-name – jest niepowtarzalnym identyfikatorem upoważnienia używanym

do zlokalizowania dostawcy w rejestrze dostawców,

path-segment – to człon określający ścieżkę dostępu do danych (inną dla każdego

dostawcy); człon ten może być powtarzany wielokrotnie.

Przykład poniżej przedstawia wywołanie listy kontaktów z podaniem identyfikatora jednego

kontaktu:

content://com.android.contacts/contacts/lookup/0n293F33292B314F2929292929/2

Dla dostawców wbudowanych (com.android) nie trzeba używać całego identyfkator,

wystarczy wskazać odpowiednie słowo: content://contacts/contacts/1.

W dalszej części omówiono przykład implementacji własnego dostawcy treści, który

wykorzystuje zdefiniowaną wcześniej bazę danych.

Aby utworzyć własnego dostawcę treści należy utworzyć klasę, która rozszerza klasę

android.content.ContentProvider. Dodatkowo w pliku AndroidManifest.xml aplikacji należy

zadeklarować utworzonego dostawcę treści, tak by był dostępny w rejestrze dostawców. Wpis w

pliku deskryptora aplikacji, rejestrujący dostawcę, określa nazwę dostawcy (parametr android:name)

oraz identyfikator (parametr android:authorities).

Tworzony dostawca treści musi również implementować kilka metod, np. query(), insert(),

update(), delete(), getType() oraz onCreate(). W przypadku braku obsługi określonych metod, dobrą

praktyką jest wywołanie wyjątku klasy UnsupportedOperationException(). Dodatkowo metoda

query() musi zwracać obiekt klasy Cursor.

Poniżej zaprezentowany został pierwszy fragment implementacji klasy MyContentProvider,

będącej dostawcą treści, który obsługuje utworzoną wcześniej bazę danych.

public class MyContentProvider extends ContentProvider { // baza danych private NotatkiDatabaseHelper database; // pola wykorzystane przez obiekt klasy UriMatcher private static final int TODOS = 10; private static final int TODO_ID = 20;

Page 8: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

8

private static final String AUTHORITY = "com.example.mk3_ap4.contentprovider"; private static final String BASE_PATH = "todos"; public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH); public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE + "/todos"; public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE + "/todo"; // utworzenie obiektu urimatchera private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH); static { sURIMatcher.addURI(AUTHORITY, BASE_PATH, TODOS); sURIMatcher.addURI(AUTHORITY, BASE_PATH + "/#", TODO_ID);

}

W przedstawionym powyżej fragmencie kodu zadeklarowano obiekt klasy

NotatkiDatabaseHelper, który jest łącznikiem pomiędzy dostawcą treści a silnikiem bazy danych

aplikacji. Za jego pomocą w dalszych implementacjach metod dostawcy treści odbywać się będzie

aktualizacja danych w bazie danych. Dodatkowo zainicjowano stałe wykorzystywane przez obiekt

UriMatcher, który wykorzystywany jest w dalszych metodach tej klasy do sprawdzania ścieżek Uri

modyfikowanych przez dostawcę treści wartości. W końcowej części przedstawionego fragmentu

utworzono obiekt klasy UriMatcher, przechowujący odpowiednie identyfikatory, ścieżkę do bazy

danych oraz wartość przechowywaną w rejestrze dostawców treści (zdefiniowaną później w pliku

deskryptora aplikacji).

Następnie zaimplementowano kolejne metody dostawcy treści przedstawione w dalszej

kolejności.

@Override public boolean onCreate() { database = new NotatkiDatabaseHelper(getContext()); return false; }

W metodzie onCreate tworzony jest nowy obiekt bazy danych za pomocą konstruktora klasy

NotatkiDatabaseHelper. Kolejną zaimplementowaną metodą dostawcy treści jest metoda query,

której ciało przedstawiono poniżej.

@Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {

Page 9: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

9

// obiekt budujący zapytanie SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); // sprawdza czy rozmówca wysłał żądanie o kolumny, które nie istnieją checkColumns(projection); // ustawienie tabeli queryBuilder.setTables(TabelaNotatki.TABLE_NOTATKI); int uriType = sURIMatcher.match(uri); switch (uriType) { case TODOS: break; case TODO_ID: // dodanie ID do oryginalnej ścieżki queryBuilder.appendWhere(TabelaNotatki.COLUMN_ID + "=" + uri.getLastPathSegment()); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } SQLiteDatabase db = database.getWritableDatabase(); Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); // upewnienie się, że potencjalni słuchacze zostali poinformowani cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; }

Metoda query służy do obsługi zapytań pochodzących od klientów i może być wywoływana z

różnych wątków. Do budowy zapytania wykorzystany został mechanizm oferowany przez klasę

SQLiteQueryBuilder. Metoda pobiera parametry w postaci: adresu URI zapytania (Uri uri), listy

kolumn, które mają być przekazane do obiektu kursora treści (String[] projection), kryterium selekcji

wierszy tabeli (String selection), lity argumentów kryterium selekcji wierszy (String[] selectionArgs)

oraz porządek sortowania (String sortOrder). Metoda query zwraca obiekt klasy Cursor, który

pozwala nawigować pomiędzy wynikami zapytania do bazy danych.

@Override public String getType(Uri uri) { return null; }

Metoda obsługująca zwracanie różnych typów MIME dla danego adresu URI. Jej

implementacja jest wymagana, jednak w tym przypadku nie istnieje potrzeba implementacji obsługi

zwracania typów elementów. W takim wypadku metoda zwraca wartość „null”.

Page 10: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

10

@Override public Uri insert(Uri uri, ContentValues values) { int uriType = sURIMatcher.match(uri); SQLiteDatabase sqlDB = database.getWritableDatabase(); long id = 0; switch (uriType) { case TODOS: id = sqlDB.insert(TabelaNotatki.TABLE_NOTATKI, null, values); break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return Uri.parse(BASE_PATH + "/" + id); }

Kolejną zaimplementowaną metodą dostawy treści jest metoda insert, która służy do

dodawania nowego elementu określonego URI do bazy danych za pomocą obiektu klasy

ContentValues. W wyniku metoda ta zwraca adres URI wstawionego elementu.

@Override public int delete(Uri uri, String selection, String[] selectionArgs) { int uriType = sURIMatcher.match(uri); SQLiteDatabase sqlDB = database.getWritableDatabase(); int rowsDeleted = 0; switch (uriType) { case TODOS: rowsDeleted = sqlDB.delete(TabelaNotatki.TABLE_NOTATKI, selection, selectionArgs); break; case TODO_ID: String id = uri.getLastPathSegment(); if (TextUtils.isEmpty(selection)) { rowsDeleted = sqlDB.delete(TabelaNotatki.TABLE_NOTATKI, TabelaNotatki.COLUMN_ID + "=" + id, null); } else { rowsDeleted = sqlDB.delete(TabelaNotatki.TABLE_NOTATKI, TabelaNotatki.COLUMN_ID + "=" + id + " and " + selection, selectionArgs); } break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return rowsDeleted; }

Następnie zaimplementowana została metoda delete, służąca do usuwania wybranego

elementu (lub elementów) z bazy. Metoda pobiera parametry w postaci: adresu URI zapytania,

Page 11: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

11

kryterium wyboru elementów oraz listy argumentów kryterium wyboru. Po przekazaniu sterowania

do głównej pętli programu zwracana jest liczba usuniętych wierszy.

@Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { int uriType = sURIMatcher.match(uri); SQLiteDatabase sqlDB = database.getWritableDatabase(); int rowsUpdated = 0; switch (uriType) { case TODOS: rowsUpdated = sqlDB.update(TabelaNotatki.TABLE_NOTATKI, values, selection, selectionArgs); break; case TODO_ID: String id = uri.getLastPathSegment(); if (TextUtils.isEmpty(selection)) { rowsUpdated = sqlDB.update(TabelaNotatki.TABLE_NOTATKI, values, TabelaNotatki.COLUMN_ID + "=" + id, null); } else { rowsUpdated = sqlDB.update(TabelaNotatki.TABLE_NOTATKI, values, TabelaNotatki.COLUMN_ID + "=" + id + " and " + selection, selectionArgs); } break; default: throw new IllegalArgumentException("Unknown URI: " + uri); } getContext().getContentResolver().notifyChange(uri, null); return rowsUpdated; }

Następnie zaimplementowano metodę update odpowiedzialną za aktualizację danych w

bazie. Metoda pobiera parametry w postaci: pełnego adresu URI zapytania, kolekcji wartości do

aktualizacji przekazaną w postaci obiektu klasy ContentValues, kryterium wyboru wierszy do

aktualizacji wartości oraz listy argumentów tego kryterium. Metoda zwraca liczbę zaktualizowanych

wierszy tabeli.

private void checkColumns(String[] projection) { String[] available = { TabelaNotatki.COLUMN_CATEGORY, TabelaNotatki.COLUMN_SUMMARY, TabelaNotatki.COLUMN_DESCRIPTION, TabelaNotatki.COLUMN_ID }; if (projection != null) {

Page 12: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

12

HashSet<String> requestedColumns = new HashSet<String>(Arrays.asList(projection)); HashSet<String> availableColumns = new HashSet<String>(Arrays.asList(available)); // Sprawdzenie czy wszystkie kolumny, które są wymagane, są dostępne if (!availableColumns.containsAll(requestedColumns)) { throw new IllegalArgumentException("Unknown columns in projection"); } }

}

Ostatnią metodą zaimplementowaną w dostawcy treści jest metoda checkColumns,

wykorzystywana w metodzie query do sprawdzenia poprawności żądania klienta odnośnie kolumn,

które mają zostać przekazane do obiektu kursora (klasa Cursor).

Dostawca treści, aby mógł być wykorzystany, musi zostać zarejestrowany w manifeście

aplikacji. Dla zaimplementowanego wcześniej dostawcy fragment odpowiedzialny za jego rejestrację

w pliku AndroidManifest.xml przedstawiono poniżej.

<provider android:name="com.example.mk3_ap4.MyContentProvider" android:authorities="com.example.mk3_ap4.contentprovider" > </provider>

W kolejnym rozdziale przedstawiono implementację aktywności oraz układu graficznego

aplikacji wykorzystującej utworzonego dostawcę treści oraz bazę danych notatek.

IV. Tworzenie aktywności wykorzystujących bazę danych i zaimplementowanego

dostawcę treści

Zanim przedstawiona zostanie implementacja aktywności aplikacji, pokazane zostaną

definicje wykorzystywanych przez nie zasobów.

Poniżej przedstawiono definicję menu dostępnego w pasku akcji (ActionBar) w postaci napisu

Dodaj, zdefiniowanego w pliku strings.xml. Za wyświetlenie elementu menu na pasku zadań

odpowiedzialny jest argument android:showAsAction=”always”. Definicja pliku menu znajduje się w

katalogu /res/menu.

<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android" > <item android:id="@+id/insert" android:showAsAction="always" android:title="@string/insert"> </item>

Page 13: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

13

</menu>

Następnie w katalogu /res/values zdefiniowano tablicę napisów opisujących priorytety

dodawanych do listy elementów. Lista składa się z dwóch elementów, których napisy zdefiniowano w

pliku „strings.xml”.

<?xml version="1.0" encoding="utf-8"?> <resources> <string-array name="priorities"> <item>@string/urgent</item> <item>@string/reminder</item> </string-array> </resources>

Wygląd pliku “strings.xml” został przedstawiony poniżej. Znajdują się w nim wszystkie napisy

wykorzystywane podczas funkcjonowania aktywności.

<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">MK3_AP4</string> <string name="action_settings">Ustawienia</string> <string name="insert">Dodaj</string> <string name="urgent">Pilne</string> <string name="reminder">Przypomnienie</string> <string name="no_todos">Brak rzeczy do zrobienia</string> <string name="menu_insert">Dodaj element</string> <string name="menu_delete">Usuń element</string> <string name="todo_summary">Streszczenie</string> <string name="todo_description">Usuń wpis</string> <string name="todo_edit_summary">Streszczenie</string> <string name="todo_edit_description">Opis</string> <string name="todo_edit_confirm">Zatwierdź</string> <string name="title_activity_edit">EditActivity</string> <string name="hello_world">Hello world!</string> </resources>

Z kolei na kolejnym fragmencie kodu przedstawiono definicję XML układu graficznego

pojedynczego wiersza listy elementów, składającego się z kontrolki TextView. Plik z definicją wiersza

znajduje się w katalogu /res/layout.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/label" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="6dp" android:lines="1" android:text="@+id/TextView01" android:textSize="24sp" >

Page 14: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

14

</TextView> </LinearLayout>

Poniżej z kolej przedstawiono definicję układu graficznego listy notatek, w którym znajduje

się kontrolka ListView oraz TextView, wyświetlająca odpowiedni napis w przypadku pustej listy

elementów.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="wrap_content" > </ListView> <TextView android:id="@android:id/empty" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/no_todos" /> </LinearLayout>

Kolejny fragment przedstawia definicję układu graficznego ekranu

edycji/dodawania/aktualizacji elementu listy. Układ ten powiązany jest z aktywnością, do której

przekazywane jest sterowanie z głównej aktywności aplikacji. W interfejsie zdefiniowano kontrolkę

Spinner (do wyboru priorytetu notatki), pola EditText oraz kontrolkę Button.

<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <Spinner android:id="@+id/category" android:layout_width="wrap_content" android:layout_height="wrap_content" android:entries="@array/priorities" > </Spinner> <LinearLayout android:id="@+id/LinearLayout01" android:layout_width="match_parent" android:layout_height="wrap_content" > <EditText android:id="@+id/todo_edit_summary" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_weight="1"

Page 15: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

15

android:hint="@string/todo_edit_summary" android:imeOptions="actionNext" > </EditText> </LinearLayout> <EditText android:id="@+id/todo_edit_description" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_weight="1" android:gravity="top" android:hint="@string/todo_edit_description" android:imeOptions="actionNext" > </EditText> <Button android:id="@+id/todo_edit_button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/todo_edit_confirm" > </Button> </LinearLayout>

Implementacja głównej aktywności została przedstawiona poniżej. Do obsługi kontrolki

ListView wykorzystany został adapter klasy SimpleCursorAdapter. Aktywność główna rozszerza klasę

ListActivity oraz implementuje interfejs LoaderManager.LoaderCallbacks do asynchronicznej obsługi

kursorów treści. Mechanizm ten zabezpiecza aplikację przez zawieszeniem w przypadku długiego

oczekiwania na wynik z bazy danych lub błędu zapytania.

public class MainActivity extends ListActivity implements LoaderManager.LoaderCallbacks<Cursor> { private static final int ACTIVITY_CREATE = 0; private static final int ACTIVITY_EDIT = 1; private static final int DELETE_ID = Menu.FIRST + 1; private SimpleCursorAdapter adapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.notatki_list); this.getListView().setDividerHeight(2); fillData(); registerForContextMenu(getListView()); } // Tworzenie menu bazującego na definicji w pliku XML @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.listmenu, menu); return true; }

Page 16: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

16

// Reakacja na wybór elementu menu @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.insert: createTodo(); return true; } return super.onOptionsItemSelected(item); } @Override public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) { case DELETE_ID: AdapterContextMenuInfo info = (AdapterContextMenuInfo) item .getMenuInfo(); Uri uri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + info.id); getContentResolver().delete(uri, null, null); fillData(); return true; } return super.onContextItemSelected(item); } private void createTodo() { Intent i = new Intent(this, EditActivity.class); startActivity(i); } // Wywołanie drugiej aktywności po kliknięciu na element listy @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); Intent i = new Intent(this, EditActivity.class); Uri todoUri = Uri.parse(MyContentProvider.CONTENT_URI + "/" + id); i.putExtra(MyContentProvider.CONTENT_ITEM_TYPE, todoUri); startActivity(i); } private void fillData() { // Pola z bazy danych (projekcja) // Zmienna musi zawierać identyfikatory (_id) kolumn aby adapter działała prawidłowo String[] from = new String[] { TabelaNotatki.COLUMN_SUMMARY }; // Pola interfejsu do zmapowania z adapterem int[] to = new int[] { R.id.label }; getLoaderManager().initLoader(0, null, this); adapter = new SimpleCursorAdapter(this, R.layout.notatki_row, null, from, to, 0);

Page 17: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

17

setListAdapter(adapter); } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); menu.add(0, DELETE_ID, 0, R.string.menu_delete); } // Tworzenie nowego loadera po wywołaniu metody initLoader() @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String[] projection = { TabelaNotatki.COLUMN_ID, TabelaNotatki.COLUMN_SUMMARY }; CursorLoader cursorLoader = new CursorLoader(this, MyContentProvider.CONTENT_URI, projection, null, null, null); return cursorLoader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { adapter.swapCursor(data); } @Override public void onLoaderReset(Loader<Cursor> loader) { // dane nie są już dostępne, usunięcie referencji adapter.swapCursor(null); }

}

Implementacja aktywności edycji elementu została przedstawiona poniżej. Komunikacja

pomiędzy aktywnością główną (MainActivity) a przedstawioną poniżej odbywa się za pomocą obiektu

Intent, w którym następuje przekazanie wartości pojedynczego elementu listy notatek.

public class EditActivity extends Activity { private Spinner mCategory; private EditText mTitleText; private EditText mBodyText; private Uri todoUri; @Override protected void onCreate(Bundle bundle) { super.onCreate(bundle); setContentView(R.layout.activity_edit); mCategory = (Spinner) findViewById(R.id.category); mTitleText = (EditText) findViewById(R.id.todo_edit_summary); mBodyText = (EditText) findViewById(R.id.todo_edit_description); Button confirmButton = (Button) findViewById(R.id.todo_edit_button); Bundle extras = getIntent().getExtras();

Page 18: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

18

// sprawdzenie danych z zapisanej instancji todoUri = (bundle == null) ? null : (Uri) bundle .getParcelable(MyContentProvider.CONTENT_ITEM_TYPE); // lub przekazanych przez inną aktywność if (extras != null) { todoUri = extras .getParcelable(MyContentProvider.CONTENT_ITEM_TYPE); fillData(todoUri); } confirmButton.setOnClickListener(new View.OnClickListener() { public void onClick(View view) { if (TextUtils.isEmpty(mTitleText.getText().toString())) { makeToast(); } else { setResult(RESULT_OK); finish(); } } }); } private void fillData(Uri uri) { String[] projection = { TabelaNotatki.COLUMN_SUMMARY, TabelaNotatki.COLUMN_DESCRIPTION, TabelaNotatki.COLUMN_CATEGORY }; Cursor cursor = getContentResolver().query(uri, projection, null, null, null); if (cursor != null) { cursor.moveToFirst(); String category = cursor.getString(cursor .getColumnIndexOrThrow(TabelaNotatki.COLUMN_CATEGORY)); for (int i = 0; i < mCategory.getCount(); i++) { String s = (String) mCategory.getItemAtPosition(i); if (s.equalsIgnoreCase(category)) { mCategory.setSelection(i); } } mTitleText.setText(cursor.getString(cursor .getColumnIndexOrThrow(TabelaNotatki.COLUMN_SUMMARY))); mBodyText.setText(cursor.getString(cursor .getColumnIndexOrThrow(TabelaNotatki.COLUMN_DESCRIPTION))); // zamknięcie kursora cursor.close(); } } protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); saveState(); outState.putParcelable(MyContentProvider.CONTENT_ITEM_TYPE, todoUri); }

Page 19: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

19

@Override protected void onPause() { super.onPause(); saveState(); } private void saveState() { String category = (String) mCategory.getSelectedItem(); String summary = mTitleText.getText().toString(); String description = mBodyText.getText().toString(); // Zapisanie tylko gdy pole summary i descriptions są wypełnione if (description.length() == 0 && summary.length() == 0) { return; } ContentValues values = new ContentValues(); values.put(TabelaNotatki.COLUMN_CATEGORY, category); values.put(TabelaNotatki.COLUMN_SUMMARY, summary); values.put(TabelaNotatki.COLUMN_DESCRIPTION, description); if (todoUri == null) { // Nowy element todoUri = getContentResolver().insert(MyContentProvider.CONTENT_URI, values); } else { // Aktualizacja elementu getContentResolver().update(todoUri, values, null, null); } } private void makeToast() { Toast.makeText(EditActivity.this, "Proszę wprowadzić streszczenie", Toast.LENGTH_LONG).show(); }

}

Na rys. 1 przedstawiono ekran aplikacji z pustą listą notatek. U góry po prawej na pasku

ActionBar znajduje się napis Dodaj. Jego kliknięcie powoduje wywołanie aktywności dodawania

elementu.

Page 20: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

20

Rys. 1 Uruchomienie programu

Rys. 2 przedstawia ekran dodawania/edycji elementu listy w postaci notatki o podanym

priorytecie (kontrolka Spinner). Odpowiednie wypełnienie poszczególnych pól ekranu i kliknięcie w

przycisk „Zatwierdź” powoduje dodanie/aktualizację notatki.

Page 21: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

21

Rys. 2 Ekran edycji

Na rys. 3 przedstawiony został ekran aplikacji w przypadku próby usunięcie zaznaczonego na

liście elementu (notatki). Przyciśnięcie napisu „Usuń element” powoduje usunięcie notatki z listy.

Page 22: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

22

Rys. 3 Usuwanie elementu

Przedstawiony tu przykład implementacyjny ma na celu wprowadzenie odbiorcę w

problematykę tworzenia adapterów baz danych oraz własnych dostawców treści w środowisku

aplikacji Android. Treści przekazane w ramach tej pracy są jednie wstępem do dalszego poszukiwania

informacji na ten temat w literaturze fachowej.

V. Bibliografia

1. Arsoba, R. (2011). Programowanie urządzeń mobilnych. Zagadnienia podstawowe. Pobrano

Czerwiec 12, 2012 z lokalizacji

http://grafika.weii.tu.koszalin.pl/android/Programowanie_Android.pdf

2. Conder S., D. L. (2011). Android. Programowanie aplikacji na urządzenia przenośne. Wydanie

II. Gliwice: Helion.

3. Geetha, S. (2011, Maj 17). Sai Geetha's Blog - Android. Pobrano Czerwiec 20, 2013 z

lokalizacji Sai Geetha's Blog: http://saigeethamn.blogspot.in/2011/05/contacts-api-20-and-

above-android.html

4. Komatineni S., M. D. (2012). Android 3. Tworzenie aplikacji. Gliwice: Helion.

Page 23: SQLite w systemie Android. Własny dostawca treści. · 1 SQLite w systemie Android. Własny dostawca treści. ... Pakiet android.database zawiera wszystkie klasy potrzebne do pracy

23

5. Lee, W.-M. (2011). Beginning Android Application Development. Indianapolis: Wiley

Publishing Inc.

6. Vogel, L. (2013, sierpień 19). Android SQL database and content provider - tutorial. Pobrano z

lokalizacji

http://www.vogella.com/articles/AndroidSQLite/article.html#contentprovider_own