Zum Inhalt springen
Mobile

Flutter und Dart - Cross-Platform-Entwicklung in der Praxis

Veröffentlicht am:
·6 Min. Lesezeit·Autor: MDS Software Solutions Group

Flutter und Dart

mobile

Flutter und Dart - Cross-Platform-Entwicklung

Flutter ist ein Open-Source-Framework von Google, das die Erstellung nativ kompilierter Anwendungen fuer Mobilgeraete, Web und Desktop aus einer einzigen Codebasis ermoeglicht. In Kombination mit der Programmiersprache Dart bietet Flutter ein herausragendes Entwicklererlebnis, schnelle Entwicklungszyklen dank Hot Reload und eine Leistung, die mit nativen Anwendungen vergleichbar ist. In diesem umfassenden Leitfaden behandeln wir Flutters Architektur, die Sprache Dart, das Widget-System, State Management, die Integration mit nativen Plattformen sowie Best Practices fuer CI/CD.

Flutter-Architektur#

Flutter zeichnet sich durch eine einzigartige Architektur aus, die es von anderen Cross-Platform-Frameworks unterscheidet. Anstatt auf native UI-Komponenten zurueckzugreifen (wie React Native), rendert Flutter jeden Pixel auf dem Bildschirm mit seiner eigenen Grafik-Engine - Skia (und seit kurzem Impeller).

Architekturschichten#

Die Flutter-Architektur besteht aus drei Hauptschichten:

  • Framework (Dart) - die oberste Schicht mit Material Design- und Cupertino-Widgets, Gestensystem, Animationen, Rendering und Painting
  • Engine (C++) - die Engine fuer Grafikrendering (Skia/Impeller), Textverarbeitung, Netzwerk, Plugin-System und Dart-Runtime
  • Embedder (plattformspezifisch) - die Schicht, die die Engine mit einer bestimmten Plattform integriert (Android, iOS, Windows, macOS, Linux)
// Beispiel einer Flutter-App-Struktur
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MDS Flutter App',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const HomePage(),
    );
  }
}

Die Rendering-Engine - Impeller#

Ab Flutter 3.16 ist Impeller die Standard-Rendering-Engine auf iOS und ersetzt Skia. Impeller beseitigt das Problem der Runtime-Shader-Kompilierung (bekannt als "Jank"), wodurch Animationen ab dem allerersten Start fluessiger ablaufen. Auf Android ist Impeller als Preview-Option verfuegbar.

Die Sprache Dart - Flutters Fundament#

Dart ist eine moderne, objektorientierte Programmiersprache, die von Google entwickelt wurde. Sie wurde mit Blick auf Entwicklerproduktivitaet und Anwendungsleistung konzipiert.

Wichtige Eigenschaften von Dart#

// Null Safety - Standard seit Dart 2.12
String? nullableString; // kann null sein
String nonNullableString = 'Hat immer einen Wert';

// Extension Methods
extension StringExtension on String {
  String capitalize() {
    if (isEmpty) return this;
    return '${this[0].toUpperCase()}${substring(1)}';
  }
}

// Pattern Matching (Dart 3.0+)
sealed class Shape {}
class Circle extends Shape {
  final double radius;
  Circle(this.radius);
}
class Rectangle extends Shape {
  final double width, height;
  Rectangle(this.width, this.height);
}

double area(Shape shape) => switch (shape) {
  Circle(radius: var r) => 3.14159 * r * r,
  Rectangle(width: var w, height: var h) => w * h,
};

// Records - leichtgewichtige Tupel (Dart 3.0+)
(String, int) getUserInfo() => ('Max Mustermann', 30);

// Async/Await
Future<List<User>> fetchUsers() async {
  final response = await http.get(Uri.parse('https://api.example.com/users'));
  if (response.statusCode == 200) {
    final List<dynamic> data = jsonDecode(response.body);
    return data.map((json) => User.fromJson(json)).toList();
  }
  throw Exception('Fehler beim Laden der Benutzer');
}

// Streams
Stream<int> countStream(int max) async* {
  for (int i = 0; i < max; i++) {
    await Future.delayed(const Duration(seconds: 1));
    yield i;
  }
}

