3. Flutter

3.1. Einführung

Allgemein

Flutter ist ein Open-Source-Framework von Google, mit dem plattformübergreifende Apps erstellt werden können. Es wurde erstmals im Jahr 2017 veröffentlicht und hat sich seitdem schnell zu einem der beliebtesten Frameworks für die Entwicklung von mobilen Anwendungen entwickelt.

Ein Hauptmerkmal von Flutter ist die Verwendung von Widgets zur Erstellung von Benutzeroberflächen. Widgets sind vorgefertigte Bausteine, die verwendet werden können, um eine App-Oberfläche zu gestalten. Es gibt eine breite Palette von Widgets, darunter Textfelder, Schaltflächen, Listen und mehr. Flutter bietet auch die Möglichkeit, eigene Widgets zu erstellen, um die Funktionalität der App zu erweitern.

Es ist plattformübergreifend, was bedeutet, dass mit einer Codebasis sowohl Android- als auch iOS-Apps erstellt werden können. Es gibt auch Möglichkeiten, Desktop- und Web-Apps mit Flutter zu erstellen.

Insgesamt bietet Flutter eine schnelle, effiziente und einfach zu erlernende Methode zur Erstellung von plattformübergreifenden Anwendungen. Es ist eine gute Wahl für Entwickler, die Apps für mehrere Plattformen erstellen möchten und gleichzeitig eine einheitliche Benutzeroberfläche und eine effiziente Entwicklungsworkflow wünschen.

Unterschied zu nativer Entwicklung

  1. Code-Reuse: Mit Flutter können Entwickler eine einzige Codebasis verwenden, um Apps für Android und iOS zu erstellen. Bei nativer Entwicklung muss der Code jedoch in der Regel separat für jede Plattform geschrieben werden, was mehr Zeit und Aufwand erfordert.

  2. UI-Entwicklung: Flutter bietet eine vereinfachte Methode zur Erstellung von Benutzeroberflächen mit Widgets und einer deklarativen Programmierung, während in der nativen Entwicklung die Tools und Bibliotheken jeder Plattform für die Erstellung von UI-Komponenten verwendet werden.

  3. Performance: Native Apps können möglicherweise eine bessere Performance bieten, da sie speziell für die jeweilige Plattform optimiert sind. Flutter kann jedoch auch eine gute Performance bieten, insbesondere durch seine Fähigkeit, die Anwendungsebene zu optimieren.

  4. Entwicklungsgeschwindigkeit: Flutter bietet Funktionen wie Hot Reload, die die Entwicklungsgeschwindigkeit erhöhen und es Entwicklern ermöglichen, schnell Änderungen zu testen und umzusetzen, ohne die App jedes Mal neu starten zu müssen. Bei der nativen Entwicklung kann dieser Prozess länger dauern.

  5. Community und Ressourcen: Die native Entwicklung hat eine große Entwicklergemeinschaft und eine Fülle von Ressourcen, Bibliotheken und Tools. Die Flutter-Community wächst jedoch ebenfalls schnell und bietet eine wachsende Anzahl von Ressourcen und Bibliotheken, die von Entwicklern erstellt werden.

Warum sollte man Flutter verwenden?

Flutter ermöglicht schnelle Entwicklung von plattformübergreifenden Apps mit einer einzigen Codebasis und bietet eine deklarative UI-Programmierung, hohe Performance, eine breite Palette von Widgets und Bibliotheken sowie eine aktive und wachsende Community.

3.2. Vorbereitungen

Das Flutter SDK kann von der offiziellen Flutter Dokumentationsseite für alle gängigen Betriebssysteme (Windows, macOS, Linux, ChromeOS) heruntergeladen werden. Im Flutter SDK enthalten ist das Dart SDK, also alle nötigen Pakete und Dateien für die Programmiersprache Dart. Unter macOS kann Flutter auch sehr angenehm mit dem Paketmanager brew und dem folgenden Kommando installiert werden: brew install –cask flutter. Nach der Installation sollte zuerst das Kommando flutter doctor ausgeführt werden. Damit kann überprüft werden, ob alle notwendigen Pakete installiert und Konfigurationen vorgenommen wurden. Ein möglicher Output kann folgender sein:

[-] Android toolchain - develop for Android devices
   Android SDK at D:\Android\sdk
   Android SDK is missing command line tools; download from https://goo.gl/XxQghQ
   Try re-installing or updating your Android SDK,
    visit https://docs.flutter.dev/setup/#android-setup for detailed instructions.

