- Mam wątpliwości co do attachView i detachView.
Dobrze rozumiem, że służą one np. do ustawiania flagi określającej czy View jeszcze istnieje? Chodzi o to, żeby np. nie próbować wyświetlić pobranych danych w Fragmencie/Activity, które już nie istnieje? Jeśli tak, to po co przekazywać i przechowywać view, skoro można stworzyć flagę.
Tak. Ideą jest to, żeby nie wysyłać takich danych, jeżeli widok umarł. Nie robi się tego za pomocą flagi, żeby nie mieć wycieków pamięci. Gdyby Twój widok był private final View view
i kontrolowałbyś dostęp do niego za pomocą boolean isViewAttached
, to w momencie, gdy widok chciałby sobie spokojnie umrzeć, nie mógłby tego zrobić, ponieważ trzymałbyś do niego referencję w prezenterze. Można to rozwiązać za pomocą np. WeakReference
, ale po co kombinować pod górę, skoro można równie dobrze widok nullować.
Zwróć też uwagę, że widok jest volatile
, żeby inne wątki odpalone w prezenterze były świadome usunięcia widoku. To też można rozwiązać za pomocą jakiegoś mechanizmu, który wewnątrz prezentera wysyłałby wszystko na główny wątek przed wykonaniem czegoś na View. Tylko czemu prezenter ma mieć w ogóle jakąkolwiek świadomość tego, że istnieje główny wątek? Niech widok lepiej sam o siebie zadba pod tym względem i jeżeli kiedykolwiek, cokolwiek ma zrobić, zrobi to na głównym wątku. Pewnie niektórzy lubią tworzyć jakąś abstrakcję na to, która będzie wysyłała wiadomości na główny wątek z prezentera. Osobiście nie lubię, ale kwestia smaku bym powiedział. Z RxJavą np. to już w ogóle żaden problem, bo taka abstrakcja jest tam za darmo i wystarczy przekazać tylko Scheduler
.
- Nie bardzo rozumiem przedstawioną przez Ciebie ideę rozbijania modelu.
Powinienem rozdzielać model na wiele małych klas ze względu na jakąś szczegółową funkcjonalność, typ danych (np. Movie, Book, Game), czy źródło danych tak jak w repo na githubie (np. baza danych, SharedPreferences)? Pierwszego przypadku nie do końca rozumiem, a w drugim i trzecim nie mogę sobie wyobrazić po co mi jakoś szczególnie dużo klas.
Jeżeli chodzi o reprezentację danych za pomocą klas, to oczywiście jakieś Movie, Book, Game, itd. Ale to nie jest de facto Model przez wielkie M. To tylko model danych (chyba że ktoś ma potrzebę robienia DDD), który jest używany w Modelu, żeby reprezentować odpowiednio stan aplikacji. Rzecz w tym, że nie ma czegoś takiego jak uniwersalny Model, bo w nim zamyka się całe mięso konkretnej aplikacji. W Androidzie może nie całe mięso, bo musimy się bawić z cyklem życia, ograniczonymi zasobami, zdarzeniami i pozwoleniami systemowymi, obsługą sensorów w przypadku np. kamery i pewnie innymi przypadkami, które mi teraz do głowy nie przychodzą. Niemniej, całe biznesowe mięso aplikacji powinno być zamknięte w Modelu i być niezależne od platformy. Dobrze jest też, kiedy nawet warstwa prezentacji jest niezależna od platformy — w końcu po to jest interfejs dla View.
Wracając do SharedPreferences
i bazy danych, jako potencjalnych kontenerów do przetrzymywania, powinny one implementować wspólny interfejs, jeżeli mają przechowywać te same dane, i w trakcie działania programu decydowałbyś o tym, który ma być użyty. Przykładowo (użyję RxJavy jako callbacków do zapisu i odczytu). Chociaż na dobrą sprawę SharedPreferences
nie powinno być wykorzystywane do tego rodzaju zapisu.
Kopiuj
interface MovieDao {
Completable updateMovies(List<Movie> movies);
Observable<List<Movie>> favoriteMovies();
Observable<List<Movie>> topRatedMovies(int count);
}
Potem miałbyś różne implementacje tego interfejsu w postaci SqlLiteMovieDao
, SharedPreferencesMovieDao
, InMemoryMovieDao
itd. Zależy, czego potrzebujesz. To jest już cześć Modelu i może być tak rozumiane w postaci prostych aplikacji. Jednak gdy aplikacja jest już bardziej skomplikowana i potrzeba zaawansowanej interakcji między różnymi elementami Modelu, to wchodzą wtedy do gry różne interactory, use casy itp. Takie klasy Mogą mieć w nazwach "Interactor" czy "UseCase", ale nie muszą. Kwestia przyjętych standardów w projekcie. Może Ci się wydawać, że to dużo klas, ale to naprawdę nie jest problem, tylko dobra praktyka, która się zwraca przy kompozycji i modularności. Zdecydowanie lepiej jest mieć dużo klas odpowiedzialnych za jedną rzecz niż jedną klasę odpowiedzialną za wszystko.
Załóżmy, że dla tych trzech implementacji DAO chcesz stworzyć kolejne DAO, które je połączy (np. AggregatedMovieDao
) i np. w przypadku gdy dopiero wystartowałeś aplikację, to dane będą przychodzić z SqlLiteMovieDao
. Jeżeli ta jest pusta, to nastąpi synchronizacja z zewnętrznym serwisem w międzyczasie. A może synchronizacja będzie następować za każdym razem, kiedy poprosisz o dane. Kwestia tego, czego potrzebujesz. Jeżeli dane istnieją, to możemy używać InMemoryMovieDao
jako dostawcy danych przez powiedzmy 30 minut, bo zakładamy, że przez tyle dane są w miarę świeże i niezdezaktualizowane. Takie interakcje powinny być też pozamykane we własnych klasach czy w tym wypadku operatorach/funkcjach RxJavy. Zasadniczo nie ma reguł i tu zamyka się clou aplikacji.
- Czy podział modelu w repo jest dobry zakładając, że nie byłoby tam Interactora, tylko jego funkcję przejąłby Presenter?
Nie chce mi się przeglądać tam wszystkiego, ale kilka rzeczy na szybko mi się nie podoba.
- Nazwa warstwy dostępu do lokalnych danych to Repository. Rozumiem, że w środowisku Androida taka nomenklatura się z jakichś dziwnych powodów przyjęła, ale drażni mnie za każdym razem, kiedy to widzę. To są zwykłe DAO, a nie żadne repozytoria.
- Źle zdefiniowane metody w "repozytoriach". Każda metoda zwraca
Observable
. W tym wypadku należałoby użyć Single
i Completable
. Observable
informuje, że dane mogą przychodzić przez dłuższy czas, a nie są jednorazowymi strzałami.
- Wstrzykiwane klasy z nazwami "Helper", to jeden, wielki WTF. "Helper" sugeruje, że ma jakieś metody rozszerzające funkcjonalość innych klas, których nie możemy edytować, takich jak
String
. Najlepiej jakby te metody były statyczne, ale jak nie mogą, to po kiego wała je wstrzykiwać. Znaczy się, w tym wypadku wiadomo po co — bo to nie są żadne helpery, tylko źle nazwane klasy robiące coś konkretnego.
- Taki podział na pakiet o ile wydaje się w porządku, to na dłuższą metę zabija orientację w projekcie. Powinien być np. pakiet
user
(albo jeszcze lepiej moduł, ale już nie wchodźmy w ten temat) i w nim opisane wszystkie zachowania z użytkowinikiem (ewentualnie w podpakietach, jeżeli coś się za bardzo rozrasta - user.ui
, user.data
, user.api
itd.).
@kulson
Dzięki za sugestię, ale jednak na ten moment zostanę przy MVP - po prostu lepiej znam tą architekturę i jest bardziej popularna w środowisku androidowym. Mimo wszystko jest to ciekawe rozwiązanie i przyjrzę mu się bliżej.
Wiesz, na dobrą sprawę, czy to MVP, MVVM, MVHipsterAbbreviationOfTheWeek, to nie ma znaczenia. To nie jest de facto architektura aplikacji. To tylko opis interakcji pomiędzy modelem a widokiem. Równie dobrze wszystko można nazywać prezenterem. O to mi chodziło w poprzednim poście, jak pisałem o strumieniach i subskrybcjach. Architektura to coś znacznie głębszego niż rzucenie skrótowca na klasę. Google niestety podąża za tym owczym pędem i z jakichś idiotycznych powodów zdecydowało się nazwać nowy zestaw bibliotek "Architecture components", kiedy z samą architekturą nie mają one dużo wspólnego. I nie mówię, że są to złe biblioteki, bo są one w większości naprawdę fajne. Zwłaszcza Room (chociaż wolę SqlDelight), LifecycleObserver/Owner i Paging. Trochę nie rozumiem, po kiego grzyba jest komponent ViewModel, ale jak jest, to niech będzie. W zasadzie to w pracy używam ViewModel od Googla, bo tak jest łatwiej w zespole, gdzie jest rotacja ludzi.
No i sam też nie daj się porwać owczemu pędowi. Jeżeli typowe, czyste MVP sprawdza się u Ciebie w aplikacji, to zostaw. O ile sam uważam, że tego typu komunikacja między komponentami jest w Androidzie uciążliwa, to jest perfekcyjnie w porządku używać klasycznego MVP. Jak będziesz chciał się douczyć np. RxJavy albo LiveData i dodać interakcje w postaci subskrybcji, to możesz ich używać dla nowych interakcji, zamiast przebudowywać całość projektu. Będzie to nawet o tyle lepsze, że odda bardziej realny przypadek, gdzie już masz gotowy produkt na produkcji i strona biznesowa (w sensie menadżment) nie da Ci czasu na przebudowę wszystkiego ad hoc. Zobaczysz wtedy jakie są wady i zalety wszystkiego, czy i jak ciężkie może być połączenie dwóch róznych rodzajów komunikacji, jak się pracuje z legacy. Wszystko to są przydatne umiejętności dla programisty. Nawet bardziej niż znajomość nowych, hipsterkich skrótów.