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

Podobne dokumenty
Programowanie urządzeń mobilnych. dr inż. Andrzej Grosser na podstawie wykładu dr inż. Juliusza Mikody

Systemy operacyjne na platformach mobilnych

akademia androida Składowanie danych część VI

Programowanie w języku Java. Bazy danych SQLite w Javie

[Android] Podstawy programowania

Fragmenty są wspierane od Androida 1.6

Programowanie aplikacji dla technologii mobilnych. mgr inż. Anton Smoliński

Aktywności są związane z ekranem i definiują jego wygląd. Dzieje się to poprzez podpięcie do aktywności odpowiedniego widoku.

DATA STORAGE. wprowadzenie Michał Mynarski. środa, 26 października 11

Programowanie urządzeń mobilnych w systemie Android. Ćwiczenie 7 Wykorzystanie układu LinearLayout

Programowanie urządzeń mobilnych. dr inż. Juliusz Mikoda

Język SQL, zajęcia nr 1

Programowanie aplikacji dla technologii mobilnych. mgr inż. Anton Smoliński

Android pierwsza aplikacja

Laboratorium Systemów Mobilnych. Wykład 1

Bazy danych. Bazy danych. Podstawy języka SQL. Dr inż. Paweł Kasprowski.

Ciekawym rozwiązaniem służącym do obsługi zdarzeń dla kilku przycisków w ramach jednej aktywności może być następujący kod:

Wprowadzenie do BD Operacje na bazie i tabelach Co poza zapytaniami? Algebra relacji. Bazy Danych i Systemy informacyjne Wykład 2.

Aby uruchomić program klienta i połączyć się z serwerem, należy komendę:

Powiadomienia w systemie Android

AndroidManifest.xml. Plik manifestu opisuje podstawowe charakterystyki aplikacji i definiuje jej komponenty.

Bazy danych. Wykład IV SQL - wprowadzenie. Copyrights by Arkadiusz Rzucidło 1

Obsługa SMS i telefonii

Programowanie w Ruby

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Laboratorium Systemów Mobilnych. Wykład 2

DECLARE VARIABLE zmienna1 typ danych; BEGIN

Android - Kontakty. Łukasz Dudzioski

Programowanie Urządzeń Mobilnych. Część II: Android. Wykład 2

Ref. 7 - Język SQL - polecenia DDL i DML

Blaski i cienie wyzwalaczy w relacyjnych bazach danych. Mgr inż. Andrzej Ptasznik

PHP: bazy danych, SQL, AJAX i JSON

Tworzenie wydajnych interfejsów. Autorzy: Piotr Michałkiewicz, 2 rok AiR Daniel Maksymow, 2 rok Informatyki

Programowanie w Ruby

Wykład 4. SQL praca z tabelami 1

UML a kod w C++ i Javie. Przypadki użycia. Diagramy klas. Klasy użytkowników i wykorzystywane funkcje. Związki pomiędzy przypadkami.

akademia androida Intencje oraz URI część III

Laboratorium 7 Blog: dodawanie i edycja wpisów

Przechowywanie danych

Instrukcja implementacji sterownika wirtualnego portu szeregowego dla systemu Android. Opracowanie: Elzab Soft sp. z o.o.

Bazy danych. Dr inż. Paweł Kasprowski

Java. język programowania obiektowego. Programowanie w językach wysokiego poziomu. mgr inż. Anna Wawszczak

Projektowanie systemów baz danych

Bazy danych 7. SQL podstawy

Paweł Cieśla. Dokumentacja projektu

Zaawansowane aplikacje WWW - laboratorium

NARZĘDZIA WIZUALIZACJI

D D L S Q L. Co to jest DDL SQL i jakie s jego ą podstawowe polecenia?

1. Połączenie z bazą danych. W wybranym edytorze tworzymy plik sqltest.py i umieszczamy w nim poniższy kod. #!/usr/bin/python3 import sqlite3

Wykład 5: PHP: praca z bazą danych MySQL

Laboratorium nr 4. Temat: SQL część II. Polecenia DML

Laboratorium 03: Podstawowe konstrukcje w języku Java [2h]

Ćwiczenia laboratoryjne nr 11 Bazy danych i SQL.

PHP 5 język obiektowy

Języki programowania wysokiego poziomu. PHP cz.4. Bazy danych

Wdrożenie modułu płatności eservice. dla systemu Zen Cart

Systemy operacyjne na platformach mobilnych

Autor: Joanna Karwowska

Inżynieria Programowania Laboratorium 3 Projektowanie i implementacja bazy danych. Paweł Paduch paduch@tu.kielce.pl

Instrukcja podwaja zarobki osób, których imiona zaczynają się P i dalsze litery alfabetu zakładamy, że takich osbób jest kilkanaście.

