Flattern. Schlüssel! Wofür sind sie?


Der key ist in fast jedem Widget-Konstruktor zu finden, wird jedoch in der Entwicklung selten verwendet. Keys behalten den Status bei, wenn Widgets im Widget-Baum verschoben werden. In der Praxis bedeutet dies, dass sie nützlich sein können, um den Bildlaufort oder den Speicherstatus des Benutzers zu speichern, wenn sich die Sammlung ändert.


Dieser Artikel wurde aus dem folgenden Video angepasst. Wenn Sie lieber hören / sehen als lesen möchten, erhalten Sie im Video das gleiche Material.


Geheime Informationen über keys


Meistens ... brauchst du keine keys . Im Allgemeinen schadet das Hinzufügen nicht, dies ist jedoch auch nicht erforderlich, da sie einfach als neues Schlüsselwort oder als neue Typdeklaration auf beiden Seiten einer neuen Variablen erfolgen (ich spreche von Ihnen, Map<Foo, Bar> aMap = Map<Foo, Bar>() ).


Wenn Sie jedoch feststellen, dass Sie Widgets in der Sammlung hinzufügen, entfernen oder neu anordnen, die einen bestimmten Status enthalten und vom gleichen Typ sind, sollten Sie auf die keys achten!

Um zu demonstrieren, warum Sie beim Ändern einer Sammlung von Widgets keys benötigen, habe ich eine äußerst einfache Anwendung mit zwei farbenfrohen Widgets geschrieben, die beim Klicken auf eine Schaltfläche die Position ändern:



In dieser Version der Anwendung habe ich zwei zustandslose Widgets mit zufälliger Farbe ( StatelessWidget ) im Row und PositionedTiles-Widget mit dem Status ( StatefulWidget ), um die Reihenfolge der StatefulWidget zu speichern. Wenn ich unten auf die Schaltfläche FloatingActionButton klicke, ändern die Farb-Widgets ihre Position in der Liste korrekt:


 void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ StatelessColorfulTile(), StatelessColorfulTile(), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatelessColorfulTile extends StatelessWidget { Color myColor = UniqueColorGenerator.getColor(); @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding(padding: EdgeInsets.all(70.0))); } } 

