Krótkie podsumowanie mojego udziału w „Daj się poznać”

To jest mój 21 wpis na tym blogu. Napisałem 20 notek i mogę tylko powiedzieć, ze nie było to łatwe dla mnie 😉

Było kilka powodów, dla którego zdecydowałem zgłosić się do „Daj się poznać”.

  • większa motywacja do stworzenia aplikacji, nad którą i tak zaczynałem pracować
  • sprawdzenie się czy jestem w stanie pisać regularnie notki na blogu
  • chęć poćwiczenia umiejętności, którą bym nazwał ‚płynność pisania’

A jak to się sprawdziło w praktyce?

Motywacja do tworzenia aplikacji była taka sama jak wcześniej 🙂 Chciałem napisać tą aplikacje (w sumie to dalej chcę ;)) tak samo i w ramach wolnego czasu starałem się ją rozwijać. Rzeczą, którą zmieniłem po zgłoszeniu się do konkursu była decyzja o zmianie języka. Pierwsze dwa ekrany miałem już napisane w Javie, ale stwierdizłem, że jest to dobra okazja, żeby pouczyć się Scali 🙂 Więc w sumie gdyby nie konkurs to właściwie miałbym aplikacji napisane więcej niż teraz ;), ale za to mniej umiałbym Scali – więc w sumie sądzę, że dobrze wyszło 🙂

Czy udało mi się pisać notki regularnie? Prawie 🙂 Wpisy pojawiały się prawie zawsze 2 razy w tygodniu, więc tutaj prawie pełen sukces 🙂 Niestety czasem miałem wrażenie, że nie mam czasu na rozwijanie aplikacji, bo muszę pisać notkę 😉

Co prowadzi nas do punktu 3 🙂 Każda notka, szczególnie na początku, sprawiała mi sporo trudności, żeby ją napisać. Wraz z każdą kolejną notką było co raz lepiej, ale do pożądanego przeze mnie poziomu jeszcze daleka droga 🙂

Podsumowując jednym zdaniem swój udział w „Daj się poznać” – Jestem zadowolony 🙂

DatePicker w Androidzie

Kolejnym ekranem, nad którym będę pracował jest zaawansowane wyszukiwanie dokumentów. Przez słowo ‚zaawansowane’ rozumiem coś więcej niż tylko wyszukiwanie po tagach, po których możemy wyszuiwać na głównym ekranie. Chcę dodatkow zaimplementować możliwość wyszukiwania po tytułach dokumentów czy po datach kiedy zostały dodane.

Właśnie do wyszukiwania po datach postanowiłem użyć datepicker’a.

W formularzu chcę mieć dwie kontrolki:

  • pole tekstowe, gdzie będzie można ręcznie wpisać date
  • ikonka kalendarza, w którą jak klikniemy pojawi nam się nasz datepicker, a wybrana data zostanie zapisana we wcześniej wspomnianym polu tekstowym

Czyli na początku stworzyłem dwie wcześniej wspomniane kontrolki:

Pierwsza kontrolka to zwyczajne pole tekstowe. Druga kontrolka to przycisk, który po kliknięciu wywoła metodę datePicker. Metoda ta wygląda w następujący sposób:

Gdzie datePickerCode jest zdefiniowanym w aplikacji kodem dla danej akcji czyli po prostu liczba typu Int.

Wywołanie metody showDialog(Int) wywoła metodę onCreateDialog, która u mnie wyglada następująco:

Akcje wykonywane w tej metodzie są dosyć proste. W przypadku gdy metoda zostanie wywołana z naszym kodem tworzymy DatePickerDialog z naszą implementacją OnDateSetListener. U mnie wygląda to następująco:

Wydaję mi się, ze kod jest na tyle prosty oraz intuicyjny, że nie będę bardziej szczegółowo opisywał tych funkcji oraz klasy 🙂

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

Dzisiaj pokaże dwa kolejne screeny z mojej aplikajci.

  1. Wyświetlanie szczegółów dokumentu
    Screenshot_2010-04-13-01-21-06Na górze strony, na niebieskim tle, znajduje się tytuł dokumentu. Po prawej stronie znajduje się button uruchamiający edycje dokumentu.
    Poniżej są wypisane wszystkie tagi z dokumentu. Pod spodem znajdują się wyświetlone wszystkie pliki wraz z opisem plików. Każda miniaturka po kliknięciu uruchamia zdjęcię w pełnej rozdzielczości.
  2. Edycja dokumentu
    Screenshot_2010-04-13-01-21-35Edycja dokumentu jest praktycznie identyczna jak dodawanie dokumentu, które opisywałem jakiś czas temu. Główną różnicą jest oczywiście to, że do formularza zostają załadowane tytuł, tagi oraz pliki wraz z opisami.

