Korzystanie z narzędzia Google Flutter do prawdziwie wieloplatformowego programowania mobilnego
Opublikowany: 2022-03-10Flutter to wieloplatformowa platforma programistyczna typu open source firmy Google. Umożliwia budowanie wydajnych, pięknych aplikacji dla systemów iOS i Android z jednej bazy kodu. Jest to również platforma programistyczna dla nadchodzącego systemu operacyjnego Google Fuchsia. Ponadto jest zaprojektowany w taki sposób, aby można go było przenieść na inne platformy za pomocą niestandardowych elementów osadzonych silnika Flutter.
Dlaczego powstał Flutter i dlaczego warto go używać
Wieloplatformowe zestawy narzędzi historycznie przyjmowały jedno z dwóch podejść:
- Zawijają widok sieciowy w natywną aplikację i budują aplikację tak, jakby była stroną internetową.
- Otaczają natywne kontrolki platformy i zapewniają nad nimi pewną abstrakcję międzyplatformową.
Flutter stosuje inne podejście, próbując ulepszyć programowanie mobilne. Zapewnia programistom aplikacji framework i silnik z przenośnym środowiskiem wykonawczym do hostowania aplikacji. Framework opiera się na bibliotece graficznej Skia, zapewniając widżety, które są faktycznie renderowane, a nie tylko otoki na natywnych kontrolkach.
Takie podejście zapewnia elastyczność w budowaniu aplikacji wieloplatformowej w całkowicie niestandardowy sposób, jak zapewnia opcja web wrapper, ale jednocześnie oferuje płynną wydajność. Tymczasem bogata biblioteka widżetów dostarczana wraz z Flutter, wraz z bogactwem widżetów typu open source, sprawia, że jest to platforma o bardzo bogatej funkcjonalności. Mówiąc prościej, Flutter jest najbliższą rzeczą, jaką mieli programiści mobilni, jeśli chodzi o rozwój międzyplatformowy, z niewielkim lub żadnym kompromisem.
Strzałka
Aplikacje Fluttera są napisane w Dart, który jest językiem programowania pierwotnie opracowanym przez Google. Dart to język zorientowany obiektowo, który obsługuje zarówno kompilację z wyprzedzeniem, jak i just-in-time, dzięki czemu dobrze nadaje się do tworzenia natywnych aplikacji, zapewniając jednocześnie wydajny przepływ pracy przy programowaniu dzięki przeładowywaniu na gorąco Fluttera. Flutter niedawno przeniósł się również na Dart w wersji 2.0.
Język Dart oferuje wiele funkcji obserwowanych w innych językach, w tym zbieranie śmieci, asynchroniczne czekanie, silne typowanie, generyczne, a także bogatą bibliotekę standardową.
Dart oferuje skrzyżowanie funkcji, które powinny być znane programistom wywodzącym się z różnych języków, takich jak C#, JavaScript, F#, Swift i Java. Dodatkowo Dart może skompilować do JavaScript. W połączeniu z Flutter umożliwia to udostępnianie kodu na platformach internetowych i mobilnych.
Historyczna oś czasu wydarzeń
- Kwiecień 2015
Flutter (pierwotnie o nazwie kodowej Sky) pokazany na Dart Developer Summit - listopad 2015
Sky przemianowane na Flutter - Luty 2018
Flutter beta 1 ogłoszony na Mobile World Congress 2018 - Kwiecień 2018
Flutter beta 2 zapowiedziany - maj 2018
Flutter beta 3 ogłoszony na Google I/O. Google ogłasza, że Flutter jest gotowy na aplikacje produkcyjne
Porównanie z innymi platformami programistycznymi
Natywny dla Apple/Android
Aplikacje natywne zapewniają najmniejsze problemy z przyjmowaniem nowych funkcji. Mają one tendencję do dostosowywania doświadczeń użytkowników do danej platformy, ponieważ aplikacje są budowane przy użyciu kontroli samych dostawców platform (Apple lub Google) i często postępują zgodnie z wytycznymi projektowymi określonymi przez tych dostawców. W większości przypadków aplikacje natywne będą działać lepiej niż te zbudowane z ofertami międzyplatformowymi, chociaż w wielu przypadkach różnica może być nieistotna, w zależności od podstawowej technologii wieloplatformowej.
Jedną z wielkich zalet natywnych aplikacji jest to, że mogą natychmiast zastosować zupełnie nowe technologie, które Apple i Google tworzą w wersji beta, bez konieczności czekania na integrację z innymi firmami. Główną wadą tworzenia aplikacji natywnych jest brak możliwości ponownego wykorzystania kodu na różnych platformach, co może sprawić, że programowanie będzie kosztowne w przypadku systemów iOS i Android.
Reaguj natywnie
React Native umożliwia budowanie natywnych aplikacji przy użyciu JavaScript. Rzeczywiste kontrolki, z których korzysta aplikacja, to kontrolki platformy natywnej, dzięki czemu użytkownik końcowy czuje się jak aplikacja natywna. W przypadku aplikacji, które wymagają dostosowania poza tym, co zapewnia abstrakcja React Native, programowanie natywne może być nadal potrzebne. W przypadkach, gdy wymagana ilość dostosowania jest znaczna, korzyści płynące z pracy w warstwie abstrakcji React Native zmniejszają się do punktu, w którym w niektórych przypadkach tworzenie aplikacji natywnie byłoby bardziej korzystne.
Xamarin
Podczas omawiania platformy Xamarin istnieją dwa różne podejścia, które należy ocenić. W przypadku najbardziej wieloplatformowego podejścia istnieje platforma Xamarin.Forms. Chociaż technologia ta bardzo różni się od React Native, koncepcyjnie oferuje podobne podejście, ponieważ abstrahuje natywne kontrolki. Podobnie ma podobne wady w zakresie dostosowywania.
Po drugie, jest to, co wielu określa jako Xamarin-classic. To podejście wykorzystuje produkty Xamarin dla systemów iOS i Android niezależnie do tworzenia funkcji specyficznych dla platformy, tak jak w przypadku bezpośredniego korzystania z natywnych systemów Apple/Android, tylko przy użyciu C# lub F# w przypadku platformy Xamarin. Zaletą platformy Xamarin jest to, że kod niezwiązany z platformą, takie jak sieć, dostęp do danych, usługi internetowe itp., mogą być udostępniane.
W przeciwieństwie do tych alternatyw, Flutter stara się zapewnić programistom bardziej kompletne rozwiązanie wieloplatformowe, z ponownym wykorzystaniem kodu, wydajnymi, płynnymi interfejsami użytkownika i doskonałymi narzędziami.
Przegląd aplikacji Flutter
Tworzenie aplikacji
Po zainstalowaniu Fluttera utworzenie aplikacji za pomocą Fluttera jest tak proste, jak otwarcie wiersza poleceń i wpisanie flutter create [app_name]
, wybranie polecenia „Flutter: Nowy projekt” w VS Code lub wybranie „Rozpocznij nowy projekt Flutter” w systemie Android Studio lub IntelliJ.
Niezależnie od tego, czy zdecydujesz się użyć IDE, czy wiersza poleceń wraz z preferowanym edytorem, nowy szablon aplikacji Flutter stanowi dobry punkt wyjścia dla aplikacji.
Aplikacja wprowadza pakiet flutter
/ material.dart
, który oferuje podstawowe rusztowanie dla aplikacji, takie jak pasek tytułu, ikony materiałów i motywy. Konfiguruje również widżet stanowy, aby zademonstrować, jak zaktualizować interfejs użytkownika, gdy zmieni się stan aplikacji.
Opcje narzędziowe
Flutter oferuje niesamowitą elastyczność w zakresie oprzyrządowania. Aplikacje można równie łatwo tworzyć z wiersza poleceń wraz z dowolnym edytorem, jak i z obsługiwanego środowiska IDE, takiego jak VS Code, Android Studio lub IntelliJ. Podejście, jakie należy przyjąć, zależy w dużej mierze od preferencji programisty.
Android Studio oferuje większość funkcji, takich jak Flutter Inspector do analizy widżetów uruchomionej aplikacji i monitorowania wydajności aplikacji. Oferuje również kilka refaktoryzacji, które są wygodne podczas tworzenia hierarchii widżetów.
VS Code oferuje lżejsze środowisko programistyczne, ponieważ zwykle uruchamia się szybciej niż Android Studio/IntelliJ. Każde IDE oferuje wbudowane pomocniki edycji, takie jak uzupełnianie kodu, umożliwiające eksplorację różnych interfejsów API, a także dobrą obsługę debugowania.
Wiersz poleceń jest również dobrze obsługiwany przez polecenie flutter
, co ułatwia tworzenie, aktualizowanie i uruchamianie aplikacji bez żadnych innych zależności od narzędzi poza edytorem.
Gorące przeładowanie
Niezależnie od oprzyrządowania Flutter zapewnia doskonałe wsparcie dla ponownego ładowania aplikacji na gorąco. Pozwala to w wielu przypadkach na modyfikację działającej aplikacji, zachowując stan, bez konieczności zatrzymywania aplikacji, przebudowywania i ponownego wdrażania.
Przeładowanie na gorąco znacznie zwiększa wydajność opracowywania, umożliwiając szybszą iterację. To naprawdę sprawia, że praca z platformą jest przyjemnością.
Testowanie
Flutter zawiera narzędzie WidgetTester
do interakcji z widżetami z testu. Nowy szablon aplikacji zawiera przykładowy test, który pokazuje, jak go używać podczas tworzenia testu, jak pokazano poniżej:
// Test included with the new Flutter application template import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:myapp/main.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(new MyApp()); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget); expect(find.text('1'), findsNothing); // Tap the '+' icon and trigger a frame. await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Verify that our counter has incremented. expect(find.text('0'), findsNothing); expect(find.text('1'), findsOneWidget); }); }
Korzystanie z pakietów i wtyczek
Flutter dopiero się zaczyna, ale istnieje już bogaty ekosystem programistów: mnóstwo pakietów i wtyczek jest już dostępnych dla programistów.
Aby dodać pakiet lub wtyczkę, po prostu dołącz zależność w pliku pubspec.yaml w katalogu głównym aplikacji. Następnie uruchom flutter packages get
Flutter z wiersza poleceń lub przez IDE, a narzędzia Flutter przyniosą wszystkie wymagane zależności.
Na przykład, aby użyć popularnej wtyczki selektora obrazów dla Fluttera, plik pubspec.yaml musi tylko wymienić go jako zależność, jak na przykład:
dependencies: image_picker: "^0.4.1"
Następnie uruchamiając flutter packages get
wszystko, czego potrzebujesz, aby z niego skorzystać, po czym można go zaimportować i użyć w Dart:
import 'package:image_picker/image_picker.dart';
Widżety
Wszystko we Flutterze to widget. Obejmuje to elementy interfejsu użytkownika, takie jak ListView
, TextBox
i Image
, a także inne części struktury, w tym układ, animację, rozpoznawanie gestów i motywy, aby wymienić tylko kilka.
Dzięki temu, że wszystko jest widżetem, cała aplikacja, która nawiasem mówiąc jest również widżetem, może być reprezentowana w hierarchii widżetów. Posiadanie architektury, w której wszystko jest widżetem, jasno pokazuje, skąd pochodzą określone atrybuty i zachowania zastosowane do części aplikacji. Różni się to od większości innych struktur aplikacji, które niespójnie kojarzą właściwości i zachowanie, czasami dołączając je z innych składników w hierarchii, a innym razem w samej kontrolce.
Przykład prostego widżetu interfejsu użytkownika
Punktem wejścia do aplikacji Flutter jest funkcja główna. Aby umieścić widżet dla elementu interfejsu użytkownika na ekranie, w funkcji main()
wywołaj runApp()
i przekaż widżet, który będzie służył jako główny element hierarchii widżetów.
import 'package:flutter/material.dart'; void main() { runApp( Container(color: Colors.lightBlue) ); }
Powoduje to pojawienie się jasnoniebieskiego widżetu Container
, który wypełnia ekran:
Bezstanowe a stanowe widżety
Widgety występują w dwóch wersjach: bezpaństwowej i stanowej. Widgety bezstanowe nie zmieniają swojej zawartości po utworzeniu i zainicjowaniu, podczas gdy widżety stanowe zachowują pewien stan, który może ulec zmianie podczas działania aplikacji, np. w odpowiedzi na interakcję użytkownika.
W tym przykładzie widżet FlatButton
i widżet Text
są rysowane na ekranie. Widżet Text
zaczyna się od domyślnego String
dla jego stanu. Naciśnięcie przycisku powoduje zmianę stanu, która spowoduje aktualizację widżetu Text
, wyświetlając nowy String
.
Aby zahermetyzować widżet, utwórz klasę, która pochodzi od StatelessWidget
lub StatefulWidget
. Na przykład jasnoniebieski Container
można zapisać w następujący sposób:
class MyWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Container(color: Colors.lightBlue); } }
Flutter wywoła metodę budowania widżetu po wstawieniu go do drzewa widżetów, aby można było renderować tę część interfejsu użytkownika.
W przypadku widżetu stanowego pochodzę od StatefulWidget
:
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
class MyStatefulWidget extends StatefulWidget { MyStatefulWidget(); @override State createState() { return MyWidgetState(); } }
Widgety stanowe zwracają klasę State
, która jest odpowiedzialna za budowanie drzewa widżetów dla danego stanu. Gdy stan się zmieni, powiązana część drzewa widżetów jest odbudowywana.
W poniższym kodzie klasa State
aktualizuje String
po kliknięciu przycisku:
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
class MyWidgetState extends State { String text = "some text"; @override Widget build(BuildContext context) { return Container( color: Colors.lightBlue, child: Padding( padding: const EdgeInsets.all(50.0), child: Directionality( textDirection: TextDirection.ltr, child: Column( children: [ FlatButton( child: Text('Set State'), onPressed: () { setState(() { text = "some new text"; }); }, ), Text( text, style: TextStyle(fontSize: 20.0)), ], ) ) ) ); } }
Stan jest aktualizowany w funkcji, która jest przekazywana do setState()
. Gdy wywoływana jest setState()
, ta funkcja może ustawić dowolny stan wewnętrzny, taki jak łańcuch w tym przykładzie. Następnie zostanie wywołana metoda build
, aktualizująca drzewo stanowego widżetu.
Zwróć także uwagę na użycie widżetu Directionality
do ustawienia kierunku tekstu dla dowolnych widżetów w poddrzewie, które tego wymagają, takich jak widżety Text
. Przykłady tutaj są budowaniem kodu od zera, więc Directionality
jest potrzebna gdzieś wyżej w hierarchii widżetów. Jednak użycie widżetu MaterialApp
, takiego jak domyślny szablon aplikacji, niejawnie konfiguruje kierunek tekstu.
Układ
Funkcja runApp
domyślnie wypełnia widżet tak, aby wypełniał ekran. Aby kontrolować układ widżetów, Flutter oferuje różne widżety układu. Istnieją widżety do wykonywania układów, które wyrównują widżety podrzędne w pionie lub poziomie, rozszerzają widżety w celu wypełnienia określonej przestrzeni, ograniczają widżety do określonego obszaru, centrują je na ekranie i umożliwiają nakładanie się widżetów.
Dwa powszechnie używane widżety to Row
i Column
. Te widżety wykonują układy, aby wyświetlać swoje widżety podrzędne poziomo (wiersz) lub pionowo (kolumna).
Korzystanie z tych widżetów układu polega po prostu na owinięciu ich wokół listy widżetów podrzędnych. mainAxisAlignment
kontroluje położenie widżetów wzdłuż osi układu, wyśrodkowane, na początku, na końcu lub z różnymi opcjami odstępów.
Poniższy kod pokazuje, jak wyrównać kilka widżetów podrzędnych w Row
lub Column
:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return Row( //change to Column for vertical layout mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.android, size: 30.0), Icon(Icons.pets, size: 10.0), Icon(Icons.stars, size: 75.0), Icon(Icons.rowing, size: 25.0), ], ); } }
Odpowiadanie na dotyk
Interakcja dotykowa jest obsługiwana za pomocą gestów, które są zawarte w klasie GestureDetector
. Ponieważ jest to również widżet, dodanie rozpoznawania gestów jest tak proste, jak zawijanie widżetów podrzędnych w GestureDetector
.
Na przykład, aby dodać obsługę dotykową do Icon
, ustaw ją jako element podrzędny GestureDetector
i ustaw obsługę detektora dla żądanych gestów do przechwycenia.
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( onTap: () => print('you tapped the star'), onDoubleTap: () => print('you double tapped the star'), onLongPress: () => print('you long pressed the star'), child: Icon(Icons.stars, size: 200.0), ); } }
W takim przypadku po dotknięciu, dwukrotnym dotknięciu lub przytrzymaniu ikony zostanie wydrukowany powiązany tekst:
To hot reload your app on the fly, press "r". To restart the app entirely, press "R". An Observatory debugger and profiler on iPhone X is available at: https://127.0.0.1:8100/ For a more detailed help message, press "h". To quit, press "q". flutter: you tapped the star flutter: you double tapped the star flutter: you long pressed the star
Oprócz prostych gestów stuknięcia istnieje wiele narzędzi do rozpoznawania wszystkiego, od panoramowania i skalowania po przeciąganie. Ułatwiają one tworzenie aplikacji interaktywnych.
Obraz
Flutter oferuje również różnorodne widżety do malowania, w tym takie, które modyfikują krycie, ustawiają ścieżki przycinania i nakładają dekoracje. Obsługuje nawet niestandardowe malowanie za pomocą widżetu CustomPaint
i powiązanych klas CustomPainter
i Canvas
.
Jednym z przykładów widżetu do malowania jest DecoratedBox
, który może malować BoxDecoration
na ekranie. Poniższy przykład pokazuje, jak tego użyć, aby wypełnić ekran wypełnieniem gradientowym:
class MyStatelessWidget extends StatelessWidget { @override Widget build(BuildContext context) { return new DecoratedBox( child: Icon(Icons.stars, size: 200.0), decoration: new BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } }
Animacja
Flutter zawiera klasę AnimationController
, która kontroluje odtwarzanie animacji w czasie, w tym uruchamianie i zatrzymywanie animacji, a także zmienianie wartości animacji. Dodatkowo istnieje widżet AnimatedBuilder
, który umożliwia tworzenie animacji w połączeniu z AnimationController
.
Każdy widżet, taki jak pokazana wcześniej ozdobiona gwiazda, może mieć animowane właściwości. Na przykład refaktoryzacja kodu do StatefulWidget
, ponieważ animowanie jest zmianą stanu, a przekazanie AnimationController
do klasy State
umożliwia użycie animowanej wartości podczas kompilowania widżetu.
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
class StarWidget extends StatefulWidget { @override State createState() { return StarState(); } } class StarState extends State with SingleTickerProviderStateMixin { AnimationController _ac; final double _starSize = 300.0; @override void initState() { super.initState(); _ac = new AnimationController( duration: Duration(milliseconds: 750), vsync: this, ); _ac.forward(); } @override Widget build(BuildContext context) { return new AnimatedBuilder( animation: _ac, builder: (BuildContext context, Widget child) { return DecoratedBox( child: Icon(Icons.stars, size: _ac.value * _starSize), decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Colors.red, Colors.blue, Colors.green], tileMode: TileMode.mirror ), ), ); } ); } }
W takim przypadku wartość służy do zmiany rozmiaru widżetu. Funkcja builder
jest wywoływana za każdym razem, gdy zmienia się animowana wartość, powodując zmianę rozmiaru gwiazdy o ponad 750 ms, tworząc w efekcie skalę:
Korzystanie z funkcji natywnych
Kanały platformy
Aby zapewnić dostęp do natywnych interfejsów API platformy na Androida i iOS, aplikacja Flutter może korzystać z kanałów platformy. Umożliwiają one kodowi Flutter Dart wysyłanie wiadomości do hostującej aplikacji iOS lub Android. Wiele dostępnych wtyczek o otwartym kodzie źródłowym jest tworzonych za pomocą przesyłania wiadomości za pośrednictwem kanałów platformy. Aby dowiedzieć się, jak pracować z kanałami platformy, dokumentacja Flutter zawiera dobry dokument, który demonstruje dostęp do natywnych interfejsów API baterii.
Wniosek
Nawet w wersji beta Flutter oferuje świetne rozwiązanie do tworzenia aplikacji wieloplatformowych. Dzięki doskonałemu oprzyrządowaniu i przeładowaniu na gorąco, zapewnia bardzo przyjemne wrażenia z rozwoju. Bogactwo pakietów open-source i doskonała dokumentacja ułatwiają rozpoczęcie pracy. W przyszłości programiści Flutter będą mogli celować w Fuchsia oprócz iOS i Androida. Biorąc pod uwagę rozszerzalność architektury silnika, nie zdziwiłby mnie widok Fluttera lądującego na wielu innych platformach. Wraz z rosnącą społecznością jest to świetny czas, aby wskoczyć do akcji.
Następne kroki
- Zainstaluj Flutter
- Wycieczka językowa w darta
- Flutter CodeLabs
- Kurs Flutter Udacity
- Kod źródłowy artykułu