Dart-Kompilierung#

Dart unterstuetzt zwei Kompilierungsmodi:

  • JIT (Just-In-Time) - wird waehrend der Entwicklung verwendet, ermoeglicht Hot Reload
  • AOT (Ahead-Of-Time) - wird fuer Produktions-Builds verwendet, generiert nativen Maschinencode

Widget Tree - Das Herz von Flutter#

In Flutter ist alles ein Widget. Die Benutzeroberflaeche wird als Baum von Widgets aufgebaut, wobei jedes UI-Element durch ein Widget-Klassenobjekt repraesentiert wird.

StatelessWidget vs StatefulWidget#

// StatelessWidget - unveraenderlich, hat keinen internen Zustand
class UserCard extends StatelessWidget {
  final String name;
  final String email;
  final String avatarUrl;

  const UserCard({
    super.key,
    required this.name,
    required this.email,
    required this.avatarUrl,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      elevation: 4,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            CircleAvatar(
              radius: 30,
              backgroundImage: NetworkImage(avatarUrl),
            ),
            const SizedBox(width: 16),
            Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(name, style: const TextStyle(
                  fontSize: 18, fontWeight: FontWeight.bold,
                )),
                const SizedBox(height: 4),
                Text(email, style: TextStyle(
                  color: Colors.grey[600], fontSize: 14,
                )),
              ],
            ),
          ],
        ),
      ),
    );
  }
}

// StatefulWidget - hat veraenderlichen internen Zustand
class CounterWidget extends StatefulWidget {
  const CounterWidget({super.key});

  @override
  State<CounterWidget> createState() => _CounterWidgetState();
}

class _CounterWidgetState extends State<CounterWidget> {
  int _counter = 0;

  void _increment() {
    setState(() {
      _counter++;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Text('Zaehler: $_counter', style: const TextStyle(fontSize: 24)),
        const SizedBox(height: 16),
        ElevatedButton(
          onPressed: _increment,
          child: const Text('Erhoehen'),
        ),
      ],
    );
  }
}

Widget-Lebenszyklus#

StatefulWidget besitzt einen umfangreichen Lebenszyklus, der die Kontrolle ueber das Widget-Verhalten in verschiedenen Phasen ermoeglicht:

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

  @override
  State<LifecycleWidget> createState() => _LifecycleWidgetState();
}

class _LifecycleWidgetState extends State<LifecycleWidget> {
  @override
  void initState() {
    super.initState();
    // Einmal bei der Erstellung aufgerufen - Controller, Abonnements initialisieren
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    // Aufgerufen wenn sich InheritedWidgets aendern
  }

  @override
  void didUpdateWidget(covariant LifecycleWidget oldWidget) {
    super.didUpdateWidget(oldWidget);
    // Aufgerufen wenn das Eltern-Widget mit neuen Parametern neu aufgebaut wird
  }

  @override
  void dispose() {
    // Ressourcen bereinigen - Controller, Abonnements, Timer
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}

Material Design und Cupertino#

Flutter bietet zwei Widget-Sets, die auf die Designkonventionen von Android und iOS abgestimmt sind.

Material Design 3#

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: const Color(0xFF1A73E8),
          brightness: Brightness.light,
        ),
        typography: Typography.material2021(),
      ),
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Material 3 App'),
          actions: [
            IconButton(
              icon: const Icon(Icons.search),
              onPressed: () {},
            ),
          ],
        ),
        body: ListView(
          padding: const EdgeInsets.all(16),
          children: [
            FilledButton(
              onPressed: () {},
              child: const Text('Filled Button'),
            ),
            const SizedBox(height: 8),
            OutlinedButton(
              onPressed: () {},
              child: const Text('Outlined Button'),
            ),
            const SizedBox(height: 16),
            const SearchBar(hintText: 'Suchen...'),
          ],
        ),
        floatingActionButton: FloatingActionButton.extended(
          onPressed: () {},
          icon: const Icon(Icons.add),
          label: const Text('Hinzufuegen'),
        ),
        navigationBar: NavigationBar(
          destinations: const [
            NavigationDestination(icon: Icon(Icons.home), label: 'Start'),
            NavigationDestination(icon: Icon(Icons.explore), label: 'Entdecken'),
            NavigationDestination(icon: Icon(Icons.person), label: 'Profil'),
          ],
        ),
      ),
    );
  }
}