Problem z GUI tworzonym w kodzie

Ostatnie kilka godzin pisania aplikacji poświęciłem na walce z dynamicznym tworzeniem elementów GUI w kodzie. Udało mi się w końcu wstępnie uzyskać efekt, który chciałem, i który jeszcze dopracuję. Problemem okazał się mały szczegół, o którym nie widziałem…

Problem, który chcę opisać, napotkałem przy tworzeniu ekranu wyświetlającego szczegóły dokumentu. Na tym ekranie powinien być widoczny tytuł dokumentu, tagi oraz wszystkie pliki wraz z ich opisami. I przy tym ostatnim elemencie powstały problemy.

Screenshot_2010-04-01-02-58-53

Powyżej wstawiłem efekt jaki bym chciał osiągnąć. Chodzi oczywiście o rozmieszczenie elementów – niebieskie linie oraz zielony kolor zostały dodane dla lepszej widoczności co się dzieję. Wracając do układu elementów – pliki chciałbym wyświetlić w formie tabelarycznej, gdzie w lewej kolumnie będzie miniaturka zdjęcia/pliku, a w prawej będzie dodany opis pliku.

Powyższy efekt udało się uzyskać przy pomocy następującego kodu:

Czy tworzę TableRow i są w nim dwa elementy:

  • FrameLayout, który w sobie zawiera ImageView z obrazem
  • TextView, który zawiera opis pliku

Oczywiście kod ten w XMLu stworzyłem tylko po to, żeby zobaczyć, których elementów mam użyć, żeby uzyskać efekt jaki chcę. Sama lista plików z opisami będzie oczywiście tworzona dynamicznie w kodzie.

Jeden wiersz tworzyłem mniej więcej w poniższy sposób:

Tworząc elementy tabeli w kodzie starałem się tworzył dokładnie te same elementy z tymi samymi parametrami, które użyłem w XMLu. Niestety tworzenie wiersza tabeli w powyższy sposób powodował, że na ekranie nic się nie pojawiało – tabela zostawała pusta. Metodą prób i błędów próbowałem dojść do tego co jest nie tak i w końcu mi się udało.

Okazało się, że przy tworzeniu ustawień layouty, czyli tworząc obiekty LayoutParams musimy użyć LayoutParams z klasy rodzica danego elementu, a nie z samego elementu. Czyli w przypadku gdzie tworzyłem wiersz tabeli w poniższy sposób

wystarczyło zamienić na:

Czyli użyć TableLayout.LayoutParams zamiast TableRow.LayoutParams, ponieważ TableLayout jest rodzicem naszego wiersza.

Po takich zmianach w kodzie ekran wyświelił elementy w taki sposób w jaki chciałem. Kod po modyfikacjach wyglada następująco:

Android Studio 2.1 i DEX 64K Methods Limit

W kwietniu została wypuszczona nowa wersja Android Studio opatrzone symbolem 2.1. Postanowiłem je ściągnąć i wypróbować.

Po ściągnięciu i uruchomieniu konfiguracja poszła gładko. Wszystkie ustawienia zostały zaimportowane z poprzedniej wersji środowiska, więc wszystko praktycznie ogarniczało się do klikania ‚Next’ i potwierdzenia przeczytania licencji 😉

Po skończonej konfiguracji uruchomiłem aplikacje na emulatorze. Wszystko poszło bez przeszkód. Problemy pojawiły się gdy chciałem uruchomić aplikacje na realnym urządzeniu. Przy próbie kompilacji i uruchomienia apki pojawiają się poniższe błędy:

Android posiada ograniczenie 64k referencji metod. Gdy przekroczymy ten próg to pojawia się na powyższy bład.

Na GitHubie pluginu do scali dla androida jest opisany ten problem w punkcie 5. Jako rozwiązanie problemu zaproponowane są dwa podejścia.

  1. Użycie ProGuard, który usuwa nieużywane metody/kod przy kompilacji, dzięki czemu nie przekracamy wyżej wspomnianego limitu.
  2. Uzycie opcji MultiDex, która pozwala utworzyć aplikacji więcej niż jeden plik Dex.

Odniosę się może najpiew do podejścia drugiego. Aby uruchomić tą opcje wystarczy dodać do pliku gradle poniższy wpis:

I to podejście działa w Android Studio 2.1, ale nie ukrywam, że nie podoba mi się. Nie po to tworzę SIMPLE Document Manager, żeby zajmował dużo miejsca 😉

