niedziela, 18 lipca 2010

Coś dla początkujących - NotePad tutorial

Wstęp
Wszystkim, którzy dopiero rozpoczynają przygodę z Androidem szczególnie polecam Notepad tutorial. Znajdują się tam trzy proste ćwiczenia, dzięki którym w łatwy i szybki sposób można poznać podstawy programowania na platformę Android.

Ponieważ potrzebowałem jakiegoś prostego przykładu aby opisywać bardziej zaawansowane rzeczy stwierdziłem, że nie będę pisał od podstaw nowego szkieletu tylko wykorzystam Notepad tutorial. Po pierwsze dlatego, że jest dobrze napisany, po drugie - dobrze objaśniony, a po trzecie - zawiera właśnie to czego potrzebowałem. Źródła ćwiczeń z rozwiązaniami można ściągnąć stąd.

Drobne zmiany
Jednak nie mogę się powstrzymać przed pewnymi modyfikacjami. Obecnie to wygląda tak:



Po pierwsze, chciałbym aby były wyświetlane na liście takie wartości jak tytuł i treść, teraz jest tylko tytuł notatki. W tym celu należy dodać nowe pole w wierszach listy, w którym będzie się znajdowała treść notatki. Plik notes_row.xml wygląda teraz tak:



Dodany jest nowy TextView z id text2, a pole tytułu będzie wyświetlane większymi literami.

Teraz trzeba poprawić metodę fillData() w klasie Notepadv3 aby wypełniała text2 treścią.

private void fillData() {
        // Get all of the rows from the database and create the item list
        Cursor mNotesCursor = mDbHelper.fetchAllNotes();
        startManagingCursor(mNotesCursor);
        
        // Create an array to specify the fields we want to display in the list (only TITLE)
        String[] from = new String[]{NotesDbAdapter.KEY_TITLE, NotesDbAdapter.KEY_BODY};
        
        // and an array of the fields we want to bind those fields to (in this case just text1)
        int[] to = new int[]{R.id.text1, R.id.text2};
        
        // Now create a simple cursor adapter and set it to display
        SimpleCursorAdapter notes = 
             new SimpleCursorAdapter(this, R.layout.notes_row, mNotesCursor, from, to);
        setListAdapter(notes);
    }


Teraz prezentuje się to tak:


Jeszcze tylko zmienić szerokości layoutów, aby cała szerokość wiersza mogła być zaznaczana i klikana. Plik notes_list.xml wygląda teraz tak:




To by było na tyle. Następnym razem opisze filtrowanie list.

środa, 28 kwietnia 2010

Android a baza danych cz. 2

Tworzenie pliku bazy danych

Można przygotować gotowy plik bazy danych i dystrybuować go razem z aplikacją. Dzięki temu przy pierwszym uruchomieniu nie będzie trzeba czekać wieków na wypełnienie tabel. 

Na początku należy stworzyć plik bazy danych. W tym przypadku korzystamy z sqlite3.exe znajdującego się w AndroidSDK\tools
W konsoli tworzymy bazę danych:

sqlite3.exe database.db

a następnie tworzymy tabele

sqlite>.read create.sql

i wypełniamy danymi

sqlite>.read data.sql

Oczywiście w pliku create.sql jest SQL tworzący tabele, a w data.sql inserty. Trzeba pamiętać o dodaniu tabeli android_metadata - o tym w poprzednim poście. Tak oto przygotowany plik database.db kopiujemy do res/raw naszej aplikacji. Teraz należy sprawić aby przy starcie aplikacji plik bazy został skopiowany do systemu plików androida.

Kopiowanie bazy
Baza danych naszej aplikacji znajduje się na androidzie w katalogu /data/data/nazwa.pakietu/databases/



private static final String DB_PATH ="/data/data/pl.pawelzieba.myapplication/databases/";
private static final int[] dbFiles = {R.raw.database};

