ORM w Androidzie

W tym wpisie napisałem kilka słów o przechowywaniu danych w mojej aplikacji. Wspominałem tam również, że z powodu prostego schematu bazy danych nie zamierzam używać ORMa. No więc… sytuacja się trochę zmieniła 🙂

Po napisaniu kilku insertów, selectów itd. stwierdziłem jednak, że odrobina pomocy od strony jakiegoś framework’u nie zaszkodzi 🙂

Po przejrzeniu dostępnych frameworków wybrałem ORMLite. Cytując opis ze strony:

Jedyną funkcjonalnością (przynajmniej na ten moment), której brakuje mi w ORMLite to wpieranie relacji many-to-many. No ale jak to mówią – nie można mieć wszystkiego 🙂

Sama konfiguracja była dosyć łatwa. Pierwszym krokiem jest dodanie zależności do gradle:

Dalej już pozostało tylko stworzyć encje:

DAO dla naszej encji:

DAO rozszerza klase RuntimeExceptionDao. Zaimplementowałem to w ten sposób ponieważ podstawowe DAO dostarczane przez ORMLite wymaga obsługi java.sql.SQLException, który może zostać rzucony przez każdą metodę DAO. RuntimeExceptionDao (jak sama nazwa wskazuje) rzucza RuntimeException zamiast wspomnianego wczesniej SQLException.

Jeszcze jedną rzeczą, na którą warto zwrócić uwagę to poniższa linijka:

Metoda queryForAll() zwraca java.util.List. Aby przekonwertować to na liste Scala’ową wystarczy na tej liście wywołać metodę toList.

Kolejnym krokiem jest implementacja klasy rozszerzająca OrmLiteSqliteOpenHelper. Moja implementacja wygląda następująco:

Mamy dwie metody – jedna jest wywoływana przy tworzeniu bazy danych, a druga w momencie jej upgrade’u. Przy tworzeniu bazy danych  używamy metody statycznej

I ostatnią rzeczą, którą musimy zrobić to utworzenie instancji DAO, których będziemy używać. Aby to zrobić musimy tylko wywołać metodę OrmDatabaseHelper.getDao(Class<T>), a zwrócone DAO przekazać jako parametr konstruktora dla RuntimeExceptionDao.

Problem ze starą wersją pluginu do Androida

W tym wpisie opisywałem swoją integracje języka Scala z Android Studio. Wspominałem tam o tym, że jest pewien problem z pluginem gradle-android-scala-plugin. Nie działa on z najnowszą (1.5.0) wersją pluginu com.android.tools.build. Wybrałem dosyć proste rozwiązanie problemu, czyli downgrade pluginu com.android.tools.build do poprzedniej wersji – 1.3.1. Nie widziałem was tego rozwiązania dopóki nie chciałem użyć grafiki wektorowej w swojej aplikacji.

Po utworzeniu grafiki wektorowej otrzymałem poniższy komunikat od IDE:

Po szybkim googlowaniu znalazłem informacje o zmianach w api pomiędzy wersjami pluginu com.android.tools.build.

Na szczęście nie byłem też pierwszą osobą, która miała ten problem. Na GitHubie powstał osobny branch, który zawiera poprawki umożliwiające współprace tego pluginu z najnowszą wersją pluginu do Androida. Wystarczyło skompilować plugin ściągnięty z tego brancha oraz zamienić wpis

na poniższy

w pliku build.gradle.

Dependency Injection – część druga

W poprzedniej notce pisałem o swojej implementacji Dependency Injection, którą użyłem w swojej aplikacji. Po chwili dopiero zauważyłem pewną wadę mojego podejścia. Wszystkie instancje są tworzone przy inicjalizacji obiektu ApplicationModule. Ponieważ bez sensu byłoby tworzenie zawsze wszystkich instancji, nawet tych, które nie będą potrzebne podczas konkretnego uruchomienia aplikacji postanowiłem to zmienić.

Wszystkie zmiany, które zrobiłem w modyfikacji mojej wersji DI dokonałem we wcześniej wspomnianym obiekcie ApplicationModule.

Poniżej przedstawiam listing tego obiektu.

Zmiany, których dokonałem to:

  • Mapa
    Wcześniej mapa przechowywała pary Klasa-Instancja. W obecnej implementacji mapa zawiera pary Klasa na krotkę zawierającą funkcje oraz instancje. Dokładniej mówiąc krotka zawiera funkcje tworzącą daną instancje na konkretną instancje klasy.
  • Metoda tworząca instancje wstrzykiwanej klasy
    Wcześniej metoda zwracała nową instancje danej klasy. W chwili obecnej zwraca funkcje bezparametrową, która po wywołaniu stworzy instancje klasy.
  • Blok inicjalizacyjny
    Do mapy wsadzamy wszystkie pary Klasa-Krotka, których będizemy chcieli używać. Jako drugą wartość krotki ustawiamy null, ponieważ nie chcemy jeszcze tworzyć instancji klasy.
  • Metoda wire
    We wcześniejszym podejściu metoda ta pobierała z mapy instancje żądanej klasy i ją zwracała. W chwili obecnej sprawdzamy czy druga wartość krotki dla żądanej klasy jest null’em. Jeżeli jest to tworzymy nową instancje żądanej klasy i ja zapisujemy w mapie, a następnie zwracamy stworzoną instancje. W przypadku gdy druga wartość krotki nie jest null’em to oczywiście oznacza, że instancja została stworzona wcześniej i właśnie ona zostanie zwrócona.

