Archiwa kategorii: Średnio zaawansowane

Wpisy dla programistów (także początkujących)

Wyrażenia regularne w Javie – figle i psikusy

To trzeci (i na jakiś czas ostatni) z serii wpisów na temat wyrażeń regularnych w Javie, po O wyrażeniach regularnych. Podstępna różnica pomiędzy find i matches oraz Wyrażenia regularne dla nieprogramistów. Historycznie ten jest najwcześniejszy – to zaktualizowana wersja tekstu z mojego poprzedniego bloga.

Poruszam tu bardzo podstawowe kwestie, które potrafią jednak dać się we znaki. Rozwiązanie tych paru niewinnych problemików kosztowało mnie niemało czasu i nerwów. Liczę, że kiedy jakaś zbłąkana dusza znajdzie się w tej samej sytuacji, Google zaprowadzi ją prosto w moje troskliwe ramiona.

Artykuł powstał podczas mojej pracy nad narzędziem do przekształcania metadanych.

Psikus 1: znaki ucieczki w wyrażeniach wczytywanych z zewnętrznego pliku

Załóżmy, że wyrażenie (które chcemy wczytać z zewnętrznego pliku) w zwykłym kodzie Javy wygląda tak:

Linia przedstawia zakres wieków, do którego dopasuje się na przykład linia:

Proste, prawda?

Prawda – ale do czasu. Problemy pojawiły się, kiedy zaczęłam wczytywać wyrażenia regularne zapisane w zewnętrznym pliku. Wczytywałam między innymi następujący fragment:

Wszystko przestało działać. Łańcuchy znaków, które bez najmniejszych wątpliwości powinny były dopasować się do wyrażenia, przechodziły niezauważone. Po godzinie analiz niebezpiecznie zbliżałam się do stanu, w którym myślałam, że oszalał albo świat dokoła mnie. Wtedy na szczęście nadeszła pora lunchu. Opowiedziałam o problemie nad talerzem naleśników, a jeden z kolegów zadał oczywiste w sumie pytanie – czy na pewno dobrze wyeskejpowałam (przepraszam!!!) wszystkie znaki specjalne. I wtedy wreszcie nadeszło olśnienie: nie, nie zrobiłam tego dobrze. Przeeskejpowałam je.

Znaki specjalne, takie jak d, w wyrażeniu regularnym oznaczające cyfrę, należy poprzedzić tzw. symbolem ucieczki, czyli w tym wypadku backslashem (ukośnikiem wstecznym). W łańcuchach znaków w kodzie Javy konieczne jest wprowadzenie dodatkowego backslasha, gdyż musimy jeszcze odebrać specjalne znaczenie samemu backslashowi (musimy poprzedzić znak ucieczki znakiem ucieczki…). Tyle razy widziałam te dwa ukośniki w parze, że zupełnie zapomniałam o tym, że w zewnętrznym pliku należy użyć tylko jednego!

Psikus 2: flagi

To bardzo proste. Załóżmy, że wyrażenie nie ma brać pod uwagę wielkość liter. Normalnie oznaczamy to tak:

Świetnie, tylko jak przekazać tę flagę, jeśli wyrażenie jest wczytywane z zewnątrz? Wychodzi na to, że flagę, poprzedzoną znakiem zapytania, należy umieścić w nawiasie na początku wyrażenia. Ignorowanie wielkości liter (przy okazji, poznałam ostatnio nowe słowo – kasztowość) to literka i, zatem dodajemy (?i). Ostatecznie, w kodzie wyrażenie wygląda tak:

a poza kodem, z pojedynczymi ukośnikami, tak:

Psikus 3: String.replaceAll

W pewnym brzegowym przypadku mój kod, w wyniku wczytania wyrażeń regularnych z pliku, wykonywał operację, którą można w uproszczeniu zapisać tak:

Po wykonaniu się tego kodu spodziewałam się, że treść zostanie całkowicie zastąpiona, czyli wartością s będzie:

skoro * jest zachłannym kwantyfikatorem, to .* powinno dopasować się do całego napisu niezależnie od okoliczności.

Wyobraźcie sobie moje zaskoczenie (czy raczej przerażenie), gdy okazało się, że s przyjęło wartość:

Próbowałam użyć jeszcze bardziej zaborczego kwantyfikatora *+, ale efekt był ten sam. Byłabym mniej zaskoczona, gdyby .* zostało dopasowane do każdej litery w łańcuchu. Jakim cudem dopasowało się dokładnie dwa razy?

Dalsze śledztwo wykazało, że przebieg akcji jest następujący:

  1. Cały łańcuch dopasowuje się do .* i jest zamieniany na łańcuch „nowa treść”.
  2. Po dopasowaniu z oryginalnego łańcucha znaków nie zostaje nic, a raczej zostaje łańcuch "". Metoda replaceAll jeszcze raz sprawdza możliwość dopasowania i okazuje się, że "" także pasuje do .*, zatem pusty łańcuch również zostaje wymieniony.
  3. Zasadniczo można by kontynuować i w nieskończoność dodawać na końcu "nowa zawartość", jednak na szczęście (?) dana pozycja w łańcuchu znaków jest traktowana jako sprawdzona i wykonanie metody kończy się.

Pozdrawiam siostry i braci w cierpieniu.

O wyrażeniach regularnych. Podstępna różnica pomiędzy find i matches

Wyrażenia regularne to jedno z zagadnień dzielących programistów. Są tacy, którzy je uwielbiają i ci, którzy szczerze ich nienawidzą.

Sama zaliczam się do pierwszej grupy, być może z powodu upodobań lingwistycznych (wyrażenia regularne ≈  języki regularne ≈ automaty skończone). Wyrażenia regularne pozwalają na bardzo zwięzły i precyzyjny zapis warunków wyszukiwania, które – gdyby ograniczyć się do tradycyjnych konstrukcji zawartych w danym języku programowania – mogłyby zająć wiele linii. Po stronie minusów należy zapisać trudność zrozumienia dłuższych wyrażeń regularnych, zwłaszcza, jeśli są dziełem kogoś innego.

W tym wpisie chcę podzielić się swoją niedawną przygodą optymalizacyjną. W najbliższym czasie (czyli przed końcem roku) wrzucę jeszcze stary tekst o pułapkach, w które można wpaść pisząc wyrażenia regularne w Javie oraz nowy o tym, jak regeksy mogą przydać się nieprogramistom w ich codziennych zadaniach.

Do rzeczy.

Napisałam ostatnio kod analizujący treść stron internetowych. Mniejsza z tym, po co to robił. Stanowił część większej całości (moduł mavenowy) i podobnie jak reszta kodu korzystał m.in. z Javy 8 i biblioteki JSoup. Napisałam masę testów jednostkowych, testowałam na kilkuset stronach… Jednak prawdziwa próba ognia miała nadejść kilka dni po oddaniu przeze mnie projektu, podczas uruchomienia pełnego łańcucha przetwarzania na kilkudziesięciu (kilkuset?) tysiącach stron.

W pięciu przypadkach mój kod zawiesił się na ponad dobę.

Naprawa, kiedy już do niej zasiadłam zajęła mi niecałą godzinę. Co się okazało? W kilku miejscach w moim kodzie użyłam metody Element::getElementsByAttributeValueMatching i dałam się zwieść nazwie. Na swoją obronę mam to, że Javadoc też wprowadza w błąd. Otóż wyobraziłam sobie, że gdzieś w głębinach metoda wywołuje metodę Matcher::matches, dlatego szukany przeze mnie fragment otoczyłam znakami ".*" przed i po właściwym wzorcu.

Skoro już wszystko się zapętliło, zajrzałam do środka, a tam:

Podstawowa różnica pomiędzy wspomnianą już Matcher::matches a Matcher::find jest taka, że matches szuka pełnych dopasowań (cały łańcuch znaków musi pasować do wzorca), a find zadowoli się dopasowaniem w środku łańcucha znaków (i może takich dopasowań zwrócić wiele).

Czyli jeśli nasz wzorzec ma postać "[abc]e", to find dopasuje go do łańcucha "cel", w przeciwieństwie do metody matches:

Jeśli dodam na początku i na końcu wzorca uniwersalne symbole ".*" (uzyskując ".*[abc]e.*") to również metoda matches uzna, że mamy dopasowanie:

Jednak jednak użyjemy metody find przy odpowiednio złożonym i zagnieżdzonym wyrażeniu regularnym z opcjonalnymi elementami i przy odpowiednio długim tekście, pojawi się tyle możliwości dopasowania wyrażenia do łańcucha znaków, że wejdziemy na obszar tzw. Catastrophic Backtracking (katastrofalne nawracanie?).

Moja poprawka sprowadziła się, oczywiście, do usunięcia czterech znaków: ".*" z początku i końca wyrażenia przekazanego jako argument metodzie Element::getElementsByAttributeValueMatching.

Jaka z tego nauczka? Rutynowo zaglądać w cudzy kod, niestety.

PS. Polecam krzyżówki regeksowe: Regex Crossword.

Spring Boot: autoryzacja użytkowników w oparciu o bazę danych

O logowaniu użytkowników pisałam wcześniej w następujących postach:

Dzisiaj dodałam do mojej raczkującej aplikacji funkcjonalność logowania użytkowników zapisanych wcześniej w bazie danych.

Poniżej przedstawiam zmiany, które musiałam w tym celu wprowadzić do mojej aplikacji.

a)  Wersja podstawowa

W klasie User, która od teraz ma być nie tylko klasą przechowującą dane, ale także bazą do autoryzacji użytkowników, muszę zaimplementować interfejs UserDetails:

W klasie SecurityConfiguration opisuję nowy sposób logowania:

Brakuje mi odpowiedniej metody do pobrania obiektu użytkownika po jego nazwie (getUserByUsername) w klasie UserRepository. Do tej pory szukałam użytkowników jedynie na podstawie confirmationId (co opisałam tutaj: Aktywacja konta poprzez email). Na szczęście dzięki Spring Data nie muszę jej nawet implementować, wystarczy, że dodam następującą linię:

b) Wersja rozszerzona. Przecież ja szyfruję hasła!

W tym celu muszę zarejestrować odpowiedni bean szyfrujący:

Jego właśnie muszę użyć do zaszyfrowania hasła przy tworzeniu nowego użytkownika:

Należy też wspomnieć o nim w konfiguracji:

Klasa BCryptPasswordEncoder implementuję metodę matches z interfejsu PasswordEncoder, która sprawdza, czy hasło w czystej postaci pasuje do jego zahaszowanej wersji przechowywanej w bazie danych.

Już.

PS. Podczas testowania mój dostawca domeny i serwera zablokował mi konto email, przekonany, że ktoś się na nie włamał i rozsyła spam

Przeglądarka w Javie FX

Stanęłam ostatnio wobec następującego problemu: potrzebowałam aplikacji do ręcznej klasyfikacji stron internetowych. Chodziło o to, żeby wyświetlić użytkownikowi kolejną stronę z listy i udostępnić przycisk, który pozwoli odpowiednio ją oznaczyć.

Oczywiście Java nie jest najlepszym rozwiązaniem, jakie można tu zaproponować, ale chciałam zrobić to szybko w języku zrozumiałym dla większości zainteresowanych osób. Na Stack Overflow znalazłam kilka wątków pokazujących, jak uruchomić domyślną przeglądarkę internetową w danym systemie, ale po pierwsze przechwycenie danych z niej to wyższa szkoła jazdy, a po drugie chciałam otwierać kolejne strony w tej samej zakładce.

