Skip to content

Kapitel 19: Erweiterte Lernswege

19.1 Erweiterte Zustandsverwaltung (Bloc, GetX, Riverpod)

Bloc (Business Logic Component)

Wann verwenden?

  • ✅ Große, komplexe Projekte
  • ✅ Strenge Trennung von Logik und UI
  • ✅ Gute Testbarkeit
  • ✅ Enterprise-Anwendungen

Grundlegende Struktur:

Bloc besteht aus:
1. Events (Ereignisse) - Benutzeraktionen
2. States (Zustände) - UI-Zustände
3. Bloc - Verarbeitet Events zu States

Einfaches Beispiel:

dart
// Event
abstract class CounterEvent {}

class IncrementEvent extends CounterEvent {}

// State
class CounterState {
  final int counter;
  CounterState(this.counter);
}

// Bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
  CounterBloc() : super(CounterState(0)) {
    on<IncrementEvent>((event, emit) {
      emit(CounterState(state.counter + 1));
    });
  }
}

Vorteile:

  • ✅ Sehr strukturiert
  • ✅ Leicht zu testen
  • ✅ Gute Dokumentation

Nachteile:

  • ❌ Viel Boilerplate-Code
  • ❌ Schwieriger für Anfänger

GetX (Einfach & beliebt)

Wann verwenden?

  • ✅ Schnelle Entwicklung
  • ✅ Wenig Code
  • ✅ Kombiniert Zustandsverwaltung, Routing, Abhängigkeitsinjektion

Einfaches Beispiel:

dart
// Controller
class CounterController extends GetxController {
  int counter = 0;
  
  void increment() {
    counter++;
    update();  // Benachrichtigt UI
  }
}

// Verwendung
class CounterPage extends StatelessWidget {
  final controller = Get.put(CounterController());
  
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            GetBuilder<CounterController>(
              builder: (c) => Text('${c.counter}'),
            ),
            ElevatedButton(
              onPressed: controller.increment,
              child: const Text('Erhöhen'),
            ),
          ],
        ),
      ),
    );
  }
}

Vorteile:

  • ✅ Sehr einfach
  • ✅ Wenig Code
  • ✅ Routing & Abhängigkeitsinjektion inklusive

Nachteile:

  • ❌ Weniger "Flutter-idomatisch"
  • ❌ Schwieriger zu debuggen

Riverpod (Moderne Alternative)

Wann verwenden?

  • ✅ Moderne Zustandsverwaltung
  • ✅ Typsicher
  • ✅ Testbar
  • ✅ Keine BuildContext-Abhängigkeit

Einfaches Beispiel:

dart
// Provider
final counterProvider = StateProvider<int>((ref) => 0);

// Verwendung
class CounterPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final counter = ref.watch(counterProvider);
    
    return Scaffold(
      body: Center(
        child: Column(
          children: <Widget>[
            Text('$counter'),
            ElevatedButton(
              onPressed: () {
                ref.read(counterProvider.notifier).state++;
              },
              child: const Text('Erhöhen'),
            ),
          ],
        ),
      ),
    );
  }
}

Vorteile:

  • ✅ Typsicher
  • ✅ Testbar
  • ✅ Keine BuildContext-Abhängigkeit

Nachteile:

  • ❌ Lernkurve (neue Konzepte)
  • ❌ Boilerplate-Code

19.2 Flutter Desktop-Anwendungen (Windows, Mac, Linux)

Vorbereitung

Aktivieren Sie Desktop-Unterstützung:

bash
flutter config --enable-windows-desktop
flutter config --enable-macos-desktop
flutter config --enable-linux-desktop

Projekt für Desktop konfigurieren:

bash
# Neues Projekt mit Desktop-Unterstützung erstellen
flutter create --platforms=windows,macos,linux mein_desktop_projekt

# Bestehendes Projekt aktualisieren
flutter create .

Windows-App erstellen

windows/runner/main.cpp bearbeiten:

cpp
#include <flutter/dart_project.h>
#include <flutter/flutter_view_controller.h>
#include <windows.h>

int APIENTRY wWinMain(_In_ int     argc,
                       _In_reads_(argc) wchar_t** argv) {
  // Flutter-Projekt initialisieren
  flutter::DartProject project(L".");
  
  // Flutter-View-Controller erstellen
  flutter::FlutterViewController controller(project);
  
  // Fenster erstellen
  // ...
  
  return 0;
}