void copyDataBase() {
    OutputStream out = null;
    try {
        Log.d(TAG, "Kopiowanie bazy danych ");
        this.getReadableDatabase();
        InputStream in = null;
        out = new FileOutputStream(DB_PATH + DATABASE_NAME);
        byte[] buffer;
        int total = 0;
        for (int i = 0; i < dbFiles.length; i++) {
            try {
                in = mCtx.getResources().openRawResource(dbFiles[i]);
                int size = in.available();
                total += size;
                buffer = new byte[size];
                in.read(buffer);
                out.write(buffer);
            } finally {
                if (in != null)
                    in.close();
            }
        }
        Log.d(TAG, "Kopiowanie zakonczone, calkowity rozmiar pliku bazy danych: " + total);
    } catch (NotFoundException e) {
        e.printStackTrace();
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        try {
            if (out != null)
                out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


Oczywiście trzeba zadbać o wywołanie tej metody przy pierwszym uruchomieniu aplikacji (poprzedni post). Jeżeli korzystamy z SQLiteOpenHelper to należy zwrócić uwagę na to, że metoda onUpdate(SQLiteDatabase db, int oldVersion, int newVersion) nie będzie działała przy tym rozwiązaniu i trzeba samemu zaimplementować obsługę wersji bazy danych.


Niestety zaprezentowane podejście dalej posiada pewną wadę. Dane wciąż są duplikowane - baza znajduje się w swoim docelowym położeniu i w resources. Jedynym rozwiązaniem wydaje się pobieranie bazy z internetu. 


W następnej części opiszę co zrobić gdy baza danych jest większa niż 1MB.

niedziela, 25 kwietnia 2010

Android a baza danych cz. 1

Wypełanie bazy danych za pomocą SQL
Bardzo przydatną klasą do obsługi bazy danych w Androidzie jest SQLiteOpenHelper, szczególnie przy jej tworzeniu oraz uaktualnianiu. Przy każdym otwarciu jest sprawdzana wersja bazy danych, a jeżeli nie jest utworzona to wywoływana jest metoda onCreate(SQLiteDatabase db), w tym miejscu należy zaimplementować sposób tworzenia bazy.
Na przykład można wykonać SQL tworzący tabele oraz wczytać dane do wypełnienia bazy z pliku data.sql wrzuconego do raw w postaci wielu insertów. Czyli coś w ten deseń:

@Override
public void onCreate(SQLiteDatabase db) {
    Log.d(TAG, "Tworzenie bazy danych");

    db.execSQL(DATABASE_CREATE);
    InputStream in = null;
    try {
        in = mCtx.getResources().openRawResource(R.raw.data);
        if (in != null) {
            InputStreamReader tmp = new InputStreamReader(in);
            BufferedReader reader = new BufferedReader(tmp);
            String str;
            while ((str = reader.readLine()) != null) {
                 db.execSQL(str);
             }
         }
    } catch (java.io.FileNotFoundException e) {
         Log.d(TAG, "FileNotFoundException ",e);
    } catch (IOException e) {
         Log.d(TAG, "IOException ",e);
    } finally {
         try {
             if (in != null){
                 in.close();
             }
         } catch (IOException e) {
             e.printStackTrace();
             Log.d(TAG, "Closing",e);
         }
    }
    Log.d(TAG, "Utworzono baze danych");
}


Należy pamiętać o dodaniu tabeli android_metadata.


CREATE TABLE "android_metadata" ("locale" TEXT DEFAULT 'en_US');


i wstawienie wartości


INSERT INTO "android_metadata" VALUES ('en_US');


Jeśli chodzi o niewielkie bazy danych to ok, ale co gdy mamy do czynienia z większą ilością danych, gdy plik data.sql ma około kilka MB. Wtedy po pierwsze android nie otworzy pliku większego niż 1MB (takie systemowe ograniczenie), po drugie wykonywanie tych wszystkich insertów będzie trochę trwało, a po trzecie aplikacja będzie zajmowała więcej miejsca na telefonie niż naprawdę potrzebuje ponieważ dane się duplikują w pliku data.sql oraz w bazie danych. Niestety nie da się skasować pliku dodanego do aplikacji ponieważ plik apk jest podpisany cyfrowo.
Jeśli chodzi o pierwszy problem to plik można zawsze podzielić na kilka mniejszych tak aby nie przekraczały 1MB. W drugim przypadku możemy się pocieszyć, że wrzucanie danych następuje tylko przy pierwszym uruchomieniu, potem aplikacja startuje normalnie. Natomiast użytkownik mając niewiele miejsca na telefonie na początku wykasuje aplikacje zajmujące najwięcej miejsca, dlatego lepiej unikać duplikacji niepotrzebnych rzeczy.
Warto więc zastanowić się nad innym rozwiązaniem. W następnej części przedstawię jak można dodać do aplikacji już gotową bazę danych.


piątek, 23 kwietnia 2010

Android na iPhonie

Ta wiadomość:
http://linuxoniphone.blogspot.com/2010/04/ive-been-working-on-this-quietly-in.html
sprawiła, że cały dzień chce mi się śmiać.

Koniec świata. Android na iPhonie!
Wreszcie pojawiła się wspólna platforma na której można porównać oba systemy, hehe.
Są jeszcze pewne problemy, ale jak zostaną rozwiązane to myślę, że takie porównanie wyjdzie na dobre obu systemom. Mam nadzieję, że wytworzy się atmosfera sprzyjająca merytorycznej dyskusji bez możliwości zrzucania winy na hardware itp.

wtorek, 23 marca 2010

Duplicate entry '0' for key 'PRIMARY'

Błąd jest spowodowany tym, że Liferay nie inkrementował klucza głównego. W pliku http://www.liferay.com/dtd/liferay-service-builder_5_2_0.dtd jest podanych kilka przykładów jak można uzyskać automatyczną inkrementację klucza, jednak każdy z nich ma jakieś ograniczenia lub ograniczone zastosowania. Tutaj podam przykład jak można to zrobić w inny sposób.

Podpatrując źródła Liferay'a, np. klasę GroupLocalServiceImpl.java znalazłem coś takiego:


String groupId = Long.toString(CounterLocalServiceUtil.increment(Group.class.getName()));

Tak więc, wystarczy zrobić coś takiego:

Entity e = EntityLocalServiceUtil.createEntity(CounterLocalServiceUtil.increment(Entity.class.getName()));
e.setContent("foobar");

EntityLocalServiceUtil.addEntity(e);

czwartek, 4 marca 2010

Liferay Service Builder

Moje pierwsze podejście zaczęło się od przerobienia prostego tutoriala. Wszystko poszło bez problemu. Kolejne podejście polegało na wykorzystaniu jakiegoś narzędzia, aby usprawnić sobie pracę. Ciekawym rozwiązaniem jest PortalPack. Dzięki niemu można szybko wygenerować masę kodu, zamiast cały dzień wklepywać coś w XML'a. Dobrym przykład znajduje się tutaj. Aby móc z niego korzystać należy po zainstalowaniu plugin'u we właściwościach projektu zdefiniować na jakim serwerze projekt będzie działał, w tym przypadku Liferay Portal Server 5.1.x/5.2.x. Tak więc:

Properties>Run>Server>Liferay Portal Server 5.1.x/5.2.x.

Jeżeli nie ma tam takiej opcji, to należy dodać przez menu:

Tools>Server>Add Server

Możliwe, że wchodzę za bardzo w szczegóły, ale jest może się kiedyś komuś przyda.

Osobiście Service Builder'a z Portal Pack używam jedynie do generowania pliku service.xml. Całą resztę robię za pomocą Ant'a i liferay-plugins-sdk-5.2.3. Jeśli kogoś interesuje co można wpisać w service.xml to niech sobie przejrzy http://www.liferay.com/dtd/liferay-service-builder_5_2_0.dtd.

Gdy wygenerujemy serwisy, to w "magiczny sposób" możemy dobrać się do bazy danych. Czasem może się zdarzyć coś takiego:

com.liferay.portal.kernel.dao.orm.ORMException: Could not execute JDBC batch update

Spowodowane przez:

Data truncation: Data too long for column 'content' at row 1

W tym momencie dotrze do Ciebie myśl kołatająca się gdzieś z tyłu głowy, że czegoś tutaj brakuje. Mianowicie "magiczny sposób" widząc String "myśli" VARCHAR(75) ;-)

Rozwiązaniem okazuje się stworzenie pliku z podpowiedziami w miejscu gdzie masz źródła w META-INF/portlet-model-hints.xml. W tym przypadku wystarczy dodać:

<field name="content" type="String">
    <hint-collection name="CLOB" />
</field>



Więcej przykładów znajdziesz w portal-impl.jar/META-INF/portal-model-hints.xml