Flutter und JSON Part 2

Im ersten Teil zu diesem Thema, den man hier findet, haben wir uns einen Code angesehen, der ein JSON Asset in unsere App einliest und mithilfe eines
ListView Widgets auf den Bildschirm bringt.

In diesem Post geht es darum diesen Code zu verbessern. Wer den ersten Teil nicht gelesen hat, sollte das nachholen. Ohne diese Info, wird es schwierig diesem Teil zu folgen. Da ich selbst am lernen von Flutter und Dart bin, ist das schreiben dieses Artikel nicht nur eine Wiederholung von etwas gelerntem, sondern dient auch der nochmaligen Reflektion :-)

Das Code Beispiel stammt aus einem YouTube Video von Zaiste, welches man hier findet (englisch).
Der komplette Code ist auch auf GitHub.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class _MyHomePageState extends State<MyHomePage> {

   List nachrichten = const [];

  Future loadNachrichtListe()async{

     var content = await rootBundle.loadString("assets/data/message.json");
     var collection = json.decode(content);
  
  setState(() {
   nachrichten = collection; 
  });
  }


  void initState(){
    loadNachrichtListe();
    super.initState();
  

var content


Der Code oben war so auch lauffähig. Aber er hat einige Schönheitsfehler. Konkret geht es um var content. var ist ein Variablen Typ von Dart der alles enthalten kann, einen Integer Wert, eine Zeichenkette etc. Dart ermittelt von sich aus, obwohl wir var implementiert haben, was für ein Typ Variable content nun tatsächlich ist.

Das heisst wenn wir in Visual Studio Code mit dem Mauszeiger über content gehen, wird uns die IDE anzeigen, das die Variable content vom Typ String ist. Man muss nicht, aber ich als lernender ändere also var content zu String content.

Bevor wir weiter machen, was für einen Sinn hat die Änderung, wenn es eh keine Rolle spielt und Dart ja schon weiß, das var content einen String enthält ?

Die Antwort: sollte man ein größeres Projekt starten, wird man irgendwann ziemlich viel Code haben, verteilt auf verschiedene Files. JETZT wissen wir, das var content einen String enthält, aber wissen wir es noch in 2 oder 3 Wochen ? Sollten wir mit anderen an dem Code arbeiten, können die sehen was für einen Typ die Variable var content haben sollte ?
Klar im Beispiel oben ist das einfach. In einem anderen Kontext kann es aber sehr schwierig werden, nach zu vollziehen, was genau für ein Typ eine var varialblexyz haben sollte. Ein geübter, sagen wir professioneller Programmierer kann wahrscheinlich mit der Problematik leben. Anfänger sollten das vermeiden, alleine um die Fehlersuche zu vereinfachen.


Aber weiter: var collection


Die nächste Ungenauigkeit oben ist var collection. Dart ermittelt den Typ von collection als dynamic, was nichts anderes heisst, als das Dart nicht genau weiß, was collection enthalten wird.
Das ist eigentlich doof, denn wenn Dart nicht weiß, was collection enthalten sollte, wie soll uns Dart dann einen Fehler anzeigen, wenn wir collection das Falsche zuweisen ? Genau, es kommt keine Warnung und wir sehen später, im Fehlerfall, das die App nicht funktioniert. Das ist erst recht..naja..doof.



1
var collection = json.decode(content);

Wir könnten jetzt natürlich auch List collection schreiben, was Dart auch akzeptiert. Ob es die richtige Liste ist, was für eine Liste es ist, das kann Dart nicht kontrollieren. Aber das möchten wir, da wir möglichst fehlerfreien Code schreiben möchten...

Mit rootBundle.loadString("assets/data/message.json"); laden wir JSON Objekte als eine Zeichenkette. Ob es nun diese Beispiel JSON Datei ist, oder später von einer realen API im Netz, wir werden und wir müssen ja vorher wissen, was für eine Strukturierung haben die JSON Daten, die wir da laden. Es liegt da nahe, das wir diese rohen JSON Daten in ein Dart Objekt überführen, bevor wir sie weiterverarbeiten:

Wir erinnern uns, wie unser JSON File ausgesehen hat:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
[ {
    "subject":"Meine erste Nachricht",
    "body": "Eu nulla pariatur labore consequat anim ullamco sint dolor officia."
  },
  {

    "subject":"Meine zweite Nachricht",
    "body": "Eu nulla pariatur labore consequat anim ullamco sint dolor officia."
  },
  {

    "subject":"Meine dritte Nachricht",
    "body": "Eu nulla pariatur labore consequat anim ullamco sint dolor officia."
  },
  {

    "subject":"Meine vierte Nachricht",
    "body": "Eu nulla pariatur labore consequat anim ullamco sint dolor officia."
  }
]

Die Schlüssel Elemente  sind dabei: "subject" und "body" .  Mit diesen Elementen können wir nun eine Klasse erstellen:


1
2
3
4
5
6
7
class Message{

  final String subject;
  final String body;

  Message(this.subject, this.body);
}

Sehr einfach, aber noch nicht komplett. Wir können jetzt statt wie zu Beginn var collection neu: List<Message> collection = json.decode(...) schreiben. Und statt diesem Code:

1
var nachricht = nachrichten[index];

können wir Message nachricht = nachrichten[index]; schreiben.

Diese Änderungen aber haben weitere Folgen. Wir können nun direkt auf die Schlüssel unseres JSON Objekts zugreifen. Aus diesem Code:



1
title: Text(nachricht['subject']),

Wird dieser Code:

1
title: Text(nachricht.subject),

Das gleiche müssen wir auch mit "body" machen.

Wo stehen wir jetzt ? Wir erhalten eine Fehlermeldung, wenn die Zeichenkette die wir dekodieren nicht vom Typ Message ist. Wir erhalten eine Fehlermeldung, wenn das Objekt, welches wir in nachricht speichern, kein Feld subject oder body hat. Im Code vom ersten Teil zu JSON war das noch nicht der Fall. Aber wir sind noch nicht am Ende. Mit den vorgenommenen Änderungen ist unser Code noch nicht lauffähig:



1
List<Message> collection = json.decode(content);

Der Code oben wird wieder eine Fehlermeldung erzeugen. Der Grund ist das json.decode(...) kein List<Message> Objekt zurückgibt, sondern ein dynamisches.
Die Lösung ist, das unsere Klasse die JSON Daten so vorbereitet, das wir sie problemlos weiter verarbeiten können. Das erreichen wir durch einen weiteren Konstruktor für die Klasse Message:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class Message{

  final String subject;
  final String body;

  Message(this.subject, this.body);

  Message.fromJason(Map<String,dynamic>json)
  : subject = json['subject'],
    body = json['body'];
}

Im Code oben haben wir jetzt 2 Konstruktoren. Der zweite Message.fromJason(...) weist der Klassen Variable subject, den Inhalt des Schlüssels, des JSON Objekts "subject" zu. Das gleiche geschieht bei body.
Wir ändern nun List<Message> collection wieder zu List collection. Aber noch ist mehr Arbeit zu tun.


1
List<Message> _messages = collection.map((json) => Message.fromJason(json)).toList();

Den Code oben fügen wir noch hinzu. Er definiert eine neue Variable _message. Anschliessend wendet er die .map Methode auf collection an. Die .map Methode erwartet von uns eine weitere Methode, die auf jedes einzelne Element von collection angewandt wird.
Im Code oben ist das der zweite Konstruktor der Klasse Message.  Der Konstruktor weist die JSON Werte  den gleichnamigen Klassen Variablen zu. Anschliessend wird dieses Message Objekt in eine Liste geschrieben. Das was jetzt noch zu tun bleibt, ist diese neue Variable einzusetzen:


1
2
3
4
 setState(() {
   nachrichten = _messages; 
  });
  }

Damit sind wir am Ende angekommen. Den kompletten Code gibt es hier ! Bis bald !

Kommentare

Beliebte Posts aus diesem Blog

Material Design in Flutter Teil 2

Dart Basic: Listen Part 1

Listen in Dart (2021): Part 1 List.filled List.empty und List.add