Cupertino (iOS-Stil)#

import 'package:flutter/cupertino.dart';

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

  @override
  Widget build(BuildContext context) {
    return CupertinoApp(
      theme: const CupertinoThemeData(
        primaryColor: CupertinoColors.activeBlue,
      ),
      home: CupertinoPageScaffold(
        navigationBar: const CupertinoNavigationBar(
          middle: Text('Cupertino App'),
        ),
        child: SafeArea(
          child: CupertinoListSection(
            header: const Text('Einstellungen'),
            children: [
              CupertinoListTile(
                title: const Text('Benachrichtigungen'),
                trailing: CupertinoSwitch(
                  value: true,
                  onChanged: (v) {},
                ),
              ),
              CupertinoListTile(
                title: const Text('Dunkler Modus'),
                trailing: CupertinoSwitch(
                  value: false,
                  onChanged: (v) {},
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Flutter bietet mehrere Ansaetze zur Navigation, vom einfachen Navigator 1.0 ueber den deklarativen Router (Navigator 2.0) bis hin zu beliebten Paketen wie GoRouter.

// GoRouter - der empfohlene Ansatz fuer Navigation
import 'package:go_router/go_router.dart';

final router = GoRouter(
  initialLocation: '/',
  routes: [
    GoRoute(
      path: '/',
      builder: (context, state) => const HomeScreen(),
      routes: [
        GoRoute(
          path: 'products',
          builder: (context, state) => const ProductsScreen(),
          routes: [
            GoRoute(
              path: ':id',
              builder: (context, state) {
                final id = state.pathParameters['id']!;
                return ProductDetailScreen(productId: id);
              },
            ),
          ],
        ),
        GoRoute(
          path: 'profile',
          builder: (context, state) => const ProfileScreen(),
          redirect: (context, state) {
            final isLoggedIn = AuthService.instance.isLoggedIn;
            if (!isLoggedIn) return '/login';
            return null;
          },
        ),
      ],
    ),
    GoRoute(
      path: '/login',
      builder: (context, state) => const LoginScreen(),
    ),
  ],
);

State Management#

State Management ist ein entscheidender Aspekt jeder Flutter-Anwendung. Es gibt viele Loesungen, von denen jede ihre eigenen Staerken hat.

Provider#

Provider ist die einfachste und offiziell empfohlene Loesung fuer State Management:

// Datenmodell mit ChangeNotifier
class CartModel extends ChangeNotifier {
  final List<Product> _items = [];

  List<Product> get items => List.unmodifiable(_items);
  int get totalItems => _items.length;
  double get totalPrice =>
      _items.fold(0, (sum, item) => sum + item.price);

  void addItem(Product product) {
    _items.add(product);
    notifyListeners();
  }

  void removeItem(Product product) {
    _items.remove(product);
    notifyListeners();
  }

  void clear() {
    _items.clear();
    notifyListeners();
  }
}

// Provider-Konfiguration
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider(create: (_) => CartModel()),
        ChangeNotifierProvider(create: (_) => UserModel()),
      ],
      child: const MyApp(),
    ),
  );
}

// Verwendung im Widget
class CartScreen extends StatelessWidget {
  const CartScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer<CartModel>(
      builder: (context, cart, child) {
        return Column(
          children: [
            Text('Produkte im Warenkorb: ${cart.totalItems}'),
            Text('Gesamt: ${cart.totalPrice.toStringAsFixed(2)} EUR'),
            Expanded(
              child: ListView.builder(
                itemCount: cart.items.length,
                itemBuilder: (context, index) {
                  final item = cart.items[index];
                  return ListTile(
                    title: Text(item.name),
                    subtitle: Text('${item.price} EUR'),
                    trailing: IconButton(
                      icon: const Icon(Icons.delete),
                      onPressed: () => cart.removeItem(item),
                    ),
                  );
                },
              ),
            ),
          ],
        );
      },
    );
  }
}

Riverpod#

Riverpod ist die Weiterentwicklung von Provider, die viele seiner Einschraenkungen behebt - es eliminiert die Abhaengigkeit vom BuildContext und bietet bessere Typsicherheit:

import 'package:flutter_riverpod/flutter_riverpod.dart';

// Provider-Definitionen
final userRepositoryProvider = Provider((ref) => UserRepository());

final usersProvider = FutureProvider<List<User>>((ref) async {
  final repository = ref.watch(userRepositoryProvider);
  return repository.fetchUsers();
});

final selectedUserProvider = StateProvider<User?>((ref) => null);

// Notifier fuer komplexe Logik
class TodoNotifier extends Notifier<List<Todo>> {
  @override
  List<Todo> build() => [];

  void addTodo(String title) {
    state = [...state, Todo(title: title, completed: false)];
  }

  void toggleTodo(int index) {
    state = [
      for (int i = 0; i < state.length; i++)
        if (i == index)
          state[i].copyWith(completed: !state[i].completed)
        else
          state[i],
    ];
  }
}

final todosProvider = NotifierProvider<TodoNotifier, List<Todo>>(
  TodoNotifier.new,
);

// Verwendung im Widget
class UserListScreen extends ConsumerWidget {
  const UserListScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final usersAsync = ref.watch(usersProvider);

    return usersAsync.when(
      data: (users) => ListView.builder(
        itemCount: users.length,
        itemBuilder: (context, index) => ListTile(
          title: Text(users[index].name),
          onTap: () => ref.read(selectedUserProvider.notifier).state = users[index],
        ),
      ),
      loading: () => const Center(child: CircularProgressIndicator()),
      error: (error, stack) => Center(child: Text('Fehler: $error')),
    );
  }
}

Bloc (Business Logic Component)#

Bloc ist ein Architekturmuster basierend auf Streams, das die Geschaeftslogik von der Praesentationsschicht trennt:

import 'package:flutter_bloc/flutter_bloc.dart';

// Events
sealed class AuthEvent {}
class LoginRequested extends AuthEvent {
  final String email, password;
  LoginRequested({required this.email, required this.password});
}
class LogoutRequested extends AuthEvent {}

// States
sealed class AuthState {}
class AuthInitial extends AuthState {}
class AuthLoading extends AuthState {}
class AuthAuthenticated extends AuthState {
  final User user;
  AuthAuthenticated(this.user);
}
class AuthError extends AuthState {
  final String message;
  AuthError(this.message);
}

// Bloc
class AuthBloc extends Bloc<AuthEvent, AuthState> {
  final AuthRepository _authRepository;

  AuthBloc(this._authRepository) : super(AuthInitial()) {
    on<LoginRequested>(_onLogin);
    on<LogoutRequested>(_onLogout);
  }

  Future<void> _onLogin(LoginRequested event, Emitter<AuthState> emit) async {
    emit(AuthLoading());
    try {
      final user = await _authRepository.login(event.email, event.password);
      emit(AuthAuthenticated(user));
    } catch (e) {
      emit(AuthError(e.toString()));
    }
  }

  Future<void> _onLogout(LogoutRequested event, Emitter<AuthState> emit) async {
    await _authRepository.logout();
    emit(AuthInitial());
  }
}

Platform Channels - Integration mit nativem Code#

Platform Channels ermoeglichen die Kommunikation zwischen Dart-Code und nativem Plattformcode (Kotlin/Java auf Android, Swift/Objective-C auf iOS):

import 'package:flutter/services.dart';

class BatteryService {
  static const _channel = MethodChannel('com.mds.app/battery');

  static Future<int> getBatteryLevel() async {
    try {
      final int result = await _channel.invokeMethod('getBatteryLevel');
      return result;
    } on PlatformException catch (e) {
      throw Exception('Batteriestand konnte nicht gelesen werden: ${e.message}');
    }
  }

  // EventChannel fuer kontinuierliches Monitoring
  static const _eventChannel = EventChannel('com.mds.app/battery_stream');

  static Stream<int> get batteryStream {
    return _eventChannel.receiveBroadcastStream().map((event) => event as int);
  }
}

// Verwendung im Widget
class BatteryWidget extends StatelessWidget {
  const BatteryWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<int>(
      stream: BatteryService.batteryStream,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          return Text('Batterie: ${snapshot.data}%');
        }
        return const CircularProgressIndicator();
      },
    );
  }
}