In diesem Beispiel muss noch das Android SDK installiert werden, um Apps für Android entwickeln zu können. Auf macOS, auf dem ausschließlich Apps auch für iOS kompiliert werden können, sieht der Output nach der Installation von Flutter, Xcode und Android SDK dann so aus:

Doctor summary (to see all details, run flutter doctor -v):
[] Flutter (Channel stable, 3.7.12, on macOS 13.3.1 22E261 darwin-arm64, locale en-DE)
[] Android toolchain - develop for Android devices (Android SDK version 32.0.0)
[] Xcode - develop for iOS and macOS (Xcode 14.3)
[] Chrome - develop for the web
[!] Android Studio (not installed)
[] IntelliJ IDEA Ultimate Edition (version 2022.2.3)
[] Connected device (2 available)
[] HTTP Host Availability

Mit diesem Setup kann mit dem Entwickeln von Flutter-Apps für iOS-, Android-, Web- und Desktop-Apps begonnen werden.

3.3. Kernkonzepte & Komponenten

1. Widgets

Widgets sind die grundlegenden Bausteine von Flutter-Apps. Alles in Flutter, einschließlich Benutzeroberfläche und UI-Elemente, ist ein Widget. Flutter bietet eine Vielzahl von vorgefertigten Widgets in der flutter/material Bibliotek

_images/widged.jpg
2. Hot Reload

Flutter ermöglicht es Entwicklern, jederzeit durch einen simplen Tastendruck ‚r‘ die Vorschau neu zu laden. Das macht Design und Debugging deutlich leichter.

3. Material Design und Cupertino Design

Flutter unterstützt sowohl das Material Designfür Android-Apps als auch das Cupertino Design von Apple für iOS-Apps. Somit können Entwickler für beide Systeme nativ wirkende Oberflächen erstellen

_images/iosAndroidDesign.png
4. State Management

Flutter bietet verschiedene Möglichkeiten, den Zustand in einer App zu verwalten, z. B. Stateful Widgets, Provider, Redux und MobX. State Management ist entscheidend, um den Zustand der App zu verfolgen und auf Benutzerinteraktionen oder andere Ereignisse zu reagieren.

5. Plugins

Flutter bietet die Funktion, über Plugins Hardwaregeräte, wie z.B. Kameras und Lautsprecher zu integrieren. Diese können später in der App genutzt werden.

6. Dart

Dart ist die von Flutter verwendete Programmiersprache. Dart ist eine objektorientierte Programmiersprache mit einem JIT-Compiler für schnelle Entwicklung. Dart bietet auch Funktionen wie asynchronen Code und Typisierung.

7. Flutter-Framework

Das Flutter-Framework ist das Herzstück von Flutter und stellt die Laufzeitumgebung für die Ausführung von Flutter-Apps bereit. Es bietet eine umfangreiche Sammlung von APIs für die Gestaltung von Benutzeroberflächen, das Verwalten von Zustand, das Kommunizieren mit APIs und mehr.

3.4. Anwendung

Verwendete Pakete üssen in der pubspec.yml angegeben werden:

name: dva05
description: A new Flutter project.
publish_to: 'none'
version: 0.1.0

environment:
sdk: '>=2.19.6 <3.0.0'

dependencies:
flutter:
    sdk: flutter
provider: ^6.0.5

dev_dependencies:
flutter_lints: ^2.0.0
flutter_test:
    sdk: flutter

flutter:
  uses-material-design: true

Mit dem provider Package konnen verschiedene Provider definiert werden. Diese können Daten über den Widgettree senden, ohne das diese als Parameter mitgegeben werden müssen. Mit dem ChangeNotifierProvider kann ein Notifier erstellt werden, auf den dann Listener definiert werden können:

void main() {
    final darkTheme = ThemeData.dark();
    final lightTheme = ThemeData.light();
    runApp(
        ChangeNotifierProvider(
        create: (_) => ThemeChanger(
            lightTheme,
            darkTheme.copyWith(
            primaryColor: Colors.purple.shade900,
            colorScheme: darkTheme.colorScheme.copyWith(
                primary: Colors.purple.shade900,
                secondary: Colors.purple.shade900),
            scaffoldBackgroundColor: Colors.grey.shade900,
            ),
        ),
        child: const MainApp(),
        ),
    );
}

Durch diesen Provider kann dann durch einen Button in der Appbar das Theme gewechselt werden:

final theme = Provider.of<ThemeChanger>(context);