Z pomocą przyszła JavaFX, a dokładniej komponent WebView oparty na otwartym silniku WebKit. W tej wersji uruchomienie przeglądarki i obserwowanie zachowania użytkownika staje się dziecinnie proste. Poniżej całość kodu, który:

  • wyświetla stronę startową
  • wypisuje w konsoli kolejne strony odwiedzane przez użytkownika

Najważniejsze linie:

  • 13: aplikacja JavaFX
  • 22: tytuł okienka
  • 24: pionowy układ elementów
  • 27: etykieta wyświetlająca aktualny url
  • 30-31: przeglądarka i strona startowa
  • 34-41: obsługa zdarzenia: załadowanie nowej strony (z lambdą!)

Fajne, prawda?

Okno aplikacji z załadowaną stroną startową
Okno aplikacji z załadowaną stroną startową

 

Aktywacja konta poprzez email

Poprzedni wpis poświęciłam kwestii wysyłania maili przez aplikację napisaną w Javie.

Kontynuując, teraz chcę upewnić się, że świeżo zarejestrowany użytkownik to człowiek (a nie bot) i że podał poprawny adres email. Poniżej opisuję kroki prowadzącego do tego celu.

Krok 1: nieaktywny użytkownik z wygenerowanym kodem

Po wypełnieniu formularza rejestracyjnego obiekt reprezentujący użytkownika jest zapisywany w bazie danych jako nieaktywny. Razem z nim zapisywany jest losowo wygenerowany kod do aktywacji konta. Na tym etapie użytkownik nie może się logować, nie zna też przypisanego mu kodu.

Krok 2: email z linkiem aktywacyjnym

Aplikacja wysyła na adres podany przez użytkownika wiadomość zawierającą link aktywacyjny. Kod wygenerowany losowo w poprzednim kroku jest doklejony do adresu jako parametr URL.

Krok 3: obsługa linku aktywacyjnego

Wejście pod adres podany w mailu powoduje uruchomienie servletu weryfikacyjnego. Aplikacja szuka w bazie danych obiektu użytkownika, któremu przypisano kod podany jako parametr. Jeśli taki użytkownik istnieje, jest oznaczany jako aktywny. Od tej chwili może się już logować, ponieważ potwierdzono (czy raczej zwiększono prawdopodobieństwo), że to rzeczywista osoba.

Krok 4: usunięcie nieaktywnych kont

Należałoby usunąć zbyt długo przechowywane nieaktywne konta. Ale jeszcze się za to nie zabrałam 🙂

Ciekawostka: wyszukanie użytkownika w bazie

Oto ilość pracy, którą włożyłam w wyszukanie użytkownika w bazie danych MongoDB po wartości confirmationId:

Resztę robi za mnie Spring Data, parsując nazwę metody!

Więcej informacji na ten temat można znaleźć tutaj.

PS.

To ostatni techniczny wpis w ramach konkursu Daj się poznać 2016. Jutro postaram się napisać krótkie podsumowanie. O ile wieczorem na spotkaniu młodych mam nie poleje się za dużo wina.

 

Jak wysłać maila z aplikacji webowej w Javie?

Po co mi maile?

Warunkiem utworzenia konta użytkownika jest podanie poprawnego adresu email, do którego dany użytkownik rzeczywiście ma dostęp.

W kolejnym wpisie omówię dokładniej proces weryfikacji. Na teraz istotne jest to, że moja aplikacja musi być w stanie wysłać do użytkownika maila z linkiem do uaktywnienia konta.

Jak to zrobić?

Korzystam z frameworka Spring Boot (czyli właściwie z nakładki ułatwiającej korzystanie z frameworka Spring zgodnie z zasadą „konwencja ponad konfiguracją”).

Kroki do realizacji mojego celu są następujące:

1. Dodaję zależność w pliku pom.xml:

2. Piszę kod tworzący i wysyłający wiadomości:

3. Ustawiam właściwości aplikacji

Co może pójść nie tak?