Dependency Injection w Androidzie

Aby ułatwić pisanie aplikacji postanowiłem spróbować znaleźć i użyć framework do Dependency Injection.

Po szybkich poszukiwaniach natrafiłem na framework Dagger. Framework ten jest przystosowany do użycia go w aplikacjach mobilnych. Dagger działa inaczej niż większość frameworków DI, które działają na refleksjach. Dagger tworzy klasy, które będzie wstrzykiwał, na etapie kompilacji. Podejście to tworzy pewne ograniczenia, lecz jest szybsze i mniej pamięciożerne, co jest zdecydowanie przydatne w przypadku aplikacji mobilnych.

Po tym wyborze przystąpiłem do konfiguracji frameworka, lecz naszła mnie pewna myśl… Moja aplikacja będzie dosyć prosta. Czy faktycznie potrzebuję frameworka DI? Po chwili namysłu stwierdziłem, że samodzielnie stworzę coś na wzór frameworka DI. Oczywiście prostszego i bardziej ubogiego ale mam nadzieję, że wystarczającego 🙂

Podszedłem do tego w następujący sposób:

  1. Stworzyłem w Javie klasę dziedziczącą po klasie Application, ze statycznym dostępem do contextu.

    W Scali nie są dostępne metody statyczne w klasach, a Object Scalowy nie mógłby być zainicjalizowany przez system. W związku z tym byłem zmuszony stworzyć powyższą klasę Javową, aby zyskać dostęp do kontekstu aplikacji.
  2. Dodałem odniesienie do powyższej klasy w pliku AndroidMnifest.xml w atrybucie android:name dla tagu application. Wygląda to mniej więcej tak:
  3. Stworzyłem obiekt, w którym tworzone są instancje, które będą ‚wstrzykniętę’ oraz metodę służącą do pobrania tych instancji.

    Obiekt zawiera w sobie mapę, która zawiera mapowanie klasy na jej instancje. Mapa ta jest uzupełniania w bloku inizjalizującym – tutaj będę musiał dodać wszystkie klasy wraz z instancjami, do których będę chciał mieć dostęp w ten sposób.
    Metoda wire pobiera instancje z mapy oraz ją zwraca.
  4. Samo ‚wstrzyknięcię’ robimy w poniższy sposób:

    Oczywiście powyższe podejscie będzię wstrzykiwać zawsze tą samą instancje, lecz na potrzeby mojej aplikacji powinno być to całkowicie wystarczające.

Opis ekranów i funkcjonalności aplikacji cz. 1

15Wydaję mi się, że najłatwiej i najbardziej przejrzyście będzię opisać funkcjonalność aplikacji na screenach z aplikacji. W tym wpisie zaprezentuję pierwsze dwa screeny oraz ich krótki opis.

  1. Ekran startowy
    main_view
    Ekran startowy będzię się składał z 3 części:
    1. Szybka wyszukiwarka
    Na podstawie wpisanych tagów zostaną wyszukane dodane wcześniej dokumenty i wyświetlone w polu 2.
    2. Lista dokumentów
    Obszar 2. będzię zawierał listę ostatnich dodanych dokumentów. Po wpisaniu tagów i kliknięciu przycisku ‚Find’ z obszaru 1. zostaną wyświetlone dokumenty pasujące do zapytania.
    3. Button przekierowujący do ekranu odpowiadającego za dodawanie dokumentu.
  2. Dodawanie dokumentu
    add_document1. Anulowanie operacji i powrót do poprzedniego ekranu
    2. Zapis tworzonego dokumentu. Przy zapisie będzię dodana walidacja:
    – pola title oraz tags nie mogą być puste
    – conajmniej jeden plik/zdjęcie zostały dodane
    3. Tytuł dokumentu
    4. Tagi przypisane do dokumentu – oddzielone spacjami
    5. Dodanie pliku do dokumentu. Plik będzię wybierany z systemu plików w telefonie.
    6. Zrobienie zdjęcia i dodanie go do dokumentu.
    7. Przycisk rozwijający opcje 5 i 6.

Integracja języka Scala z Android Studio