return Scaffold(
    appBar: AppBar(
        title: const Text("Beispiel App Gruppe 5"),
        centerTitle: true,
        actions: [
            IconButton(
                onPressed: () {
                theme.toggle();
                },
                icon: theme.isDark()
                    ? const Icon(Icons.dark_mode_outlined)
                    : const Icon(Icons.light_mode_outlined),
            )
        ],
    ),
...

Die Klasse ThemeChanger enthält die verschiedenen Themes:

class ThemeChanger with ChangeNotifier {
    final ThemeData _lightThemeData;
    final ThemeData _darkThemeData;

    bool _isDark = false;

    ThemeChanger(this._lightThemeData, this._darkThemeData);

    getTheme() => _isDark ? _darkThemeData : _lightThemeData;

    isDark() => _isDark;

    setTheme(bool isDark) {
        if (isDark == _isDark) return;
        _isDark = isDark;
        notifyListeners();
    }

    toggle(){
        _isDark = !_isDark;
        notifyListeners();
    }
}

Start der App:

class MainApp extends StatelessWidget {
    const MainApp({super.key});

    @override
    Widget build(BuildContext context) {
        final theme = Provider.of<ThemeChanger>(context);

        return MaterialApp(
        title: 'DVA Prak 05',
        theme: theme.getTheme(),
        debugShowCheckedModeBanner: false,
        initialRoute: "/main",
        onGenerateRoute: RouteGenerator.generateRoute,
        );
    }
}

Hier wird das Theme festgelegt und angegeben, dass ein eigener Router für die verschiedenen Seiten verwendet werden soll. Durch einen Router kann die ganze Logik für das Wechseln der Seiten in eine Datei ausgelagert werden. Dies erleichtert außerdem, dass man spezifische Routen, die nur unter bestimmten Bedingungen erreichbar sind, sperren kann.

RouteGenerator:

class RouteGenerator {
    static Route<dynamic>? generateRoute(RouteSettings settings) {
        // final args = settings.arguments;
        switch (settings.name) {
        case "/":
            return null;
        case "/main":
            return MaterialPageRoute(builder: (_) => const HomePage());
        case "/listview":
            return MaterialPageRoute(builder: (_) => const ListViewPage());
        case "/gridview":
            return MaterialPageRoute(builder: (_) => const GridViewPage());
        case "/tabview":
            return MaterialPageRoute(builder: (_) => const TabViewPage());
        default:
            return _errorRoute();
        }
    }

    static Route<dynamic> _errorRoute(
        [String error = "Diese Seite exsistiert nicht"]) {
        return MaterialPageRoute(builder: (_) {
            return Scaffold(
                appBar: AppBar(title: const Text("Error")),
                body: Center(
                child: Text(error),
                ),
            );
        });
    }
}

Der eigentliche Inhalt der App wird mit der body Variable gesetzt:

body: Center(
    child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
            ElevatedButton(
                onPressed: () {
                    Navigator.pushNamed(context, "/listview");
                },
                child: const Text(
                    "ListView",
                    style: TextStyle(fontSize: 20),
                ),
            ),
            ElevatedButton(
                onPressed: () {
                    Navigator.pushNamed(context, "/gridview");
                },
                child: const Text(
                    "GridView",
                    style: TextStyle(fontSize: 20),
                ),
            ),
            ElevatedButton(
                onPressed: () {
                    Navigator.pushNamed(context, "/tabview");
                },
                child: const Text(
                    "Tabview",
                    style: TextStyle(fontSize: 20),
                ),
            ),
            ElevatedButton(
                onPressed: () {
                    Navigator.pushNamed(context, "/nix");
                },
                child: const Text(
                    "Error",
                    style: TextStyle(fontSize: 20),
                ),
            ),
        ],
    ),
),

Für diese Beispielapplikation werden auf der Startseite 4 Buttons angezeigt, die jeweils zu einer weiteren Seite führen. Diese enthalten verschiedene Widgets, die dessen Funktion demonstrieren.

Mit der ListView kann Content, der über eine Seite hinaus geht, mit scrollen, dargestellt werden:

class ListViewPage extends StatelessWidget {
    const ListViewPage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(),
            body: ListView.builder(
                itemCount: 30,
                itemBuilder: (context, i) {
                    double value = (30 - i) / 30;
                    int red;
                    int green;
                    int blue;
                    if (value < 0.5) {
                        red = (value * 2 * 255).round();
                        green = (value * 2 * 255).round();
                        blue = ((1 - value * 2) * 255).round();
                    } else {
                        red = ((1 - value) * 2 * 255).round();
                        green = ((1 - value) * 2 * 255).round();
                        blue = (value * 2 * 255).round();
                    }
                    return Container(
                        color: Color.fromARGB(255, red, green, blue),
                        height: 100,
                    );
                },
            ),
        );
    }
}
_images/ListView.png