windows/runner/Runner.rc bearbeiten (App-Informationen):

// App-Name
1 VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGSMASK 0x3fL
FILEFLAGS 0x0L
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
    BLOCK "StringFileInfo"
    BEGIN
        BLOCK "040704b0"
        BEGIN
            VALUE "CompanyName", "Meine Firma"
            VALUE "FileDescription", "Meine Desktop-App"
            VALUE "FileVersion", "1.0.0.0"
            VALUE "InternalName", "meine_app"
            VALUE "LegalCopyright", "Copyright (C) 2024"
            VALUE "OriginalFilename", "meine_app.exe"
            VALUE "ProductName", "Meine App"
            VALUE "ProductVersion", "1.0.0.0"
        END
    END
END

Mac-App erstellen

macos/Runner/Info.plist bearbeiten:

xml
<key>CFBundleName</key>
<string>Meine App</string>
<key>CFBundleDisplayName</key>
<string>Meine App</string>
<key>CFBundleIdentifier</key>
<string>com.beispiel.meineapp</string>
<key>CFBundleVersion</key>
<string>1.0.0</string>

macos/Runner.xcworkspace in Xcode öffnen:

  1. Signierungskonfiguration vornehmen
  2. App bauen (Product → Archive)
  3. App veröffentlichen

Linux-App erstellen

linux/main.cc bearbeiten:

cpp
#include <flutter_linux/flutter_linux.h>

int main(int argc, char** argv) {
  // Flutter-Projekt initialisieren
  g_autoptr<FlDartProject> project = fl_dart_project_new();
  
  // Flutter-View erstellen
  FlView* view = fl_view_new(project);
  
  // Fenster erstellen und anzeigen
  // ...
  
  return 0;
}

App paketieren (Snap, Deb, etc.):

bash
# Snap-Paket erstellen
snapcraft init
snapcraft

# Deb-Paket erstellen
dpkg-deb --build mein_paket

19.3 Flutter Mini-Programm-Entwicklung (WeChat Mini-Programm)

flutter_wechat (Plugin)

Installation:

yaml
dependencies:
  flutter_wechat: ^0.1.0

Verwendung:

dart
import 'package:flutter_wechat/flutter_wechat.dart';

void login() async {
  final result = await FlutterWechat.login();
  print('Login-Ergebnis: $result');
}

Offizielle WeChat Mini-Programm-Dokumentation

Wichtigste Konzepte:

  1. WXML (ähnlich wie HTML)
  2. WXSS (ähnlich wie CSS)
  3. JavaScript (Logik)
  4. WeChat API (Login, Bezahlung, etc.)

Flutter mit WeChat Mini-Programm kombinieren:

  • ✅ Flutter für die App-Entwicklung verwenden
  • ✅ WeChat Mini-Programm für leichte Funktionen verwenden
  • ✅ API-Kommunikation zwischen App und Mini-Programm

19.4 Flutter native Interaktion (mit Android/iOS nativen Code kommunizieren)

MethodChannel (Kommunikation zwischen Flutter & nativem Code)

Flutter-Seite (Dart-Code):

dart
import 'package:flutter/services.dart';

static const platform = MethodChannel('samples.flutter.dev/battery');

Future<void> getBatteryLevel() async {
  try {
    final result = await platform.invokeMethod<int>('getBatteryLevel');
    print('Batteriestand: $result%');
  } on PlatformException catch (e) {
    print('Fehler: ${e.message}');
  }
}

Android-Seite (Java-Code):

java
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugin.common.MethodChannel;

public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "samples.flutter.dev/battery";

  @Override
  public void configureFlutterEngine(FlutterEngine flutterEngine) {
    new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
        .setMethodCallHandler(
          (call, result) -> {
            if (call.method.equals("getBatteryLevel")) {
              int batteryLevel = getBatteryLevel();
              result.success(batteryLevel);
            } else {
              result.notImplemented();
            }
          }
        );
  }
  
  private int getBatteryLevel() {
    // Batteriestand abrufen
    return 100;  // Beispiel
  }
}

iOS-Seite (Swift-Code):

