UseCase jest jednym z komponentów znajdujących się w koncepcji czystej architektury. Zawiera się w warstwie domeny, czyli tej opisującej logikę biznesową aplikacji. Powinien odzwierciedlać jakiś jeden konkretny przypadek użycia danego fragmentu kodu. Jeśli aplikacja została zaprojektowana oraz zaimplementowana zgodnie z zasadami czystej architektury, to po nazwie samych klas typu UseCase powinno się dać stwierdzić jakie posiada funkcjonalności.
W tym artykule dowiesz się jak zaimplementować generyczny UseCase, żeby tworzenie każdych kolejnych klas tego typu ograniczyć do minimum kodu. Poniższy wycinek kodu obrazuje jaki efekt chcemy osiągnąć.
Generyczny UseCase krok po kroku
UseCase ma bardzo proste zadanie. W większości przypadków będzie po prostu wykonywał fragment kodu. Stąd najczęściej metody znajdujące się w takiej klasie będą nosiły nazwę run, execute czy invoke. W Kotlinie zamiast tworzyć ręcznie jedną z takich funkcji można nadpisać operator invoke
. Dzięki temu wywołanie takiego UseCase’a będzie bardziej zwięzłe.
Idąc dalej. UseCase po wykonaniu zdefiniowanego kawałka kodu powinien przekazać w jakiś sposób rezultat do klasy, która go wywołała. Najczęściej będzie to ViewModel. Dodatkowo będą przypadki, gdzie do poprawnego działania będą potrzebne parametry wejściowe. Na te potrzeby dodamy dwa argumenty do metody invoke. Pierwszy z nich będzie reprezentował dane wejściowe. Drugim natomiast będzie lambda zwracająca wynik konkretnej operacji. Dodajmy też metodę abstrakcyjną definiującą akcję, jaka ma się wykonać w ramach konkretnego UseCase’a.
W większości aplikacji mobilnych logika biznesowa schowana jest w zewnętrznym serwerze, żeby była spójna między różnymi platformami. Stąd najczęściej UseCase będzie prosił o te dane wywołując odpowiednią metodę z repozytorium. Dobrą praktyką związaną z wysyłaniem zapytania do zewnętrznego serwera czy lokalnej bazy danych jest wykonywanie tych operacji po za głównym wątkiem aplikacji. Można to w prosty sposób osiągnąć korzystając z Kotlin Coroutines.
Dodajmy kolejny argument do metody invoke – CoroutineScope
na której będziemy uruchamiać korutynę korzystając z metody launch
. Jeśli UseCase jest wywoływany z poziomu ViewModelu, to najlepiej w to miejsce przekazać viewModelScope. Należy pamiętać, że ten dedykowany do ViewModelu scope operuje na głównym wątku aplikacji. Trzeba zadbać o to, żeby operacje zdefiniowane w obrębie klasy UseCase były wykonywane w tle i nie blokowały UI naszej aplikacji. Do tego celu skorzystamy z metody withContext
z parametrem Dispatchers.IO
, który jest przeznaczony do operacji wejścia-wyjścia.
Dobrą praktyką jest ograniczanie twardych powiązań w kodzie aplikacji, dlatego wyciągniemy sobie parametr funkcji withContext do argumentu funkcji invoke. Ten prosty zabieg pozwoli na sterowanie Dispatcherem dla akcji zdefiniowanej w UseCase. W większości przypadków będzie to Dispatchers.IO, więc dodamy sobie to jako argument domyślny i tym samym nie będzie konieczności pisania tego przy każdym wywołaniu naszego UseCase’a.
W zasadzie w takiej postaci nasz UseCase będzie działał. Problem tkwi w tym, że jego działanie zakłada tylko pozytywny scenariusz wykonania danej operacji. Jak wiadomo błędy się zdarzają i trzeba umieć je obsługiwać. Dlatego dla bezpiecznego wykonywania metod w obrębie UseCase i uwzględnienia też niepowodzenia takiej operacji można skorzystać z funkcji runCatching
. Działa ona analogicznie do javowego bloku try catch. Różnica polega na tym, że funkcja ta opakowuje wynik wykonania konkretnej metody w typ Result
. Jeśli wszystko przebiegnie pomyślnie, to wywołując metodę onSuccess
na obiekcie result można dostać się do danych. W przeciwnym wypadku wykona się metoda onFailure
i zostanie zwrócony Throwable
. Ostateczna postać naszego generycznego UseCase’a wygląda następująco.
Przykład implementacji
Teraz każdy nowy UseCase możemy stworzyć po prostu definiując dla niego akcję. Poniżej znajdziesz dwa przykłady – jeden z parametrem wejściowym jako String i drugi bez parametru (a w zasadzie będąc bardziej precyzyjnym z parametrem Unit).
Przykład użycia
Tak przygotowany UseCase można teraz wywołać z poziomu ViewModelu. Zwróć uwagę na metodę getBooks
. W jej ciele znajdziesz wywołanie UseCase’a oraz obsługę zwracanych danych bazującą na typie Result.
Jak widzisz każdorazowa implementacja nowego UseCase’a, bazująca na generycznym rozwiązaniu zaproponowanym w tym poście, sprowadza się do nadpisania jednej linijki kodu. Samo korzystanie również jest proste i intuicyjne dzięki opakowaniu wyniku w typ Result. Mam nadzieję, że to rozwiązanie przypadło Ci do gustu.
Jeśli chcesz więcej tego typu treści to jeszcze do czwartku do godziny 21:00 możesz dołączyć do programu “Architektura MVVM & Clean” gdzie kompleksowo będziemy zgłębiać tematykę czystego kodu i tworzyć skalowalną i testowalną aplikację mobilną. Kliknij tutaj po więcej informacji.