Wenn wir jedoch unseren Farb-Widgets Status hinzufügen (sie zu StatefulWidget ) und die Farbe darin speichern, sieht es so aus, als ob nichts passiert, wenn wir auf die Schaltfläche klicken:



 List<Widget> tiles = [ StatefulColorfulTile(), StatefulColorfulTile(), ]; ... class StatefulColorfulTile extends StatefulWidget { @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Zur Erklärung: Der obige Code ist insofern fehlerhaft, als er den Farbaustausch nicht anzeigt, wenn der Benutzer auf die Schaltfläche klickt. Um diesen Fehler zu beheben, müssen Sie den farbigen StatefulWidget Widgets den key hinzufügen. Anschließend werden die Widgets wie gewünscht ausgetauscht:



 List<Widget> tiles = [ StatefulColorfulTile(key: UniqueKey()), // Keys added here StatefulColorfulTile(key: UniqueKey()), ]; ... class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); // NEW CONSTRUCTOR @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Dies ist jedoch nur erforderlich, wenn Sie Widgets mit Status in dem Teilbaum haben, den Sie ändern. Wenn der gesamte Teilbaum des Widgets in Ihrer Sammlung keinen Status hat, werden keine Schlüssel benötigt.
So! Alles in allem alles, was Sie wissen müssen, um keys in Flutter . Aber wenn Sie etwas tiefer in das Geschehen einsteigen wollen ...




Verstehen, warum keys manchmal notwendig sind


Du bist immer noch hier, oder? Dann kommen Sie näher und entdecken Sie die wahre Natur von Elementbäumen und Widgets, um ein Flatter-Magier zu werden! Wahahaha! Ha ha! Ha ha! Tut mir leid.


Wie Sie wissen, erstellt Flutter in jedem Widget das entsprechende Element. So wie Flutter einen Widget-Baum erstellt, erstellt er auch einen Elementbaum (ElementTree). ElementTree ist extrem einfach, es enthält nur die Typinformationen jedes Widgets und einen Link zu untergeordneten Elementen. Sie können sich ElementTree als das Grundgerüst Ihrer Flutter-Anwendung vorstellen. Es zeigt die Struktur Ihrer Anwendung, aber alle zusätzlichen Informationen finden Sie unter dem Link zum Quell-Widget.


Das Zeilen-Widget im obigen Beispiel enthält eine Reihe geordneter Slots für jedes seiner untergeordneten Elemente. Wenn wir die Farb-Widgets in Row neu anordnen, geht Flutter um ElementTree herum, um zu überprüfen, ob die Skelettstruktur der Anwendung identisch ist.



Die Validierung beginnt mit einem RowElement und geht dann zu den untergeordneten Elementen über. ElementTree überprüft, ob das neue Widget denselben Typ und key wie das alte hat. In diesem Fall aktualisiert das Element seinen Link zum neuen Widget. In der zustandslosen Version des Codes haben Widgets keinen key , daher überprüft Flutter einfach nur den Typ. (Wenn zu viele Informationen gleichzeitig vorhanden sind, sehen Sie sich das animierte Diagramm oben an.)


Unter ElementTree für Status-Widgets sieht es etwas anders aus. Nach wie vor gibt es Widgets und Elemente, aber es gibt auch einige Statusobjekte für Widgets, in denen Farbinformationen und nicht in den Widgets selbst gespeichert sind.



Bei farbigen StatefulWidget Widgets ohne key geht Flutter beim Ändern der Reihenfolge von zwei Widgets um ElementTree herum, überprüft den Typ des RowWidget und aktualisiert den Link. Anschließend überprüft das Farb-Widget-Element, ob das entsprechende Widget vom gleichen Typ ist, und aktualisiert den Link. Das gleiche passiert mit dem zweiten Widget. Da Flutter ElementTree und den entsprechenden Status verwendet, um zu bestimmen, was tatsächlich auf Ihrem Gerät angezeigt werden soll, scheinen aus unserer Sicht die Widgets nicht ausgetauscht worden zu sein!



In der festen Version des Codes in farbigen Widgets mit Status im Konstruktor habe ich die key definiert. Wenn wir nun die Widgets in Row ändern, stimmen sie nach Typ wie zuvor überein, aber die key für das Farb-Widget und für das entsprechende Element in ElementTree sind unterschiedlich. Dies führt dazu, dass Flutter diese Elemente der Farb-Widgets deaktiviert und die Verknüpfungen zu ihnen in ElementTree entfernt, beginnend mit dem ersten, der nicht mit dem key übereinstimmt.



Anschließend sucht Flutter im ElementTree mit dem entsprechenden key nach den Widgets im Zeilenelement. Wenn es übereinstimmt, wird ein Link zum Widget-Element hinzugefügt. Flattern tut für jedes Kind ohne Link. Jetzt zeigt Flutter an, was wir erwarten. Die Farb-Widgets ändern ihre Position, wenn ich auf die Schaltfläche klicke.


Daher sind keys nützlich, wenn Sie die Reihenfolge oder Anzahl der Widgets mit dem Status in der Sammlung ändern. In diesem Beispiel habe ich die Farbe gespeichert. Oft ist der Zustand jedoch nicht so offensichtlich. Eine Animation abspielen, Benutzereingaben anzeigen und durch einen Ort scrollen - alles hat einen Status.




Wann sollte ich keys ?


Kurze Antwort: Wenn Sie der Anwendung keys hinzufügen müssen, sollten Sie diese oben im Widget-Teilbaum mit dem Status hinzufügen, den Sie speichern möchten.


Ein häufiger Fehler, den ich gesehen habe, ist, dass die Leute denken, dass sie den key nur für das erste Widget mit Status definieren müssen, aber es gibt Nuancen. Glaubst du mir nicht? Um zu zeigen, in welchen Problemen wir uns befinden könnten, habe ich meine Farb-Widgets in Padding Widgets eingeschlossen und die Tasten für die Farb-Widgets belassen.


 void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { // Stateful tiles now wrapped in padding (a stateless widget) to increase height // of widget tree and show why keys are needed at the Padding level. List<Widget> tiles = [ Padding( padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(key: UniqueKey()), ), Padding( padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(key: UniqueKey()), ), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Jetzt erhalten Widgets auf Knopfdruck völlig zufällige Farben!



So sehen der Widget-Baum und ElementTree mit den hinzugefügten Padding Widgets aus:



Wenn wir die Positionen von untergeordneten Widgets ändern, sieht der Übereinstimmungsalgorithmus zwischen Elementen und Widgets eine Ebene im Elementbaum aus. Im Diagramm sind die Kinder der Kinder abgedunkelt, so dass nichts von der ersten Ebene ablenkt. Auf dieser Ebene stimmt alles richtig überein.



Auf der zweiten Ebene stellt Flutter fest, dass der key Farbelements nicht mit dem key Widgets übereinstimmt. Daher wird dieses Element deaktiviert, verworfen und alle Links zu ihm entfernt. keys in diesem Beispiel verwendeten keys sind LocalKeys . Dies bedeutet, dass Flutter beim Abgleichen eines Widgets mit Elementen nur auf einer bestimmten Ebene des Baums nach keys sucht.


Da er das Farb-Widget-Element auf dieser Ebene mit dem entsprechenden key nicht finden kann, erstellt er ein neues und initialisiert einen neuen Status, wodurch das Widget in diesem Fall orange wird!



Wenn wir keys für Padding Widgets definieren:


 void main() => runApp(new MaterialApp(home: PositionedTiles())); class PositionedTiles extends StatefulWidget { @override State<StatefulWidget> createState() => PositionedTilesState(); } class PositionedTilesState extends State<PositionedTiles> { List<Widget> tiles = [ Padding( // Place the keys at the *top* of the tree of the items in the collection. key: UniqueKey(), padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(), ), Padding( key: UniqueKey(), padding: const EdgeInsets.all(8.0), child: StatefulColorfulTile(), ), ]; @override Widget build(BuildContext context) { return Scaffold( body: Row(children: tiles), floatingActionButton: FloatingActionButton( child: Icon(Icons.sentiment_very_satisfied), onPressed: swapTiles), ); } swapTiles() { setState(() { tiles.insert(1, tiles.removeAt(0)); }); } } class StatefulColorfulTile extends StatefulWidget { StatefulColorfulTile({Key key}) : super(key: key); @override ColorfulTileState createState() => ColorfulTileState(); } class ColorfulTileState extends State<ColorfulTile> { Color myColor; @override void initState() { super.initState(); myColor = UniqueColorGenerator.getColor(); } @override Widget build(BuildContext context) { return Container( color: myColor, child: Padding( padding: EdgeInsets.all(70.0), )); } } 

Flutter bemerkt das Problem und aktualisiert die Links korrekt, wie in unserem vorherigen Beispiel. Die Ordnung im Universum wird wiederhergestellt.





Welche Art von Key soll ich verwenden?


Flutter-APIs gaben uns die Wahl zwischen mehreren Key . Welche Art von key Sie verwenden sollten, hängt davon ab, was die Unterscheidungsmerkmale von Elementen sind, die keys benötigen. Sehen Sie sich die Informationen an, die Sie in den jeweiligen Widgets speichern.


Stellen Sie sich die folgende Aufgabenanwendung [1] vor, in der Sie die Reihenfolge der Elemente in der Aufgabenliste basierend auf der Priorität ändern und anschließend löschen können.



ValueKey
In diesem Fall können wir erwarten, dass der Text des Absatzes über die Implementierung dauerhaft und eindeutig ist. Wenn ja, dann ist dies wahrscheinlich ein guter Kandidat für ValueKey , wo der Text "Wert" ist.


 return TodoItem( key: ValueKey(todo.task), todo: todo, onDismissed: (direction) => _removeTodo(context, todo), ); 

Objektschlüssel
Alternativ können Sie die Adressbuchanwendung verwenden, in der Informationen zu jedem Benutzer aufgelistet sind. In diesem Fall speichert jedes untergeordnete Widget eine komplexere Kombination von Daten. Jedes der einzelnen Felder, z. B. Name oder Geburtstag, kann mit einem anderen Eintrag übereinstimmen, die Kombination ist jedoch eindeutig. In diesem Fall ist ObjectKey höchstwahrscheinlich die beste ObjectKey .



Uniquekey
Wenn Sie mehrere Widgets in der Sammlung mit demselben Wert haben oder wirklich sicherstellen möchten, dass sich jedes Widget von allen anderen unterscheidet, können Sie UniqueKey . Ich habe UniqueKey in der Beispielanwendung zum Wechseln der Farben verwendet, da wir keine anderen konstanten Daten hatten, die in unseren Widgets gespeichert würden, und wir nicht wussten, welche Farbe das Widget beim Erstellen haben würde.


Eine Sache, die Sie jedoch nicht als key möchten, ist eine Zufallszahl. Jedes Mal, wenn ein Widget erstellt wird, wird eine neue Zufallszahl generiert und Sie verlieren die Konsistenz zwischen den Frames. In diesem Szenario dürfen Sie überhaupt keine keys !


PageStorageKeys
PageStorageKeys sind spezielle keys , die den aktuellen Status des PageStorageKeys enthalten, damit die Anwendung ihn zur späteren Verwendung speichern kann.



Globalkeys
Es gibt zwei Optionen für die Verwendung von GlobalKeys : Sie ermöglichen Widgets, die GlobalKeys an einer beliebigen Stelle in der Anwendung zu ändern, ohne den Status zu verlieren, und können verwendet werden, um auf Informationen zu einem anderen Widget in einem völlig anderen Teil des Widget-Baums zuzugreifen. Als Beispiel für das erste Szenario können Sie sich vorstellen, dass Sie dasselbe Widget auf zwei verschiedenen Bildschirmen GlobalKey möchten, aber mit demselben Status, damit die GlobalKey gespeichert werden, verwenden Sie GlobalKey . Im zweiten Fall kann es vorkommen, dass Sie das Kennwort überprüfen müssen, aber keine Statusinformationen für andere Widgets in der Baumstruktur freigeben möchten. GlobalKeys kann auch zum Testen nützlich sein, indem der key um auf ein bestimmtes Widget zuzugreifen und Informationen über dessen Status anzufordern.



Oft (aber nicht immer!) GlobalKeys bisschen wie globale Variablen. Oft können sie durch InheritedWidgets oder etwas wie Redux oder die BLoC-Vorlage ersetzt werden.




Kurzer Abschluss


Verwenden Sie im Allgemeinen Keys wenn Sie den Status zwischen Widget-Teilbäumen beibehalten möchten. Dies geschieht am häufigsten, wenn Sie die Sammlung von Widgets desselben Typs ändern. Platzieren Sie den key oben im Widget-Teilbaum, den Sie speichern möchten, und wählen Sie den key basierend auf den im Widget gespeicherten Daten aus.


Herzlichen Glückwunsch, Sie sind jetzt auf dem Weg, ein Flatter-Magier zu werden! Oh, ich sagte ein Zauberer? Ich meinte den Zauberer [2], wie denjenigen, der den Anwendungsquellcode schreibt ... was fast genauso gut ist. ... fast.


[1] Inspiration zum Schreiben des hier erhaltenen To-Do-Anwendungscodes
https://github.com/brianegan/flutter_architecture_samples/tree/master/vanilla
[2] Der Autor verwendet das Wort sorcerer und fügt später vor dem sourcerer einen zusätzlichen Buchstaben sourcerer

Source: https://habr.com/ru/post/de446050/


All Articles