swift
import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller: FlutterViewController = window?.rootViewController as! FlutterViewController
    let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                              binaryMessenger: controller.binaryMessenger)
    batteryChannel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      if call.method == "getBatteryLevel" {
        self.getBatteryLevel(result: result)
      } else {
        result(FlutterMethodNotImplemented)
      }
    })
    
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }
  
  private func getBatteryLevel(result: FlutterResult) {
    // Batteriestand abrufen
    result(100)  // Beispiel
  }
}

19.5 Flutter Performance-Optimierung fortgeschritten (Tiefe Optimierung, Ruckeln lösen)

1. const Konstruktoren verwenden

Schlecht:

dart
Text('Hallo')  // Jedes Mal neu erstellt

Gut:

dart
const Text('Hallo')  // Einmal erstellt, wiederverwendet

2. ListView.builder statt ListView.children verwenden

Schlecht (alle Elemente werden auf einmal erstellt):

dart
ListView(
  children: List.generate(1000, (index) => ListTile(title: Text('Element $index'))),
)

Gut (nur sichtbare Elemente werden erstellt):

dart
ListView.builder(
  itemCount: 1000,
  itemBuilder: (context, index) => ListTile(title: Text('Element $index')),
)

3. Bilder cachen (cached_network_image)

Installation:

yaml
dependencies:
  cached_network_image: ^3.3.0

Verwendung:

dart
CachedNetworkImage(
  imageUrl: 'https://example.com/bild.jpg',
  placeholder: (context, url) => const CircularProgressIndicator(),
  errorWidget: (context, url, error) => const Icon(Icons.error),
)

4. setState() sparsam verwenden

Schlecht (gesamte Seite wird neu gebaut):

dart
void _erhoehen() {
  setState(() {
    _zaehler++;
    _aktualisiereText();  // Unnötig, da es nicht die UI beeinflusst
  });
}

Gut (nur notwendige Teile aktualisieren):

dart
void _erhoehen() {
  setState(() {
    _zaehler++;
  });
}

void _aktualisiereText() {
  // Logik, die kein setState() benötigt
}

5. AnimatedBuilder oder ValueListenableBuilder verwenden

Für komplexe Animationen/Zustandsänderungen:

dart
ValueListenableBuilder<int>(
  valueListenable: _zaehler,
  builder: (context, wert, child) {
    return Text('$wert');
  },
)

6. RepaintBoundary verwenden

Verwendung:

dart
RepaintBoundary(
  child: Container(
    width: 100,
    height: 100,
    color: Colors.red,
  ),
)

Wann verwenden?

  • ✅ Komplexe Widgets, die sich oft ändern
  • ✅ Animationen
  • ✅ Bilder

Zusammenfassung

In diesem Kapitel haben Sie:

  • ✅ Erweiterte Zustandsverwaltung (Bloc, GetX, Riverpod) kennengelernt
  • ✅ Flutter Desktop-Anwendungen (Windows, Mac, Linux) erstellt
  • ✅ Flutter Mini-Programm-Entwicklung (WeChat Mini-Programm) kennengelernt
  • ✅ Flutter native Interaktion (mit Android/iOS nativen Code kommunizieren) gelernt
  • ✅ Flutter Performance-Optimierung fortgeschritten (Tiefe Optimierung, Ruckeln lösen) gelernt

Nächstes Kapitel: Wir werden Lernressourcen lernen (offizielle Dokumenten, Online-Übungen, Videos).


Übungsaufgaben:

  1. Implementieren Sie Zustandsverwaltung mit Bloc für ein komplexes Projekt
  2. Erstellen Sie eine Desktop-Anwendung (Windows, Mac oder Linux)
  3. Implementieren Sie native Interaktion mit MethodChannel (Batteriestand abrufen)
  4. Optimieren Sie die Performance einer App mit komplexen Animationen

Häufige Fehler:

  • ❌ Zustandsverwaltung zu komplex machen für einfache Apps → Bei einfachen Zuständen setState() verwenden!
  • ❌ Desktop-Anwendung ohne Konfiguration bauen → Desktop-Unterstützung aktivieren!
  • const Konstruktoren vergessen → Performance-Probleme!
  • ListView ohne builder für große Datenmengen verwenden → Performance-Probleme!

Frei für alle Anfänger