Programowanie urządzeń mobilnych. dr inż. Andrzej Grosser na podstawie wykładu dr inż. Juliusza Mikody

Odnawialne Źródła Energii I rok. Tutorial PostgreSQL

Programowanie w SQL procedury i funkcje. UWAGA: Proszę nie zapominać o prefiksowaniu nazw obiektów ciągiem [OLIMP\{nr indeksu}] Funkcje użytkownika

Bazy Danych - Instrukcja do Ćwiczenia laboratoryjnego nr 8

Dokumentacja do API Javy.

E.14 Bazy Danych cz. 18 SQL Funkcje, procedury składowane i wyzwalacze

Wdrożenie modułu płatności eservice. dla systemu oscommerce 2.3.x

Instytut Mechaniki i Inżynierii Obliczeniowej Wydział Mechaniczny Technologiczny Politechnika Śląska

akademia androida Service, BroadcastReceiver, ContentProvider część IV

Życie aktywności Nawigując przez aplikacje poszczególne Aktywności przechodzą pomiędzy stanami. Dla przykładu gdy aktywność uruchamia się po raz

PRZESTRZENNE BAZY DANYCH WYKŁAD 2

Wykład 8. SQL praca z tabelami 5

Instrukcja 10 Laboratorium 13 Testy akceptacyjne z wykorzystaniem narzędzia FitNesse

Kowalski Marcin Wrocław, dn Jaśkiewicz Kamil Bazy Danych 1 Podstawy Projekt Temat: Baza danych do zarządzania projektami

Wprowadzenie do projektu QualitySpy

ASP.NET MVC. Podstawy. Zaawansowane programowanie internetowe Instrukcja nr 3

Programowanie obiektowe

Programowanie obiektowe

Podstawowe wykorzystanie Hibernate

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Hurtownia Świętego Mikołaja projekt bazy danych

Wykład 7: Pakiety i Interfejsy

Programowanie urządzeń mobilnych. dr inż. Andrzej Grosser na podstawie wykładu dr inż. Juliusza Mikody

Bazy danych dla producenta mebli tapicerowanych. Bartosz Janiak Marcin Sikora Wrocław r.

Aplikacje w środowisku Java

Programowanie usług działających w tle

15. Funkcje i procedury składowane PL/SQL

Bazy danych 10. SQL Widoki

Bazy danych. Bazy danych. Zapytania SELECT. Dr inż. Paweł Kasprowski.

Projekt Hurtownia, realizacja skojarzeń dostawców i produktów

Projekt Hurtownia, realizacja rejestracji dostaw produktów

Polimorfizm, metody wirtualne i klasy abstrakcyjne

Dokumentacja SQL API 1

Projektowanie bazy danych. Jarosław Kuchta Projektowanie Aplikacji Internetowych

Programowanie obiektowe

Metody dostępu do danych

Przykład 3 Zdefiniuj w bazie danych hurtownia_nazwisko przykładową funkcję użytkownika fn_rok;

Mechanizm zapisu preferencji

Aplikacje bazodanowe. Laboratorium 1. Dawid Poªap Aplikacje bazodanowe - laboratorium 1 Luty, 22, / 37

Programowanie urządzeń mobilnych w systemie Android. Ćwiczenie 8 Wykorzystanie układu RelativeLayout

Transkrypt:

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

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 2

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; 3

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 4

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."); 5

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 public void oncreate(sqlitedatabase database) { TabelaNotatki.onCreate(database); //metoda wywoływana podczas modernizacji bazy 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 6

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; 7

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. 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. public Cursor query(uri uri, String[] projection, String selection, String[] selectionargs, String sortorder) { 8

// 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. 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. 9

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. 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, 10

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. 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) { 11

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> 12

</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" > 13

</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" 14

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; 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 public boolean oncreateoptionsmenu(menu menu) { MenuInflater inflater = getmenuinflater(); inflater.inflate(r.menu.listmenu, menu); return true; 15

// Reakacja na wybór elementu menu public boolean onoptionsitemselected(menuitem item) { switch (item.getitemid()) { case R.id.insert: createtodo(); return true; return super.onoptionsitemselected(item); 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 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 ; from, getloadermanager().initloader(0, null, this); adapter = new SimpleCursorAdapter(this, R.layout.notatki_row, null, to, 0); 16

setlistadapter(adapter); 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() 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; public void onloadfinished(loader<cursor> loader, Cursor data) { adapter.swapcursor(data); 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; 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(); 17

// 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); 18

values); 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, 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. 19

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. 20

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. 21

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-andabove-android.html 4. Komatineni S., M. D. (2012). Android 3. Tworzenie aplikacji. Gliwice: Helion. 22

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 23