Mit der GridView können Widgets scrollbar in einem Gitterlayout mit festgelegten Spalten oder Zeilen dargestellt werden:

class GridViewPage extends StatelessWidget {
    const GridViewPage({super.key});

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(),
            body: GridView.count(
                crossAxisCount: 3,
                children: List.generate(30, (i) {
                    double value = (30 - i) / 30;
                    int red;
                    int green;
                    int blue;
                    if (value < 0.5) {
                        red = (value * 2 * 255).round();
                        green = (value * 2 * 255).round();
                        blue = ((1 - value * 2) * 255).round();
                    } else {
                        red = ((1 - value) * 2 * 255).round();
                        green = ((1 - value) * 2 * 255).round();
                        blue = (value * 2 * 255).round();
                    }
                    return Container(
                        color: Color.fromARGB(255, red, green, blue),
                        height: 100,
                    );
                }),
            ),
        );
    }
}
_images/GridView.png

Mit einem TabView erfolgt die Unterteilung des Inhalts in mehrere Seiten:

class TabViewPage extends StatefulWidget {
    const TabViewPage({super.key});

    @override
    State<TabViewPage> createState() => _TabViewPageState();
}

class _TabViewPageState extends State<TabViewPage> with SingleTickerProviderStateMixin {
    late TabController _tabController;

    final List<TabPage> _pages = [
        const TabPage(title: "First page"),
        const TabPage(title: "Second page"),
        const TabPage(title: "Third page"),
    ];

    final List<Tab> _tabs = [
        const Tab(icon: Icon(Icons.cloud_outlined)),
        const Tab(icon: Icon(Icons.beach_access_sharp)),
        const Tab(icon: Icon(Icons.brightness_5_sharp)),
    ];

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
                title: Text(_pages[_tabController.index].title),
                bottom: TabBar(controller: _tabController, tabs: _tabs)),
            body: Column(
                children: [
                    Expanded(
                        child: TabBarView(
                        controller: _tabController,
                        children: _pages,
                        ),
                    ),
                    Padding(
                        padding: const EdgeInsets.all(8.0),
                        child: TabPageSelector(
                        controller: _tabController,
                        ),
                    ),
                ],
            ),
            bottomNavigationBar: BottomNavigationBar(
                currentIndex: _tabController.index,
                onTap: (value) {
                    setState(() {
                        _tabController.index = value;
                    });
                },
                items: const [
                    BottomNavigationBarItem(
                        icon: Icon(Icons.abc),
                        label: "1",
                    ),
                    BottomNavigationBarItem(
                        icon: Icon(Icons.abc),
                        label: "2",
                    ),
                    BottomNavigationBarItem(
                        icon: Icon(Icons.abc),
                        label: "3",
                    ),
                ],
            ),
        );
    }

    @override
    void initState() {
        super.initState();
        _tabController = TabController(
            length: _pages.length,
            vsync: this,
        );
        _tabController.addListener(() {
            setState(() {});
        });
    }
}


class TabPage extends StatelessWidget {
    const TabPage({Key? key, required this.title}) : super(key: key);
    final String title;

    @override
    Widget build(BuildContext context) {
        return Center(
        child: Text(title),
        );
    }
}
_images/TabView.png

3.5. Fazit

Mit nur einer Codebase können mit Flutter Anwendungen für mobile, Web- und Desktop-Plattformen erstellt werden. Außerdem bietet Flutter eine hervorragende Dokumentation und eine aktive Community, die ständig daran arbeitet, das Framework zu verbessern und weitere Packages hinzuzufügen. Ein weiterer Vorteil von Flutter ist seine Hot-Reload-Funktion, mit der Entwickler Änderungen am Code in Echtzeit sehen können. Dies beschleunigt den Entwicklungsprozess und ermöglicht es Entwicklern, Fehler schnell beheben zu können.

Insgesamt ist Flutter also eine gute Wahl für Entwickler, die plattformübergreifende Anwendungen erstellen möchten. Es bietet eine schnelle Entwicklung, eine hohe Benutzerfreundlichkeit, eine breite Palette von Funktionen und Werkzeugen sowie eine aktive Community.

3.6. Literaturangaben

Informationen:

https://docs.flutter.dev/

Bilder:

Widget: https://i.ytimg.com/vi/WOvj84xq_fc/maxresdefault.jpg

Material vs Cupertino: https://cdn.hashnode.com/res/hashnode/image/upload/v1620467794206/2lpjMZJ47.png