Flutter Web und Desktop#

Flutter beschraenkt sich nicht auf mobile Anwendungen. Das Framework unterstuetzt auch Web- und Desktop-Plattformen.

Flutter Web#

Flutter Web kompiliert Dart-Code zu JavaScript (CanvasKit oder HTML-Renderer) und ermoeglicht die Ausfuehrung von Anwendungen im Browser:

import 'package:flutter/foundation.dart' show kIsWeb;

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

  @override
  Widget build(BuildContext context) {
    if (kIsWeb) {
      return const WebOptimizedLayout();
    }

    return switch (Theme.of(context).platform) {
      TargetPlatform.iOS || TargetPlatform.macOS => const CupertinoLayout(),
      TargetPlatform.windows || TargetPlatform.linux => const DesktopLayout(),
      _ => const MaterialLayout(),
    };
  }
}

Flutter Desktop#

Flutter Desktop ermoeglicht die Erstellung nativer Anwendungen fuer Windows, macOS und Linux:

// Fensterverwaltung auf Desktop
import 'package:window_manager/window_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  if (!kIsWeb && (Platform.isWindows || Platform.isMacOS || Platform.isLinux)) {
    await windowManager.ensureInitialized();
    WindowOptions windowOptions = const WindowOptions(
      size: Size(1280, 720),
      minimumSize: Size(800, 600),
      center: true,
      title: 'MDS Desktop App',
    );
    windowManager.waitUntilReadyToShow(windowOptions, () async {
      await windowManager.show();
      await windowManager.focus();
    });
  }

  runApp(const MyApp());
}

