Android- mvp, clean architecture, rx - kilka pytań

0

Cześć, od kilku miesięcy pracuję jako programista aplikacji mobilnych - android oraz ios. Chciałbym rozwijać się coraz bardziej, dlatego staram się wyszukiwać jakieś ciekawe artykuły na temat dobrych praktyk( np. artykuły z realma są ciekawe).

Ostatatnio zacząłem sobie ogarniać mvp z githuba googla : https://github.com/googlesamples/android-architecture

Co o tym sądzicie ? Jest tam też o rx oraz clean architecture. Mam teraz do napisania aplikacje kolejną na androida po godzinach pracy i chciałbym wprowadzić właśnie to mvp i coś jeszcze, aby zobaczyć jak to w praktyce działa. Na pewno użyję butterknifa, retrofita i teraz się zastanawiam nad clean architecture, databinding i rx. Czy to są wymienne podejścia czy można je jakoś łączyć ? Na razie trochę poruszam się jeszcze po omacku po tym .. tzn rozumiem, że data binding to wstrzykiwanie modelu bezpośredniu do widoku i automatyczny refresh widoku po zmianie modelu; rx to inne podejście do programowania, a clean architecture to wprowadzenie dodatkowej warstwy z uses case'ami ( patrzyłem na ten przykład z githuba googla, ale nie do końca jeszcze czuję po co to jest - łatwiejszy i przejrzystszy kod wydaje się być w czystym MVP ).

I jeszcze jedno pytanie - czy jeśli ogarnę te rzeczy na androida, to na iosa łatwiej mi to przyjdzie ?

Pozdrawiam

1

Praktyki związane z pisaniem czystego kodu są przenoszalne, niezależnie od platformy.

Przy implementacji RX'a trzeba się namęczyć. Bo trzeba dodać lambdy, a żeby dodać lamby trzeba mieć Jave w wersji 8 co w przypadku Androida jest trochę problematyczne. No albo można dodać retrolambdy, ale tutaj mamy także dwa końce tego rozwiązania - dobrze to wygląda, ale Lint jest tak upośledzony, że omija podczas statycznej analizy kodu klasy z lambdą bo nie potrafi ich otworzyć.

Należy pamiętać, że MVP jest tylko narzędziem do wykonania czegoś większego jakim jest właśnie clean architecture a ten może być realizowany na wiele sposobów. Głównym założeniem Clean Architecture jest odpowiednia separacja warstw tak aby kod był jak najlepszy w modyfikacji oraz rozszerzaniu. To co jest w tym repozytorium Google'a jest tylko przykładem i nie musisz tego brać za pewnik bo czysty kod można czy czystą strukturę można otrzymać na wiele sposobów.

Co do Databindingu to jest bardzo spoko rzecz, znacząco ułatwia wytwarzanie nowych funkcjonalności, ale ma jedną brzydką rzecz jak mieszkanie logiki biznesowej w pliku xml widoku przez co ten staję się strasznie nieczytelny.

Aktualnie w jednym projekcie dla zachodniego klienta zrobiłem wdrożenie czystej architektury i jak na razie wszyscy są zadowoleni z mojego rozwiązania ;).

UseCase to jest przypadek użycia aplikacji, czyli wszystkie biznesowe interakcje w aplikacji to usecase'y. Jest to po to aby nie mieszać prezentera do tego co ma robić. Prezenter jest tylko do zarządzania widokiem i uruchamianiem tych właśnie interakcji (usecaseów). Gdyby Prezenter miałby w sobie jakiekolwiek przypadki użycia to byłoby jednoznaczne z złamaniem zasady pojedynczej odpowiedzialności. Co nie zmienia, że prezenter może mieć wiele use'caseów (bo na widoku mogą być różne interakcje biznesowe).

Inna sprawa, że w tych repozytoriach googla moim zdaniem jest jedna krytyczna wada - wszystko jest w app module. Czyli teoretycznie warstwa biznesowa która nie powinna być zależna od platformy jest w module typowo Androidowym co jest złamaniem zasady Clean Architecture. Powinno się budować CA od podzielenia aplikacji na odpowiednie moduły w Gradle'u (np Domain oraz Presentation jest typowo Javowym kodem a Data oraz App Andoidowym). Dzięki czemu mamy twarde zabezpieczenie przed niechcianymi importami na warstwie biznesowej z frameworka Androidowego.

Dobra, koniec pisania tego posta bo zaraz z tego zrobię wykład na temat CA w Androidzie (za długo w nim siedzę).

0

Bardzo Ci dziękuję za kompleksową odpowiedź :) .. no właśnie widziałem na jakiś filmach ze rx + retrolambda skraca strasznie kod. Ale Twoim zdaniem warto w to iść, czy jednak lepiej póki co ogarnąć dobrze czystą architekturę ?

Co do tego databinding to fakt, widok przestaje być czystym widokiem - przypomina mi to trochę pliki html gdzie wstrzykujemy modele ze springa. Ale zastanowię się jeszcze nad tym.

I wracając do rdzenia tematu .. nie wiem czy dobrze rozumiem te przypadki użycia - pisałem aplikację gdzie można było lajkować posty. I to wyglądało tak, że na kliknięcie buttonu działa się jakaś akcja na widoku(zakolorowanie/niezakolorowanie serca), następnie szło zapytanie do sewera i w razie błędu zmiany na widoku były cofane. I taka operacja powtarzała się w dwóch miejscach apki. Czy to właśnie mógłby być use case ?

Miałbyś może zaproponować jakieś dobre lektury/filmy/repozytoria androida zawierające dobry kontent na temat czystej architektury ?

Pozdrawiam serdecznie :)

1
Zakręcony Jeleń napisał(a):

Bardzo Ci dziękuję za kompleksową odpowiedź :) .. no właśnie widziałem na jakiś filmach ze rx + retrolambda skraca strasznie kod. Ale Twoim zdaniem warto w to iść, czy jednak lepiej póki co ogarnąć dobrze czystą architekturę ?

Co do tego databinding to fakt, widok przestaje być czystym widokiem - przypomina mi to trochę pliki html gdzie wstrzykujemy modele ze springa. Ale zastanowię się jeszcze nad tym.

I wracając do rdzenia tematu .. nie wiem czy dobrze rozumiem te przypadki użycia - pisałem aplikację gdzie można było lajkować posty. I to wyglądało tak, że na kliknięcie buttonu działa się jakaś akcja na widoku(zakolorowanie/niezakolorowanie serca), następnie szło zapytanie do sewera i w razie błędu zmiany na widoku były cofane. I taka operacja powtarzała się w dwóch miejscach apki. Czy to właśnie mógłby być use case ?

Miałbyś może zaproponować jakieś dobre lektury/filmy/repozytoria androida zawierające dobry kontent na temat czystej architektury ?

Pozdrawiam serdecznie :)

Mogę polecić pair programming z mną.

Teraz mam projekt z Databinding w którym to wiele rzeczy skraca, ale powoduje dużo nieczytelności. Jednak mimo wszystko warto znać i syntezować z architekturą.

Lajkowanie postu flow:

Activity ->presenter.likePost(postId);

public void likePost(int postId){
useCaseFactory.getLikePostUseCase(new LikePostCallback(),postId).execute();
}

I dalej idzie to tak, że factory zwraca nam odpowiedniego UseCase który w konstruktorze przyjmuje interfejs EntityGateway i tak dalej.

0

@MrHyperion: :

Czyli mniej więcej dobrze to rozumiem ..

Pair programming, to zawsze chyba najwięcej nauczy, a na jakiej zasadzie to widzisz ?

EDIT: przypomniałem sobie pasy do konta :)

A jaki jest flow w sytuacji, kiedy to nie jest widok pojedynczego postu z 1 buttonem like, tylko mamy recyclerview z postami i każdy ma swój button od lika ?
Wtedy mam jakiś view holder reprezentujący post. Do tej pory robiłem tak, że do adaptera, który zawiera wewnętrzną statyczną klasę view holdera tego posta i wstrzykuje temu adapterowi jakiś OnItemClickListener z metodą onItemClick(int position, Object data) i mam do tego dostęp gdzieś wyżej, ale nie wiem czy to dobre rozwiązanie. Jak to powinno wygładać jeśli mamy używac use case'y ?