com.sun.mail.smtp.SMTPSendFailedException: 530 5.7.0 Must issue a STARTTLS command first.

Koniecznie jest nadanie wartości true właściwości spring.mail.properties.mail.smtp.starttls.enable, a najlepiej całej trójce kończącej powyższy listing.

javax.mail.AuthenticationFailedException: 534-5.7.9 Application-specific password required.

To akurat sprawa typowa dla serwisu GMail. Jeśli chcesz, żeby aplikacja łączyła się z kontem email i w Twoim imieniu wysyłała maile, musisz stworzyć specjalne hasło aplikacji, różne od hasła prywatnego. W każdej chwili możesz podejrzeć listę aplikacji, które korzystają z takiego hasła (na stronie https://security.google.com/settings/security/apppasswords), a także dowolną z nich zablokować.

Google uprzejmie zwraca uwagę, że nadal nie jest to najbezpieczniejsze rozwiązanie.

com.sun.mail.util.MailConnectException: Couldn’t connect to host.

Ten błąd pojawił się, kiedy przełączyłam konto z GMaila na adres email na mojej wirtualnej instancji obsługującej kilka domen. Początkowo myślałam, że Java Mail nie chce pracować z „wirtualnymi” adresami obsługiwanymi przez serwer SMTP w innej domenie. Ostatecznie okazało się jednak, że przyczyną błędu był nadmiarowy biały znak w pliku application.properties 🙂

Ciekawostka: tymczasowe maile

Wiele aplikacji wymusza na użytkowniku podanie działającego adresu email. Główne powody to:

  • zwiększenie prawdopodobieństwa, że mamy do czynienia z człowiekiem, a nie robotem,
  • umożliwienie spamowania 🙂
  • zabezpieczenie użytkownika przed literówką w adresie email, która mogłaby doprowadzić nawet do utraty konta w aplikacji (brak możliwości zresetowania hasła bez działającego adresu).

Wiele osób tworzy sobie dodatkowe konto email, które używane jest do rejestracji w mniej istotnych serwisach (sama mam osobne konto do zakupów internetowych). Zdarza się jednak, że adresu email żąda strona lub aplikacja, z którą na pewno już nigdy nie będziesz mieć nic wspólnego. Co wtedy zrobić?

Możesz założyć tzw. jednorazowe (ang. disposable) konto email, np. w serwisie Guerilla Mail. Poczta, która tam trafia, jest przechowywana przez godzinę niezależnie od tego, czy ktoś ja odczyta, czy nie.

Ocenę moralną pozostawiam Czytelnikowi.

Jak zacząć z MongoDB?

Wprowadzenie

Najwyższa pora dodać do mojej konkursowej aplikacji jakąś bazę danych. Od początku zakładałam, że z powodów edukacyjnych użyję bazy nierelacyjnej. Zdecydowałam się w końcu na MongoDB. Jest to jedna z popularnych baz nierelacyjnych obsługiwanych przez mojego dostawcę PaaS (Platform as a Service), tj. Pivotal Web Services.

Źródła

Swoją działalność w tym obszarze opieram na następujących źródłach:

Pierwsze kroki

Po instalacji serwera bazodanowego wykonuję następujące kroki:

  1. Uruchamiam proces serwera MongoDB:
  2. Przygotowuję rekord reprezentujący użytkownika mojego systemu (plik user.json). Rekordy w MongoDB mają postać dokumentów w standardzie JSON (w przybliżeniu). Wartościami pól mogą być również inne dokumenty oraz tablice (w tym tablice dokumentów). Dokumenty są przechowywane w ramach kolekcji. Kolekcja przypomina tablicę w relacyjnej bazie danych, tyle że zawarte w niej dokumenty wcale nie muszą mieć tej samej struktury. Dokumenty w kolekcji muszą mieć klucz główny o nazwie _id:
  3. Importuję rekord użytkownika do bazy danych:
  4. Dodaję sterownik MongoDB jako zależność w mavenowym pliku pom.xml.
  5. Łączę się z bazą danych z kodu w Javie:
  6. Wyszukuję użytkowników:
  7. Zmieniam wartość jednego z pól użytkownika (dokument d pełni tu rolę wzorca, według którego odnajdywany jest rekord z bazy):
  8. Nakładam indeks na nazwy użytkowników:

No i działa 🙂

Cały plik jest dostępny w GitHub.

Co dalej?

  1. Na serio dodać to do aplikacji 🙂
  2. Zastanowić się nad użyciem Hibernate.
  3. Zastanowić się, jak przechowywać zdjęcia.

Pozbywam się powtarzalnych fragmentów HTML przy użyciu Thymeleaf

Każdy programista wie, że duplikacja w kodzie źródłowym to zło, gdyż:

  • jeśli zduplikowany kod zawiera błąd, trzeba naprawić go w kilku miejscach (i z reguły o którymś się zapomina),
  • długi kod trudniej się czyta.

A jednak zadanie przerzucenia powtarzanych fragmentów HTML (jak nagłówek i stopka wyświetlane na wszystkich podstronach) do osobnych, reużywalnych plików wcale nie jest trywialne!

Mój plik index.html w oryginalnej postaci zawierał 71 linii, z czego około 50 było wspólnych z innymi szablonami (nagłówek, stopka, menu, załączane skrypty js). Zależało mi na wyodrębnieniu części wspólnych do własnych plików. Chociażby po to, żeby dodanie pozycji z menu nie musiało być odwzorowywane w pięciu miejscach.

Jakie miałam możliwości?

  1. Oprogramować operację include w JavaScripcie (lub użyć czyjegoś kodu, np. z W3Schools). Wady:
    • dużo JavaScriptu,
    • metoda z gatunku nieeleganckich hacków.
  2. Użyć znaczników HTML: object, embed lub nawet iframe. Wady:
    • dwa pierwsze znaczniki zostały zaprojektowane do osadzania na stronach obiektach takich jak filmy, choć da się ich użyć tak że do wstawienia dokumentu HTML,
    • wszystkie służą do dodania do strony kompletnego dokumentu, a nie fragmentu,
    • styl CSS obowiązujący na stronie nie zadziała na załączone w ten sposób fragmenty.
  3. Skorzystać z mechanizmu udostępnianego przez silnik szablonów, w tym wypadku Thymeleaf. Wady:
    • serwer wykonuje pracę, którą mógłby wykonać klient 🙂
    • ewentualna zmiana silnika szablonów staje się jeszcze bardziej kosztowna.

Zdecydowałam się na rozwiązanie numer 3. Jak to wygląda w praktyce?

Na wszystkich moich stronach powtarzał się poniższy fragment:

Przeniosłam go (wraz z innymi podobnymi elementami) do osobnego pliku _menus.html:

Dzięki temu mogę załączać go wszędzie tam, gdzie jest potrzebny, w następujący sposób:

Pułapka 1: Na początku umieściłam atrybuty id i class we fragmencie załączanym (w _menus.html). Zostały zjedzone 🙂

A co, jeśli fragment, który chcę załączyć, nie jest otoczony sensowną parą znaczników? Mogę wtedy użyć bloku, np.:

I załączyć go w analogiczny sposób:

W obecnej chwili index.html ma tylko 32 niepuste linie i dużo zyskał na czytelności, podobnie jak inne szablony.

Pułapka 2: Po wyodrębnieniu stopki do osobnego pliku, moim oczom ukazał się taki oto widok:

Problem z kodowaniem załączonego pliku
Problem z kodowaniem załączonego pliku

Wszystkie pliki mam zakodowane w UTF-8, takie samo kodowanie deklaruję w nagłówku HTML. Poczytałam trochę o problemach z kodowaniem na linii Spring/Thymeleaf, ale ostatecznie, ponieważ na teraz jest to jedyne wystąpienie polskiego znaku na mojej stronie, po prostu zmieniłam odpowiadającą za to linię na:

 

 

 

 

Czasami coś idzie nie tak… Moja własna strona błędu

Nadal w ramach Daj się poznać.

Podobny obrazek widział chyba każdy programista aplikacji webowej. Od czasu do czasu serwer Tomcat postanawia poinformować użytkownika aplikacji, że coś poszło nie tak. Informuje dość brutalnie, przynajmniej od strony stylistycznej. Co gorsza, przy okazji potrafi wyjawić przypadkowemu użytkownikowi dość dużo informacji na temat wnętrza programu.

Informacja o błędzie wygenerowana przez serwer Tomcat. W tym wypadku żądana strona nie istnieje.
Informacja o błędzie wygenerowana przez serwer Tomcat. W tym wypadku żądana strona nie istnieje. Czasami można w tym miejscu zobaczyć ciekawszy materiał, czyli stack trace.

Tego typu informacje przydają się do debugowania aplikacji na etapie jej powstawania, ale użytkownik końcowy nie powinien ich oglądać (chociażby z powodów estetycznych).

Jak sobie z tym poradzić? Framework Spring Boot automatycznie ustawia dla nas nieco tylko ładniejszą stronę błędu:

Whitelabel Error Page, czyli strona błędu generowana przez Spring Boot
Whitelabel Error Page, czyli strona błędu generowana przez Spring Boot

Mechanizm ten można wyłączyć we właściwościach aplikacji (application.properties):

Także zmiana domyślnego ViewResolver może usunąć tę wersję informacji o błędzie i przywrócić wersję tomcatową.

Jeśli zamiast strony prezentowanej przez Springa chcesz ustawić swoją własną, musisz zerknąć do dokumentacji wybranego przez Ciebie silnika szablonów. W przypadku Thymeleaf wystarczy dodać szablon o nazwie error.html:

Szablon błędu dla silnika Thymeleaf
Szablon błędu dla silnika Thymeleaf

Dzięki temu strona błędu może wyglądać na przykład tak:

error

Czy jest ładniejsza… Pozostawiam ocenie Czytelnika 😀

Historie komórkowe

Ten tekst miał nazywać się „Mobilna wersja strony: podejście 2” w nawiązaniu do Mobilna wersja strony w Spring Boot. Potrzebowałam do niego zrzutu ekranu z mojego telefonu, a jedno spojrzenie na niego wyzwoliło całą lawinę wspomnień… Które również zamieszczam poniżej.

@media

Najpierw krótko o konkretach: w poprzednim wpisie pokazywałam, jak Spring Boot pozwala stworzyć mobilną wersję strony i zarządza jej wyświetlaniem. Wychodzi jednak na to, że w dzisiejszych czasach istnieją lepsze metody. Prym wśród nich wiodą CSS3 i reguły @media. Logika odpowiedzialna za formatowanie informacji trafia w tym modelu dokładnie tam, gdzie powinna: do arkusza stylów. W arkuszu stylów, przy użyciu reguł @media, możemy podjąć decyzję na temat sposobu wyświetlania informacji w zależności od tego, jakie urządzenie korzysta ze strony (np. ekran czy drukarka?), jaką ma orientację (pozioma czy pionowa?) bądź rozdzielczość.

Prosty przykład ze strony W3Schools:

Kolor tła zostanie zmieniony na magentę (ekhem, fuksję), jeśli szerokość wyświetlacza wyniesie co najmniej 480 pikseli.

W swojej konkursowej aplikacji postanowiłam pójść po najmniejszej linii oporu – znalazłam darmowy szablon CSS implementujący ten mechanizm 🙂 Wygląda to obecnie tak:

Przeglądarka na PC
Przeglądarka na PC
Strona wyświetlona na ekranie Samsung Galaxy Xcover 3
Strona wyświetlona na ekranie  urządzenia mobilnego o niskiej rozdzielczości

Telefony 🙂

Co do kiepskiej jakości urządzeń mobilnych… Jestem specyficznym typem użytkownika: masowo rozbijam telefony. Nie rzucam nimi ze złości (od dobrych paru lat). Same wypadają: z kieszeni, z torebki, z samochodu. Kąpią się w wannie i w napojach. Psują się.

Przedstawiony powyżej zrzut ekranu pochodzi z mojego Samsung Galaxy Xcover 3. A było tak.

W połowie ubiegłego roku doszłam do wniosku, że nie stać mnie na kupowanie co dwa miesiące nowego dobrej klasy smartfona (w obliczu śmiertelnego wypadku poprzednika). Zdecydowałam się wtedy na zakup urządzenia niskiej klasy, za to w metalowej obudowie. Przesadziłam – nie działało na nim nawet quizzwanie. Na szczęście, problem szybko rozwiązał się sam. Pierwszego dnia telefon wypadł mi z ręki, kiedy przechodziłam przez przedpokój na piętrze. Pięknym lobem poszybował w stronę krętych schodów, po czym szybem w rogu spadł aż do samej piwnicy. Kiedy bez większych nadziei zeszłam na dół, okazało się, że urządzenie nadal działa, chociaż róg ma zauważalnie wgnieciony. Nabrałam do niego nowego szacunku. Dwa dni później wypadł mi z tylnej kieszenie spodni, kiedy szukałam kluczy na podjeździe – i zbił się na amen.

Wtedy wpadłam na radykalnie odmienny pomysł: kupić drogie i dobre urządzenie, o które będę drżeć i którego wobec finansowych rozterek z pewnością nie upuszczę na podłogę. Wybrałam najnowszy model Galaxy Note. Telefon był piękny, wygodny, szybki… Tyle że trafiłam na wadliwy egzemplarz, który restartował się w losowo wybranych momentach kilka razy dziennie. Przegapiłam (tygodniowy) termin na oddanie telefonu bez wyjaśnień w lokalnym Brand Store, ponieważ od razu pojechałam z nim na wakacje. Po powrocie najpierw wmawiano mi, że wyłączyłam telefon podczas aktualizacji i dlatego działa źle. Potem kilka razy zwracano mi go jako naprawiony – pierwszą rzeczą, jaką robił po włączeniu (najpierw w domu, potem już w sklepie), był restart. Pikanterii sprawie dodawał fakt, że byłam wtedy w bardzo zaawansowanej ciąży i brak sprawnego telefonu był coraz bardziej ryzykowny. Po około miesiącu użyłam na piśmie magicznego sformułowania „Miejski Rzecznik Konsumentów” i odzyskałam pieniądze. Nie chciałam już innego egzemplarza (o który dopominałam się od początku, ustaliwszy szybko, że problem jest sprzętowy i dotyczy konkretnej sztuki w ogólności dobrego modelu), bo nie miałam ochoty na jeszcze jedno spotkanie z serwisem.

Lato 2015, Mazury. Dawid i Gałgan ćwiczą pracę wodną. Ja łapię zasięg i reinstaluję Androida
Lato 2015, Mazury. Dawid i Gałgan ćwiczą pracę wodną. Ja łapię zasięg i reinstaluję Androida

Potem chciałam tanio i dobrze kupić One Plus. Powstrzymało mnie to, że właśnie pojawił się nowy model. Kosztował dokładnie tyle, co stary, ale do zakupu wymagane było posiadanie zaproszenia. Kto normalny kupiłby starą wersję, jeśli nowa kosztowała tyle samo?! A zaproszenie ciągle nie nadchodziło…

Wtedy wygasła umowa na usługi komórkowe w organizacji, do której należę i pojawiła się możliwość uzyskania bez dopłaty cuda w postaci Samsung Galaxy Xcover 3. Na kolana powala przede wszystkim rozdzielczość (480 x 800px), ale pancerny telefon nadal działa po dziesiątkach upadków, kąpieli i ugryzień. I takiego doświadczenia życzę wszystkim!