Hot Reload und Hot Restart#

Einer der groessten Vorteile von Flutter ist Hot Reload - ein Mechanismus, der es ermoeglicht, Codeaenderungen sofort zu aktualisieren, ohne den Anwendungszustand zu verlieren.

  • Hot Reload - injiziert aktualisierten Quellcode in die laufende Dart VM und bewahrt dabei den Anwendungszustand. UI-Aenderungen sind in weniger als einer Sekunde sichtbar
  • Hot Restart - startet die Anwendung von Grund auf neu und setzt den gesamten Zustand zurueck. Erforderlich bei Aenderungen in initState, globalen Variablen oder Klassenstrukturen

Hot Reload funktioniert dank der JIT-Kompilierung (Just-In-Time), die im Debug-Modus verwendet wird. In Produktions-Builds nutzt Flutter die AOT-Kompilierung (Ahead-Of-Time) und generiert nativen Maschinencode fuer maximale Leistung.

Testen in Flutter#

Flutter bietet umfassende Testwerkzeuge auf drei Ebenen.

Unit-Tests#

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('CartModel', () {
    late CartModel cart;

    setUp(() {
      cart = CartModel();
    });

    test('sollte Produkt zum Warenkorb hinzufuegen', () {
      final product = Product(id: '1', name: 'Widget', price: 29.99);
      cart.addItem(product);

      expect(cart.totalItems, 1);
      expect(cart.totalPrice, 29.99);
    });

    test('sollte Gesamtpreis berechnen', () {
      cart.addItem(Product(id: '1', name: 'A', price: 10.0));
      cart.addItem(Product(id: '2', name: 'B', price: 20.0));

      expect(cart.totalPrice, 30.0);
    });

    test('sollte Warenkorb leeren', () {
      cart.addItem(Product(id: '1', name: 'A', price: 10.0));
      cart.clear();

      expect(cart.totalItems, 0);
      expect(cart.totalPrice, 0.0);
    });
  });
}

Widget-Tests#

import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('CounterWidget sollte Zaehler erhoehen', (tester) async {
    await tester.pumpWidget(const MaterialApp(home: CounterWidget()));

    expect(find.text('Zaehler: 0'), findsOneWidget);
    expect(find.text('Zaehler: 1'), findsNothing);

    await tester.tap(find.text('Erhoehen'));
    await tester.pump();

    expect(find.text('Zaehler: 0'), findsNothing);
    expect(find.text('Zaehler: 1'), findsOneWidget);
  });

  testWidgets('UserCard sollte Benutzerdaten anzeigen', (tester) async {
    await tester.pumpWidget(const MaterialApp(
      home: Scaffold(
        body: UserCard(
          name: 'Max Mustermann',
          email: 'max@example.com',
          avatarUrl: 'https://example.com/avatar.jpg',
        ),
      ),
    ));

    expect(find.text('Max Mustermann'), findsOneWidget);
    expect(find.text('max@example.com'), findsOneWidget);
  });
}

Integrationstests#

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  testWidgets('vollstaendiger Login-Ablauf', (tester) async {
    await tester.pumpWidget(const MyApp());

    await tester.enterText(find.byKey(const Key('email')), 'test@mds.de');
    await tester.enterText(find.byKey(const Key('password')), 'password123');
    await tester.tap(find.byKey(const Key('loginButton')));
    await tester.pumpAndSettle();

    expect(find.text('Willkommen, Test!'), findsOneWidget);
  });
}

CI/CD mit Codemagic und Fastlane#

Die Automatisierung des Build- und Bereitstellungsprozesses ist fuer professionelle Flutter-Projekte unerlaesslich.

Codemagic#

Codemagic ist eine dedizierte CI/CD-Plattform fuer Flutter, die native Unterstuetzung fuer das Erstellen von Anwendungen auf allen Plattformen bietet:

# codemagic.yaml
workflows:
  production-release:
    name: Production Release
    max_build_duration: 60
    instance_type: mac_mini_m1
    environment:
      flutter: stable
      groups:
        - app_store_credentials
        - google_play_credentials
    triggering:
      events:
        - tag
      tag_patterns:
        - pattern: 'v*'
    scripts:
      - name: Abhaengigkeiten installieren
        script: flutter pub get
      - name: Code-Analyse
        script: flutter analyze
      - name: Unit-Tests
        script: flutter test
      - name: Android Build
        script: |
          flutter build appbundle --release \
            --build-number=$PROJECT_BUILD_NUMBER
      - name: iOS Build
        script: |
          flutter build ipa --release \
            --build-number=$PROJECT_BUILD_NUMBER \
            --export-options-plist=/Users/builder/export_options.plist
    artifacts:
      - build/**/outputs/**/*.aab
      - build/ios/ipa/*.ipa
    publishing:
      google_play:
        credentials: $GCLOUD_SERVICE_ACCOUNT_CREDENTIALS
        track: production
      app_store_connect:
        api_key: $APP_STORE_CONNECT_PRIVATE_KEY
        key_id: $APP_STORE_CONNECT_KEY_ID
        issuer_id: $APP_STORE_CONNECT_ISSUER_ID

Fastlane#

Fastlane ist ein beliebtes Werkzeug zur Automatisierung des Erstellens, Testens und Bereitstellens mobiler Anwendungen:

# fastlane/Fastfile
default_platform(:ios)

platform :ios do
  desc "Bereitstellung auf TestFlight"
  lane :beta do
    build_ios_app(
      scheme: "Runner",
      archive_path: "./build/Runner.xcarchive",
      export_method: "app-store",
      output_directory: "./build/Runner",
    )
    upload_to_testflight(skip_waiting_for_build_processing: true)
  end

  desc "Bereitstellung im App Store"
  lane :release do
    build_ios_app(scheme: "Runner", export_method: "app-store")
    upload_to_app_store(force: true, submit_for_review: true)
  end
end

platform :android do
  desc "Bereitstellung auf Google Play (Beta)"
  lane :beta do
    upload_to_play_store(
      track: 'beta',
      aab: '../build/app/outputs/bundle/release/app-release.aab',
    )
  end
end

Flutter vs React Native - Vergleich#

Die Wahl zwischen Flutter und React Native ist eine der haeufigsten Fragen in der Welt der mobilen Entwicklung. Hier sind die wichtigsten Unterschiede:

| Aspekt | Flutter | React Native | |--------|---------|-------------| | Sprache | Dart | JavaScript/TypeScript | | Rendering | Eigene Engine (Skia/Impeller) | Native Plattformkomponenten | | Leistung | Sehr hoch (AOT-Kompilierung) | Hoch (JSI, Fabric) | | UI | Pixelgenau, identisch auf jeder Plattform | Natives Look and Feel der Plattform | | Oekosystem | Wachsend, Google-unterstuetzt | Sehr gross, Meta-unterstuetzt | | Hot Reload | Ja (sehr schnell) | Ja (Fast Refresh) | | Lernkurve | Mittel (Dart muss erlernt werden) | Niedrig (JavaScript/React) | | Web/Desktop | Offizielle Unterstuetzung | React Native Web (Community) | | App-Groesse | Groesser (integrierte Rendering-Engine) | Kleiner (native Komponenten) |

Wann Flutter waehlen?#

  • Sie benoetigen eine identische UI auf allen Plattformen
  • Hochleistungsfaehige Animationen sind wichtig
  • Sie planen Apps fuer Mobile, Web und Desktop
  • Sie wuenschen schnelle Entwicklung mit Hot Reload
  • Sie erstellen Apps mit individuellem, reichhaltigem UI

Wann React Native waehlen?#

  • Ihr Team hat JavaScript/React-Erfahrung
  • Sie benoetigen das native Look and Feel der Plattform
  • Sie moechten das grosse npm-Oekosystem nutzen
  • Sie integrieren sich in eine bestehende native App
  • Die App-Groesse ist ein kritischer Faktor

Flutter Best Practices#

  1. Verwenden Sie const-Konstruktoren - markieren Sie Widgets wo moeglich als const, um unnoetige Neuaufbauten zu vermeiden
  2. Vermeiden Sie uebermassige Verschachtelung - extrahieren Sie Sub-Widgets in eigene Klassen
  3. Verwenden Sie geeignetes State Management - Provider fuer einfache Faelle, Riverpod oder Bloc fuer komplexe Anwendungen
  4. Nutzen Sie DevTools - Flutter DevTools bietet fortschrittliche Werkzeuge fuer Profiling, Debugging und Widget-Tree-Inspektion
  5. Implementieren Sie responsives Design - verwenden Sie MediaQuery, LayoutBuilder und Flex-Widgets
  6. Optimieren Sie Listen - verwenden Sie ListView.builder anstelle von ListView mit Children fuer lange Listen
  7. Schreiben Sie Tests - Unit-, Widget- und Integrationstests gewaehrleisten die Stabilitaet der Anwendung

Zusammenfassung#

Flutter und Dart sind ein leistungsstarkes Duo fuer die Erstellung moderner Cross-Platform-Anwendungen. Flutters einzigartige Architektur, das reichhaltige Widget-Oekosystem, die Multi-Plattform-Unterstuetzung und das herausragende Entwicklererlebnis machen es zu einer der besten Optionen fuer die Erstellung von Mobil-, Web- und Desktop-Anwendungen im Jahr 2024. Unabhaengig davon, ob Sie ein MVP oder eine Enterprise-Anwendung erstellen, liefert Flutter die Werkzeuge und die Leistung, die Sie benoetigen.


Benoetigen Sie eine professionelle Cross-Platform-Anwendung? Das Team von MDS Software Solutions Group ist auf die Entwicklung leistungsstarker Flutter-Apps fuer iOS, Android, Web und Desktop spezialisiert. Kontaktieren Sie uns, um Ihr Projekt zu besprechen und zu erfahren, wie wir Ihnen helfen koennen, Ihre Vision zu verwirklichen.

Autor
MDS Software Solutions Group

Team von Programmierexperten, die sich auf moderne Webtechnologien spezialisiert haben.

Flutter und Dart - Cross-Platform-Entwicklung in der Praxis | MDS Software Solutions Group | MDS Software Solutions Group