flutter

logo


Motivation

traffic-share


Development

Native
Eine App je für Android/iOs
Cross Platform
Eine App, welche irgendwie auf beiden Systemen läuft
Player:
  • React Native
  • Ionic
  • Flutter

(React) Native vs Flutter

comparison


Architektur

architektur


Setup

  1. Flutter [Get the Flutter SDK, Windows setup[
    
                PS C:\> flutter doctor
                Doctor summary (to see all details, run ...):
                [√] Flutter (Channel stable, 2.5.3, on Microsoft ...)
                [√] Android toolchain - develop for Android ...
                [√] Chrome - develop for the web
                [√] Android Studio (version 2020.3)
                [√] IntelliJ IDEA Ultimate Edition (version 2021.3)
                [√] Connected device (2 available)
            
  2. Android Studio Plugins Dart/Flutter

Hello World

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class HelloWorldApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // abstract
    return MaterialApp(
      home: Text('Hello World'), // , -> Autoformatter
    );
  }
}

Android

  • Einstellungen ->
    (Telefoninfo -> Softwareinformation -> )
    Buildnummer -> 7 mal tappen
  • Developermodus aktiviert ✔️
  • USB-Debugging ✔️

Flutter Inspector


Widget Tree

widget

widget


Widget Tree

widget tree


Widget

  • immutable description of UI
  • inflated zu Elements
  • build() updated oder deleted und inflated den Subtree neu

Scaffold


Container


Container/Column/Row

container/row/column


ListView

listview/builder

Builder ⟹ Performance


Expanded


StatefulWidget

class Counter extends StatefulWidget {
  @override
  _CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
  int _count = 0;
  @override
Widget build(BuildContext context) {
  return Row(
    children: [
      ElevatedButton(
        onPressed: _updateCount,
        child: Text('$_count'),
      ),
    ],
  );
}
  void _updateCount() {
  setState(() => _count++);
}

state


UI updated nicht

class Changer extends StatefulWidget {
  @override
  _ChangerState createState() => _ChangerState();
}

class _ChangerState extends State<Changer> {
  String _text = 'Lorem';

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
          onPressed: () => _text = 'ipsum',
          child: Text('$_text'),
        ),
      ],
    );
  }

kein Update ⟹ setState


widget

class Changer extends StatefulWidget {
  final Object data;

  Changer(this.data);

  @override
  _ChangerState createState() => _ChangerState();
}

class _ChangerState extends State<Changer> {
  final myOwnData = widget.data;
  final myContext = context;

  ...
}
  • widget.x greift auf Variable x im Widget zu
  • context liefert den Context des Widgets

State Management

Wohin mit dem State?

So tief wie möglich, so hoch wie nötig


state

TextField fügt zum ListView hinzu


Widget Tree - Element Tree - Render Tree

3 trees


Widget Lifecycle

lifecycle


? :

Container(
  child: input.isEmpty 
      ? Image.asset('assets/images/error.png')
      : Text('Yay'),
);
Flex(
  children: [
    if(isPortrait) Widget(...)

Theme

Text(
  'lorem ipsum',
  style: const TextStyle(
    color: Colors.blue,
    fontWeight: FontWeight.bold,
    fontSize: 16,
  ),
)

don't repeat yourself!

MaterialApp(
  theme: ThemeData(
    fontFamily: 'Quicksand',
    colorScheme: ColorScheme.fromSwatch(
      primarySwatch: Colors.blue,
      accentColor: Colors.amber,
    ),
  )
);
color: Theme.of(context).colorScheme.primary

Responsiveness

responsiveness


MediaQuery.of(context).
size // width/height
    .textScaleFactor
    .orientation // portrait/landscape
    .viewInsets // onscreen-Keyboard etc.

👍 Speichern für mehr Performance👍

@override
Widget build(BuildContext context) {
  final mediaQuery = MediaQuery.of(context);

Verwendung

...children: [
  if (mediaQuery.orientation == Orientation.landscape) MyWidget(),
  mediaQuery.size.width > 400 
      ? Row(...)
      :Widget,
  Text(
    'text',
    style: TextStyle(
      fontWeight: FontWeight.bold,
      fontSize: 20 * mediaQuery.textScaleFactor,
      color: Theme.of(context).colorScheme.primary,
    ),
  ),
],

LayoutBuilder


Adaptiveness

android-vs-ios


Revenue

android-vs-ios-revenue


Implementierung

Switch.adaptive(...) // Switch/CupertinoSwitch
Platform.isIOS
  ? Container()
  : FloatingActionButton()

👍 kapseln in MyWidget.adaptive() 👍


Navigation

stack

Top ist sichtbar


Simple Routing

Navigator.of(context).push(
  MaterialPageRoute(
  builder: (context) =>
      OtherPage(data),
  ),
);

Named Routes

class OtherPage extends StatelessWidget {
  static const route = '/other-page';

  @override
  Widget build(BuildContext context) {
    var data = ModalRoute
        .of(context)
        ?.settings
        .arguments as Data;
MaterialApp(
  // home: const CategoriesPage(),
  routes: {
    '/': (context) => Home(),
    OtherPage.route: (context) => OtherPage(),
  },
  onUnknownRoute: (settings) {
    return MaterialPageRoute(builder: (context) => ErrorPage());
  },
Navigator.of(context).pushNamed(
    OtherPage.route, arguments: arguments,);

Returning

push-pop


Tabs

static const _pages = [
  PageA(),
  PageB(),
];
int _selectedPageIndex = 0;

void _selectPage(int index) {
  setState(() => _selectedPageIndex = index);
}
Widget build(BuildContext context) {
  return Scaffold(
    body: _pages[_selectedPageIndex],
    bottomNavigationBar: BottomNavigationBar(
      onTap: _selectPage,
      currentIndex: _selectedPageIndex,
      items: const [
        BottomNavigationBarItem(icon: Icon(Icons.category)),
        BottomNavigationBarItem(icon: Icon(Icons.star)),],),

State again

state-problems

State forwarding ⟹ Kompletter Rebuild


$$UI = f(state)$$ state-ui


Provider

class DataProvider with ChangeNotifier {
  final items = <Data>[ ... ];
  
  void addItem(Data data) {
    ...
    notifyListeners();
  }

  Data findById( ... ) { ... }
}
@override
Widget build(BuildContext context) {
  return ChangeNotifierProvider(
    create: (context) => DataProvider(),
    child: MaterialApp( ... )
@override
Widget build(BuildContext context) {
  final dataProvider = Provider.of<DataProvider>(context, listen: true);
  final data = dataProvider.items;

Advanced

ListView/GridView recyclen Elemente
bzw wenn builder keinen Konstruktor aufruft

ChangeNotifierProvider.value(
  value: data, 
  child: ...,

Feingranularer

Widget build(BuildContext context) {
  return Consumer<Data>(
    builder: (context, data, child) => FooWidget(foo: data, child: child),
    child: NotChanging(), //---^ not rebuilt
  );
}