Co do pierwszego podejścia, to ono działało w Android Studio 1.5 jak należy, natomiast przestało działać w Android Studio 2.1. Pokombinowałem trochę, szukałem w internecie i niestety nie udało mi się rozwiązać tego problemu. W związku z tym chcąc, nie chcąc, muszę póki co został przy Android Studio w wersji 1.5.

Zapis danych na dysku

W dzisiejszym wpisie przedstawię w jaki sposób dokonać zapisu danych na pamięci wewnętrznej urządzenia.

Pierwszym krokiem jaki musimy zrobić to dodanie do pliku AndroidManifest.xml poniższego wpisu:

Jak można wywnioskować, jest to dodanie uprawnień do zapisu danych na pamięci zewnętrznej do naszej aplikacji.

Sam zapis pliku nie jest specjalnie skomplikowany. Stworzyłem prostą metodę, której zadaniem jest skopiowanie wskazane pliku do wskazanego miejsca.

Dlaczego kopiowanie? W swojej aplikacji zapisuje dwa rodzaje plików:

  • zrobione zdjęcia, które są zapisywane do pliku tymczasowego; właśnie ten plik kopiuje to folderu z dokumentami dla aplikacji
  • pliki wskazane bezpośrednio przez użytkownika; w tym przypadku wskazany plik jest kopiowany do folderu z plikami aplikacji

 

SqLite – typy danych oraz zapis daty

W Androidzie mamy dostępną wbudowaną relacyjną baze danych – SqLite. Jak można wnioskować po nazwie, ta baza danych jest trochę okrojona w porównaniu do baz takich jak Oracle, Sybase czy PostgreSQL.

Dostępne typy danych w SqlLite to:

  • INTEGER – służy do przechowywania liczb całkowitych; w zależności od wielkości wartości przechowywane są na 1, 2, 3, 4, 6 lub 8 bitach
  • REAL – liczby zmiennoprzecinkowe
  • TEXT- ciągi znaków
  • BLOB – dane binarne

Jak widać nie ma tego za wiele, lecz wystarczająco, aby zapewnić nam wszystko czego potrzebujemy. Przynajmniej w większości wypadków 🙂

Chociaż po chwili zastanowienia… gdzie jest typ Date? Jak zapisywać datę? Tyle pytań bez odpowiedzi… Aż do teraz!

Data może być przechowywana w bazie danych w jednym z trzech formatów:

  • TEXT – data jest zapisana w formacie ISO8601, czyli „YYYY-MM-DD HH:MM:SS.SSS”
  • REAL – data jest zapisana jako liczba dni, które mineły od 24 listopada 4714 r. p.n.e., czyli tak zwana data Juliańska
  • INTEGER – czas UNIXowy, czyli liczba milisekund, które mineły od 1 stycznia 1970r.

Do operacji na datach SqLite dostarcza 5 metod:

  1. date(timestring, …)
  2. time(timestring, …)
  3. datetime(timestring, …)
  4. julianday(timestring, …)
  5. strftime(format, timestring, …)

Pierwsze 4 funkcje przyjmują string jako pierwszy argument, a następnie dodatkowe argumenty. Funckja strftime przyjmuje jako pierwszy argument format daty, następnie wartość daty jako string oraz dodatkowe argumenty jako kolejne argumenty.

String opisujący date może być w jednym z poniższych formatów:

  1. YYYY-MM-DD
  2. YYYY-MM-DD HH:MM
  3. YYYY-MM-DD HH:MM:SS
  4. YYYY-MM-DD HH:MM:SS.SSS
  5. YYYY-MM-DDTHH:MM
  6. YYYY-MM-DDTHH:MM:SS
  7. YYYY-MM-DDTHH:MM:SS.SSS
  8. HH:MM
  9. HH:MM:SS
  10. HH:MM:SS.SSS
  11. now
  12. DDDDDDDDDD

Formaty od 1 do 4 wydają się być oczywiste. W formatach 5-7 występuję stały znak T, który służy jako separator daty i godziny. Formaty 8-10 służą do przechowywania samej godziny. Format 11 to string now, który odpowiada aktualnej dacie. Ostatni format jest to liczba zmiennoprzecinkowa opisująca date Juliańską.

OrmLite – realizacja relacji wiele-do-wielu

W tym wpisie przedstawiłem orientacyjny schemat bazy danych, który będzie zastosowany w mojej aplikacji. Można tam zauważyć relacje wiele-do-wielu pomiędzy tabelami tags i documents oraz tabele documents_tags, która jest tak zwaną tabelą skrzyżowań.

OrmLite niestety nie wspiera relacji wiele-do-wielu, ale sama realizacja tego na szczęście nie jest trudna.

Zacznę od pokazania encji dla tabeli documents_tags.

Jak widać encja zawiera tylko 3 pola:

  • id – id encji, które jest wymagane
  • document – klucz obcy odnoszący się do dokumentu
  • tag – klucz obcy odnoszący się do tagu

Encja tag wygląda następująco:

Wydaję mi się, że tutaj nie ma nic do tłumaczenia 🙂

Encja dla dokumentu wygląda natomiast tak:

Tutaj istotne jest następujące pole:

Jak widać jest to ‚obca’ kolekcja. Co będzie zawierała kolekcja po pobraniu dokumentu przy pomocy metody queryForId?

Otóż, jak można się domyślić, kolekcja ta będzie zawieraja liste encji DocumentTag powiązanych z dokumentem. Jest tylko jeden haczyk – pobrane encje DocumentTag nie zawierają ‚całych’ obiektów, a jedynie ich id. Czyli np. instancja klasy Tag w pobranej kolekcji bedzie zawierała uzupełnione id, lecz pole name zawsze będzie puste.

Mając tą wiedze wystarczą dwa kroki do pobrania naszych tagów.

Po pierwsze musimy stworzyć metodę, która na podstawie listy id tagów, zwróci nam tagi.

A mając pobraną naszą encje możemy pobrać jej taki wywołując powyższą metodę w następujący sposób:

 

Jak widać pomimo braku jawnego wsparcia dla relacji wiele-do-wielu możemy sobie z tym w miarę ładny sposób poradzić 🙂

Tworzenie layoutu w Androidzie

Jedną z rzeczy, która mnie bardzo pozytywnie zaskoczyła w pisaniu aplikacji na Androida jest sposób tworzenia layoutu. Chodzi mianowicie o separacje pomiędzy logiką aplikacji, a wartwą prezentacji, która jest opisywana w plikach XML.

Stworzenie aktywności z menu rozwijanym z boku może wyglądać następująco.

activity_main.xml

app_bar_main.xml

content_main.xml

 

Wydaję mi się, że tworzenie elementów layoutu jest na tyle intuicyjne, że nie będę szczegółowo opisywał co się dokładnie dzieje w powyższych listingach.

Jest jednak jednak jedna rzecz, na którą chciałbym zwrócić uwagę. Wewnątrz activity_main.xml umieszczany jest app_bar_main.xml, wewnątrz którego umieszczony jest content_main.xml.

Pierwsze dwa pliki są odpowiedzialne za tworzenie aktywności z bocznym menu, natomiast trzeci plik – content_main.xml – zawiera layout specyficzny dla konkretnego ekranu.

I teraz pojawia się problem, którego nie udało mi się rozwiązać w elegancki sposób.

Tworzę drugą aktywność, która ma zawierać to samo menu, lecz sam ekran ma mieć inną zawartość. I teraz, żeby to zrobić muszę skopiować dwa pierwsze layouty odpowiedzialne za menu, zmienić nazwy plików i wartości atrybutów w samych plikach. Podejście to oczywiście działa, lecz generuje sporo plików, które są praktycznie identyczne.

Rzeczą, która by się tutaj mogła przydać byłaby możliwość podania parametrów, które były by przekazywane do załączanego layoutu. Mam na myśli rozwiązanie podobne do tego, które jest dostępne np. w JSF.

Niestety, póki co nie udało mi się znaleźć rozwiązania tego problemu i w związku z tym, póki co zostaje z moim rozwiązaniem.

Ciekawy problem z ORMLite

Podczas implementacji ekranu edycji dokumentu natknąłem się na ciekawy błąd związany z ORM.

Podczas próby aktualizacji encji

otrzymałem poniższy błąd:

Błąd sugeruje, że encja ta nie posiada pola id. Sama encja wyglądała tak:

Encja oczywiście zawierała pole id, a sam zapis encji do bazy danych odbył się bez problemu.

Zanim przejdę do mojego rozwiązania to chciałbym odnieść się do znaku _, który występował przy kazdym polu, np:

Idąc za dokumentacją języka:

Czyli w przypadku pola id powinno do niego zostać przypisane domyślnie 0.

Wracając do mojego rozwiązania problemu. Jedyną zmianą, którą zrobiłem w moim kodzie to poniższa modyfikacja.

Jak widać bezpośrednie przypisanie 0 do pola id pomogło z rozwiązaniem problemu.