Dlaczego warto używać Fluttera
Wprowadzenie
Znalazłem stary czujnik podczerwieni, przekaźnik i kilka rezystorów. Poświęciłem kilka godzin (sporo przy tym przeklinając), aby w końcu móc włączyć mój wzmacniacz pilotem od telewizora. Dodanie wsparcia dla większej ilości klawiszy od tego momentu było już proste (klawisze Vol+ i Vol- zmieniają głośność zarówno na moim wzmacniaczu jak i telewizorze, ale mi to nie przeszkadza).
Muzyka! To jest rzecz, której brakowało w mojej konfiguracji. Postanowiłem więc zainstalować serwer Mopidy. Posiada on wsparcie dla różnych formatów muzycznych, Spotify, a nawet youtube. Zapewnia podstawową komunikację przez web sockety używając protokołu json rpc 2. Istnieją dziesiątki klientów dla tego serwera, część z nich jest webowa, część działa w terminalu, są też klienci dla telefonów komórkowych. Żaden z nich oczywiście nie obsługuje włączania i wyłączania mojego własnego zestawu stereo.
Oto mój wzmacniacz, piękny, czyż nie?
Przepraszam za ten długi wstęp. Chciałem tylko uzasadnić mój pomysł napisania klienta Mopidy we Flutterze. Był to mój pierwszy kontakt z tym frameworkiem i chcę się podzielić kilkoma spostrzeżeniami na jego temat jako programista C++/Qt.
Powody, dla których warto pokochać Flutter
Ustawianie środowiska
W porównaniu do Qt, ustawienie całego środowiska pracy dla Androida było super proste. Wiele problemów można było po prostu rozwiązać za pomocą narzędzia wiersza poleceń o nazwie flutter doctor. Wystarczy uruchomić je w terminalu, a ono pobierze brakujące rzeczy, poprosi o potwierdzenie licencji i pokaże czytelne podsumowanie. Dużo prostsze niż ręczne instalowanie i ustawianie ścieżek w Qt Creator dla JRE, NDK, SDK... Oto przykład podsumowania flutter doctor:
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel master, v1.15.4-pre.144, on Microsoft Windows [Version 10.0.17763.1039], locale pl-PL)
[√] Android toolchain - develop for Android devices (Android SDK version 29.0.3)
[√] Chrome - develop for the web
[!] Android Studio (version 3.5)
X Flutter plugin not installed; this adds Flutter specific functionality.
X Dart plugin not installed; this adds Dart specific functionality.
[√] Connected device (2 available)
! Doctor found issues in 1 category.
Zależność i menedżer pakietów
Nienawidzę tego, że C++ nie ma standardowego menedżera pakietów. Conan zyskuje na popularności i C++20 będzie miał moduły, ale to wciąż nic w porównaniu do npm z node.js czy nawet pythons pip. Flutter ma coś podobnego do Cargo z Rust i to jest świetne. Korzystanie z zewnętrznych bibliotek jest bardzo proste, wystarczy dodać wpis do pliku pubspec.yaml. Sekcja Dependencies wygląda tak:
dependencies:
flutter:
sdk: flutter
# additional libraries with minimal versions
cupertino_icons: ^0.1.2
loading: ^1.0.2
Jeśli chcę skorzystać np. z biblioteki json_rpc_2 w wersji 2.1.0 lub wyższej, wystarczy, że dodam tę linijkę w sekcji zależności i zapiszę plik.
json_rpc_2: ^2.1.0
Biblioteka zostanie pobrana i będzie gotowa do użycia w ciągu sekundy!
Hot reload
Ta funkcja jest niesamowita! Budowanie i wdrażanie aplikacji może zająć dużo czasu. Staje się to irytujące, gdy jedyne co chcemy sprawdzić, to prosta, drobna poprawka. We Flutterze istnieje funkcja 'Hot reload', która oznacza superszybkie ładowanie przy każdym zapisie (jeśli kod da się skompilować). Ustawianie widżetów na stronie jest niemal tak wygodne, jak pisanie dokumentu Markdown z podglądem. To naprawdę działa!
Deklaratywne podejście
Flutter tworząc widok, odzwierciedla stan aplikacji. Jest to duża zmiana modelu, ale dzięki temu łatwiej jest utrzymywać stany UI. Zapomnij o np. ustawianiu koloru czcionki na czerwony gdy coś się dzieje, a następnie usuwaniu przycisków przy innym zdarzeniu. Tutaj definiujesz jak ma wyglądać UI dla danego stanu. Jeśli coś się zmieni, UI przebuduje się od zera. Wszystko odbywa się automatycznie, pod maską. Jest to proste i eleganckie rozwiązanie, ale tutaj pojawia się moja największa obawa - czy nie spowoduje to problemów z wydajnością dla skomplikowanych UI? Prawdopodobnie wszystko zależy od tego jak UI jest tworzone i projektowane.
Responsywność
Kod async jest całkiem naturalny w języku programowania Dart. Używanie futures jest tak proste jak używanie fetch w javascript. Oto przykład: załóżmy, że mamy asynchroniczny strumień, który od czasu do czasu produkuje jakieś dane. Możemy bezpośrednio zbudować widżet na podstawie otrzymanych informacji:
body: StreamBuilder<BrowsingDTO>(
stream: widget._mopidy.browsingStream(),
builder: (BuildContext context, AsyncSnapshot<BrowsingDTO> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.none: return Text("No connection");
case ConnectionState.waiting: return Text("Waiting");
case ConnectionState.active: return _createBrowsingList(snapshot.data);
case ConnectionState.done: return _createBrowsingList(snapshot.data);
}
return null;
},
)
Widget _createBrowsingList(BrowsingDTO dto, mopidy) => ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: dto.results.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
child: FlatButton(
onPressed: () {
switch (dto.results[index].type) {
case "directory":
mopidy.browse(uri: dto.results[index].uri);
break;
case "track":
mopidy.add(uris: [dto.results[index].uri]);
mopidy.play();
break;
case "playlist":
mopidy.addPlaylist(dto.results[index].uri);
mopidy.play();
break;
}
},
child: Text(dto.results[index].name),
));
});});
Małe wyjaśnienie - ten mały blok kodu dołącza się do strumienia, który dostarcza dane na podstawie danych przeglądania przez użytkownika (aktualny folder, itp.). Za każdym razem, gdy pojawia się nowy obiekt BrowsingDTO, widżety są przebudowywane, a gdy użytkownik kliknie w wygenerowany płaski przycisk, tworzone jest nowe żądanie i cykl trwa dalej. Wygląda fajnie, prawda?
Wzorzec projektowy Bloc
Chociaż nie użyłem go w moim kliencie Mopidy (jeszcze), zrobiłem z nim kilka eksperymentów i jest bardzo obiecujący. Według mnie jest on bardziej testowalny niż standardowe podejście MVC i znacznie lepiej pasuje do deklaratywnego sposobu działania Flutter. To są moje pierwsze spostrzeżenia, nie znalazłem żadnych wad tego wzorca (choć wierzę, że jakieś są).
Visual Studio Code
Mogę się założyć, że ten punkt może być zaskakujący i kontrowersyjny dla niektórych osób. Osobiście uwielbiam VS Code za jego solidność i rozszerzalność. Nie wszystkie środowiska idą w parze z tym edytorem. Na szczęście Flutter nie jest jednym z nich. Skonfigurowanie go do pracy z Flutterem jest płynne i nie doświadczyłem żadnych problemów podczas pracy w nim.
Szybki development
To będzie podsumowanie. Wszystkie te małe rzeczy powyżej prowadzą do niego. Potrzeba mniej czasu na stworzenie w pełni funkcjonalnych aplikacji. Nie miałem większych problemów ze znalezieniem rozwiązania, gdy utknąłem, a to było moje zmartwienie, ponieważ Flutter jest dość nowy i społeczność jest stosunkowo mała.
Za co nie lubię Fluttera
Duża zmiana modelu
Trzeba zmienić sposób myślenia o budowaniu aplikacji. Deklaratywne podejście jest zupełnie inne niż standardowe, iteracyjne. Potrzeba czasu, aby się do niego przyzwyczaić. Pomocne w tym przypadku jest doświadczenie w tworzeniu gier.
Problemy z debugowaniem i hot reload
Czasami debugger nie został uruchomiony we właściwym miejscu. Stos wywołań jest czasami niechlujny i przez to bezużyteczny. Hot reload jest świetną funkcją, ale czasami jest ona myląca. Znalezienie błędu w moim kodzie zajęło mi trochę czasu. Nie było żadnego - wszystko co musiałem zrobić to zrestartować całą aplikację.
Brzydki kod
Język Dart sam w sobie jest w porządku, ale budowanie widoków za jego pomocą może prowadzić do nieczytelnego, niechlujnego kodu z kaskadowymi widżetami. Nadal nie przyzwyczaiłem się do tego.
Pułapki kodowania
Flutter i Dart dają dużo swobody. Są bardzo elastyczne, ale kuszące jest tworzenie dużych klas all-in-one. Drugim problemem, który przychodzi mi do głowy jest to, że łatwo jest mieszać modele z logiką widoku. Zdarzyło mi się to kilka razy.
Zewnętrzne biblioteki
Większość ważnych rzeczy jest dostępna, ale to nic w porównaniu z dojrzałymi językami z ogromnymi bazami kodów. To się zmienia. Flutter się rozwija!
Brak natywnych buildów
Uruchamianie aplikacji na Windows czy Linux nie jest jeszcze możliwe, uruchomienie w przeglądarce tak, ale wciąż na wczesnym etapie. Wiem, wiem - to środowisko mobilne.
Plusy | Minusy |
---|---|
Dobra dokumentacja | Zła dokumentacja ;) |
Łatwa konfiguracja | Łatwa konfiguracja |
VS Code | Drobne problemy z debugowaniem |
Hot reload | Problemy z Hot reload |
Łatwość reagowania | Brzydki kod UI |
Proste tworzenie widoków | Łatwo o tworzenie dużych klas |
Szybki development | Łatwo pomieszać model z logiką widoku |
Wzorzec Bloc | Brak natywnych buildów |
Proste testy UI | Mała baza kodów |
Dogodne zależności |
Wnioski
Moje ogólne przemyślenia są zdecydowanie pozytywne. Warto spróbować! Ten projekt polegał głównie na nauce i eksperymentowaniu. Świetnie się przy tym bawiłem i chyba nie zawsze trzymałem się dobrych praktyk. Najlepsze jest to, że w rezultacie otrzymałem działającą i funkcjonalną aplikację w stosunkowo krótkim czasie.
Ostatnia rzecz - zrzuty ekranu, które zrobiłem telefonem: