Gelebte Best Practices in der moguru: Dos und Don’ts in Flutter

Flutter, Googles UI Framework, verfolgt die Vision mit einer Codebasis nicht nur Android und iOS zu unterstützen, sondern auch Web- und Desktopanwendungen, welche nativ entwickelten Anwendungen in nichts nachstehen! 

Die eingesetzte Programmiersprache ist Dart, welche mit der Hot-Reload Funktionalität die Entwicklung enorm beschleunigt. Aktuell unterstützt Flutter Android und iOS, kompiliert in nativen Code und erreicht dadurch eine sehr hohe Performance: 60FPS und mehr sind dadurch kein Problem! 

Alles ist ein Widget und jedes Widget besteht wieder aus mehreren Widgets, so baut sich jede Flutter App aus einem Widget-Baum zusammen. Flutter nutzt dabei nicht die nativen Komponenten von iOS und Android, sondern liefert eine gigantische Variation an anpassbaren eigenen Widgets, wie Buttons, Listen, Schiebereglern etc. Diese werden dann auf ein Canvas immer wieder neu gerendert. Flutter ermöglicht aufgrund dieser Eigenschaften eine Crossplattform-Entwicklung von komplexen Apps in kürzester Zeit.

In der Softwareentwicklung haben sich viele Best Practices etabliert um qualitativ hochwertigen Code zu schreiben und eine hohe Wartbarkeit zu erreichen. Gleiches gilt natürlich auch für Flutter. Im folgenden haben wir einige unserer gelebten Do’s und Don’ts bei der moguru niedergeschrieben. Diese sind teilweise nicht Flutter spezifisch und können daher auch auf andere Sprachen und Technologien übertragen werden.

REST nutzen

Der defacto Standard um Informationen zwischen Frontend und Backend auszutauschen – REST – sollte natürlich auch bei der Entwicklung mit Flutter genutzt werden. Dabei ist es nicht dringend erforderlich, dass strikt der RESTfull Ansatz eingehalten wird. RESTless ist der richtige Begriff hier! Wichtig hierbei ist es die Prinzipien einzuhalten: Client-Server Architektur, Zustandslosigkeit, Caching, Einheitliche Schnittstellen und Mehrschichtige Systeme.

Naming Conventions

Klassen, Enums, Typdefinitionen und Extensions sollten immer in UpperCamelCase geschrieben werden:

class MainClass {}
enum MainData {}
typedef MyFunction = void Function(T value)
extension MyList<T> on List<T> {}

Libraries, Packages, Pfad-/Dateiangaben sollten immer in snake_case(lowercase_with_underscores) geschrieben werden:

import ‘package:app/screens/my_main_screen.dart’;

Variablen, Konstanten und Parameter sollten in lowerCamelCase geschrieben sein:

int myIntValue = 1;

Typen von Attributen in einer Klasse explizit angeben

Generell ist es besser den Typ einer Variable explizit anzugeben, wenn dieser vor der Laufzeit bereits bekannt ist. Dies gilt nicht nur in Flutter sondern auch in anderen Sprachen.

Do

int value = 0;
final String name = “John”;
const int timeOut = 5000;

Don’t

var value = 0;
final name = “John”;
const timeOut = 5000;

Type Checking vs Type Casting

Es ist sinnvoller den Typ zu prüfen, als diesen zu casten. Falls man einen falsch typisierte Variable versucht zu casten, kann hier eine Exception geworfen werden. Bei Flutter sollte daher lieber der is Operator statt as Operator verwendet werden:

Do

if (item is Car) item.color = “Blue”;

Don’t

(item as Car).color = “Blue”;

Benutze ?? und ?. Operatoren

Es bietet sich an ?? (if null) und ?. (null aware) Operatoren zu benutzt statt Variablen auf null zu prüfen und dann entsprechend zuzuweisen.

Do

v = a ?? b;



Don’t

if (a == null) {
v = b;
} else {
v = a;
}

Do

 v = a?.b;

Don’t

if (a != null) v = a.b;

Spread Collections benutzen

Wenn Bereits Daten in einer Collection vorhanden sind, ist es sinnvoll Spread Collections zu benutzen um lesbaren Code zu produzieren:

Do

var widgetsX =
  [WidgetA(), WidgetB(), WidgetC()];

var widgetsY = [WidgetD(), WidgetE()];

…

Column(
  children: <Widget>[
    ...widgetsX,
    ...widgetsY
  ]
)

Don’t

var widgetsX = 
  [WidgetA(), WidgetB(), WidgetC()];

var widgetsY = [WidgetD(), WidgetE()];

var widgetsZ = widgetsX.addAll(widgetsY);

…

Column(
  children: widgetsZ;
)

Benutze den Cascade Operator

Wenn man mehrere Operationen auf ein und das selbe Objekt ausführen möchte, bietet es sich an den Cascade Operator zu benutzen:

Do

Car car = Car()
..color = “Green”
..speed = 9000;

Don’t

Car car = Car();
car.color = “Green”;
car.speed = 9000;

Expression Funktionsrumpf nutzen

Wenn eine Funktion nur aus einem Ausdruck besteht, dann kann diese Funktion auch mit der => (Pfeil) Notation beschrieben werden:

Do

double get myValue => _myValue;

Don’t

double get myValue {
  return _myValue;
}

Widgets splitten

Wenn man setState() auf einen State aufruft, werden alle Widgets, welche sich unterhalb diesen Widgets im Widgetbaum befinden ebenfalls neu gebaut. Es ist daher sinnvoller setState() nur auf den Teilbaum aufzurufen, welcher sich für die UI tatsächlich verändern muss. Das heißt in unserem konkreten Beispiel kann man MyStatefulWidgetB updaten, wenn setState() aufgerufen wird, ohne dass MyStatelessWidgetA und MyStatefulWidgetC geupdatet werden müssen und dadurch auch weiter die Performance gesteigert werden kann.

class MyStatelessWidgetA extends StatelessWidget {
    @override
       Widget build(BuildContext context) {
         return Scaffold(
             body: Column(
                children: <Widget>[
                MyStatefulWidgetB(),
                MyStatefulWidgetC()
                ]
            )
        )
    }
}

class MyStatefulWidgetB extends StatefulWidget {
    …
    class MyStatefulWidgetB extends State<MyStatefulWidgetB> {
    …
    setState(() { … });
    …
    }
}

class MyStatefulWidgetC extends StatefulWidget {
    …
}

Nutze den ListView.builder für lange Listen

Wenn man lange oder sogar unendliche Listen nutzt, ist es ratsam den ListView.builder zu benutzen um die Performance zu verbessern. Das liegt daran, dass der Standard ListView Konstruktor die gesamte Liste auf einmal baut. Der ListView.builder erstellt die Liste erst, wenn der User runter scrollt, d.h. Flutter baut Widgets, erst wenn sie tatsächlich gebraucht werden.

Const in Widgets benutzen

Generell ist es Best Practice in Konstanten zu arbeiten. In Flutter kann dies auch auf Widgets verwendet werden. Das heißt, wenn ein Widget sich während der Laufzeit nicht ändert kann, dieses als Konstante deklariert werden:

Padding(
      padding: const EdgeInsets.all(8.0),
      child: Text("text"),
);

Dies sind einige Best Practices die wir bei unseren Crossplattform Projekten, insbesondere unserem großen Projekt rente.de leben und hiermit gerne weitergeben möchten damit jeder qualitativ hochwertigen und gut wartbaren Flutter Code schreiben kann.