Jestem też ciekawy co do tego dzielenia apki ma moduły przez gradla - chodzi po prostu o pakiety: app, data, domain, presenter czy to jakaś grubsza sprawa ?

1
Bambo napisał(a):

@MrHyperion: :

Czyli mniej więcej dobrze to rozumiem ..

Pair programming, to zawsze chyba najwięcej nauczy, a na jakiej zasadzie to widzisz ?

EDIT: przypomniałem sobie pasy do konta :)

A jaki jest flow w sytuacji, kiedy to nie jest widok pojedynczego postu z 1 buttonem like, tylko mamy recyclerview z postami i każdy ma swój button od lika ?
Wtedy mam jakiś view holder reprezentujący post. Do tej pory robiłem tak, że do adaptera, który zawiera wewnętrzną statyczną klasę view holdera tego posta i wstrzykuje temu adapterowi jakiś OnItemClickListener z metodą onItemClick(int position, Object data) i mam do tego dostęp gdzieś wyżej, ale nie wiem czy to dobre rozwiązanie. Jak to powinno wygładać jeśli mamy używac use case'y ?

Jestem też ciekawy co do tego dzielenia apki ma moduły przez gradla - chodzi po prostu o pakiety: app, data, domain, presenter czy to jakaś grubsza sprawa ?

Android jest tak upośledzony, że widok jest traktowany jako logika biznesowa, to widać właśnie na przykładzie ViewHolderów czy całych Recyclerów w których są zaimplmentowane onCliki. Ja robię tak, że Adapter przyjmuje tylko i wyłącznie presentera, nie ma informacji odnośnie tego jaka jest kolekcja do wyświetlenia czy o elemnty tej kolekcji. Wszystkie dane potrzebne dla recyelra idą z presentera.

View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_prelegent, parent, false);
PrelegentViewHolder holder = new PrelegentViewHolder(view);
view.setOnClickListener(click -> presenter.itemClick(holder.getAdapterPosition()));
0

@MrHyperion: Te wszystkie rzeczy wiesz z jakiegoś konkretnego źródła czy sam na to wpadałeś w trakcie pracy? Sam też staram się stosować jakieś solidne wzorce w swoim kodzie i próbuje pisać tak żeby ktoś po mnie nie płakał, ale np. rozwiązanie z przekazywanie presentera do adaptera widzę pierwszy raz. Zasadniczo na tą chwilę widzę same zalety takiego rozwiązania, jednak sam tego nie robię bo na to nie wpadłem.
TLDR: Czy sa jakieś źródła których używasz?

0
Lukasz_ napisał(a):

@MrHyperion: Te wszystkie rzeczy wiesz z jakiegoś konkretnego źródła czy sam na to wpadałeś w trakcie pracy? Sam też staram się stosować jakieś solidne wzorce w swoim kodzie i próbuje pisać tak żeby ktoś po mnie nie płakał, ale np. rozwiązanie z przekazywanie presentera do adaptera widzę pierwszy raz. Zasadniczo na tą chwilę widzę same zalety takiego rozwiązania, jednak sam tego nie robię bo na to nie wpadłem.
TLDR: Czy sa jakieś źródła których używasz?

@Lukasz_

Dostawałem dużo negatywnego feedbacku w PR'ach w mojej pierwszej firmie. Tak normalnie wiedzę czerpie z różnych źródeł. Trochę szkoleń, trochę pair programmingu z seniorami po pracy, trochę czytania (Android Weekly czy jakieś posty na Medium.com), trochę YouTuba, trochę czystego kodu, ale nigdy nie czytałem Symfonii :P.

0

@MrHyperion: A powiedz mi na przykład: czy presentery u Ciebie (konkretnie Android) są statycznymi składnikami fragmentów/aktywności? Chodzi konkretnie o ta statyczność. Czy wiążesz je z androidowym cyklem życia?

0
Lukasz_ napisał(a):