Jak już wspominałem w swoim pierwszym wpisie, postanowiłem napisać swoją aplikacje w języku Scala. Android studio z założenia przystosowany jest do języka Java. W związku z tym, aby móc programować w języku, którym chciałem wymagana była pewna konfiguracja.

  1. Scala plugin
    Plugin wymagany przez Android Studio, aby móc poprawnie wspierać język Scala. Najłatwiejsza i najbardziej intuicyjna część konfiguracji 🙂
  2. Gradle plugin
    Jako plugin do gradle znalazłem/wybrałem gradle-android-scala-plugin, który jest opisany tutaj:  https://github.com/saturday06/gradle-android-scala-plugin.  Niestety plugin ten nie działa z najnowszą wersją com.android.tools.build (1.5.0). Przy próbie zbudowania projektu z tą wersją pojawia się poniższy błąd
    Error:No such property: bootClasspath for class: com.android.builder.core.AndroidBuilder
  3. JUnit
    Konfiguracja testów jednostkowych była dosyć prosta. Poniżej zamieszczam prosty przykład testu.
    Jedyny problem, który napotkałem pojawił się przy próbie użycia AssertJ, którego używam w codziennej pracy. Niestety biblioteka ta nie jest przystosowana do pracy Android SDK.

Przechowywanie danych w aplikacji

Duża część aplikacji potrzebuje przechowywać dane wprowadzone przez użytkownika czy też swój stan. Nie inaczej jest też w mojej aplikacji.

W aplikacji będę musiał przechowywać dane na dwa sposoby:

  • W bazie danych
    Android na szczęście dostarcza nam SQLite. Jak można wywnioskować z nazwy jest to baza danych z ‚lekko’ okrojoną funkcjonalnością, ale na szczęście do moich zastosowań jest to całkowicie wystarczające.
    Poniżej przedstawiam wstępny schemat bazy danych, który zastosuję w menadżerze dokumentów.
    sdm_db_diagram

    • document – tabela przechowująca informacje o dodanych dokumentach
    • tag – tagi, które zostały dodane do dokumentów podczas ich tworzenia
    • document_tag – tabela pomocnicza dla relacji many-to-many
    • document_file – table zawierająca informacje o plikach/zdjęciach dodany do danego dokumentu

Na początku chciałem wykorzystać ORM przy zarządzaniu wpisami w bazie danych, lecz mając na uwadze wielkość oraz stopień skomplikowania bazy danych sądzę, że jest to zbędnę.

  • W pamięci urządzenia
    Wszystkie dodane pliki oraz zdjęcia, które będą powiązane z dodawanymi dokumentami będą musiały znaleźć swoje miejsce w pamięci urządzenia lub na karcie SD

Kilka słów o aplikacji

Często gubię/zapodziewam/dobrze chowam wszelkiego rodzaju paragony czy rachunki, które bywają przydatne np. podczas składania reklamacji. Zdarzało mi się również, że będąc poza domem, pilnie potrzebowałem informacji z umowy/pisma, które miałem schowane głęboko w szufladzie.

Szukałem aplikacji na androida, która by mi pomogła w przechowywaniu elektronicznych kopii takich dokumentów oraz umożliwiła dostęp do nich w każdym momencie.
Niestety nie udało mi się znaleźć aplikacji, która by spełniała wszystkie moje wymagania. Ale jak to się mówi – nie ma tego złego, co by na dobre nie wyszło. Brak takiej aplikacji jest świetnym powodem, żeby napisać swój pierwszy program na Androida 🙂

Planowałem na początku napisać aplikację w Javie, ale po na myślę stwierdziłem, że spróbuję napisać ją w Scali. Jak szaleć to szaleć! Nie mam doświadczenia w pisaniu aplikacji w Scali, więc jest to również dobra okazja, żeby poduczyć się trochę nowego języka.

Aplikacja, którą chcę napisać, powinna spełniać następujące wymagania:

  • Jako dokumenty mogą być przechowywanie zarówno zdjęcia zrobione aparatem, jak i dokumenty załadowane z systemu plików
  • Dokumenty mogą być wyszukiwane po tagach
  • Pliki powinny być synchronizowane z Google Drive

Są to główne założenia, które traktuje jako minimum, żeby aplikacja była użyteczna.

W trakcie pisania pewnie będę sobie przypominał/zmieniał/dodawał nowe wymagania, aby zrobić aplikację jak najbardziej użyteczną i wygodną w użyciu.

Daję się poznać!

Witam Was na moim blogu.

Strona powstała w ramach mojego udziału w konkursie Daj się poznać.

Aplikcja, którą zdecydowałem się stworzyć to prosty menadżer dokumentów na Androida. Jej kod będzię dostępny tutaj.

Od jakiegoś czasu chodziło za mną, aby stworzyć taki menadżer, który będzie dostosowany do moich potrzeb, a dodatkowo stworzyć swoją pierwszą aplikację na system Android. Jak się pojawiła przy okazji możliwość wzięcia udziału w konkursie jako dodatkowa motywacja, pomyślałem, że warto spróbować.

Mam nadzieję, że uda mi się dotrwać do końca konkursu, szczególnie z blogowaniem, co będzie dla mnie największym wysiłkiem 🙂

Serdecznie zapraszam do wszelkich komentarzy.

pozdrawiam serdecznie
Łukasz Antkowiak