@MrHyperion: A powiedz mi na przykład: czy presentery u Ciebie (konkretnie Android) są statycznymi składnikami fragmentów/aktywności? Chodzi konkretnie o ta statyczność. Czy wiążesz je z androidowym cyklem życia?

@Lukasz_

Presentery są wtrzykiwane do aktywności poprzez Daggera. Wiąże je, ale w bardzo uproszczony sposób - onAttachView oraz onDetachView. Nie ma sensu dorzucania więcej metod i tworzenia zagmatwanego lifecycle presentera w celu synchronizowania go z fragmentem czy aktywnością bo wyjdzie drugi lolcycle.

0

@MrHyperion: Czyli de facto twoje presentery są singletonami? Czy używasz jakiegoś innego scope'a?

0

Ja jeszcze mam pytanie odnośnie walidacji. W fragmencie(widoku) używam sobie butterknifa i załóżmy po kliknięciu w button chciałbym wysłać wypełniony formularz do serwera, więc robię to tak:

@OnClick(R.d.button)
void sendForm() {
       presenter.send(textField1.getText().toString(), ... );
}

No ale co jeśli chciałbym wprowadzić walidację. Mam napisane jakieś tam walidatory, które implementują interfejs Validator z metodą isValid(). Te walidatory mam utworzyć w widoku i sprawdzać przed presenter.send(..) walidacje ? I wtedy stworzyłbym dodatkową prywatną metodą showErrors() widoku ? Bo zastanawiam się czy w kontrakcie widoku (interfejsie) powinno być to showErrors() skoro presenter tego nie wywołuje tylko ona sama się wywołuje z widoku ?

Poniżej moja wizja tego:

interface View {
    void showNextScreen();
}

interface Presenter {
    void sendForm(String str1, String str2);
}

public class FormFragment extends Fragment implements View {

    @Override showNextScreen() {
        //start new activity
    }

    @OnClick(R.id.button)
    void sendForm() {
        if (usernameValidator.isValid() && emailValidator.isValid()) {
            presenter.sendForm(editText1.getText().toString(), editText2.getText().toString());
        } else {
            showErrorDialog();
        }
    }

    private void showErrorDialog() {
        // show any dialog
    }

}

public class RegisterPresenter implements Presenter {   
    
    @Override
    public void sendForm(String str1, String str2) {
        Form form = new Form(str1, str2);
        
        // send to server
        
        view.showNextScreen();
    }
    
}
0

a pewno użyję butterknifa, retrofita i teraz się zastanawiam nad clean architecture, databinding i rx. Czy to są wymienne podejścia czy można je jakoś łączyć ?

Można łączyć, bo choć są pokrewne, odnoszą się do nieco innych rzeczy.

Clean architecture mówi o strukturze całości, z lotu ptaka. Chodzi o to, żeby aplikacja była ładnie rozcięta na warstwy. Nie przesądza o doborze narzędzi. Można mieć clean architecture bez RxJavy i databindingu, a z drugiej strony użycie ich obydwu nie gwarantuje, że uzyskamy clean architecture. Natomiast dobrze się ze sobą połączą.

Databinding dostarcza "kleju", dzięki któremu możemy uprościć synchronizację widoku z podpiętym do niego modelem (czy viewmodelem).

RxJavy też można użyć w charakterze takiego kleju, a databindingiem ją zastąpić. Nie eliminuje to jednak możliwości korzystania z RxJavy, ponieważ wciąż można zastosować ją do sprzęgnięcia ze sobą głębiej położonych komponentów systemu (np. serwisów z modelami).

W temacie czystej architektury na Androida polecam cykl Dorfmanna (autora biblioteki Mosby do MVP), którego najnowszym pomysłem jest coś, co nazwał MVI.

Ma pięć części - to pierwsza:
http://hannesdorfmann.com/android/mosby3-mvi-1

Mnie jego podejście i argumenty bardzo przekonują. Na dodatek jest przejrzyście i niespiesznie wyjaśnione. W przykładach zakłada on stosowanie RxJavy, aczkolwiek co do zasady nie jest to bezwzględnie konieczne.

1 użytkowników online, w tym zalogowanych: 0, gości: 1