Mein Eindruck von Baldur's Gate: Siege of Dragonspear
Monday, 5. December 2022
Siege of Dragonspear ist eine Erweiterung, die in der Enhanced Edition des ersten Baldur's Gate die Storylücke zwischen Teil eins und zwei füllen soll. Sie hinterließ bei mir einen schließlich doch negativen Eindruck.
Gelungener Anfang
Auf der einen Seite ist die Ausgangslage nicht schlecht. Nach den Geschehnissen im Hauptspiel erklärt die Stadt Baldur's Gate den Spielercharakter zu ihrem geschätzten Helden. Ein nicht uninteressanter erster Dungeon führt in diese Rolle ein, beschreibt dabei direkt Imoens Weg zur Magierin (sie war ja in BG1 ein Dieb, in BG2 dann eine Doppelklasse Dieb/Magierin) und ein Zerfallen der alten Heldengruppe durch Wegfall ihrer alten Existenzberechtigung. Kaum zurück bedroht ein Kreuzzeug die Stadt, dem eine eigene Armee entgegengeschickt werden soll – begleitet von der eiligst wieder zusammengerufenen Heldentruppe des Spielers.
Schon bei diesen Anfangsszenen wird klar, dass sich die Erweiterung ein paar Freiheiten vom Grundspiel nehmen und es übertrumpfen will. Teils schlicht, indem Techniklimitierungen aufgelöst wurden. Die Karten sind nun nicht immer gleich (über-)groß, sondern oft kleiner, sodass sie zu durchsuchen weniger nervig wird. Doch auch wenn klein, sind sie manchmal beeindruckend übervoll mit NPCs. In den Städten sind das zumeist Statisten, mit denen man nicht interagieren kann. Später sind es Verbündete und Gegner, was zu echten Massenschlachten führt, eine wirkliche Neuerung selbst zu Teil 2.
Einige andere Modernisierungen helfen dem alten Spielkonzept zusätzlich: Dass abgelehnte Begleiter im Lager auf eine neue Rekrutierung warten, dass es für überschüssige Gegenstände eine sichere Schatztruhe gibt. Die ganzen Interaktionen mit den Story-NPCs in der Stadt und während dem Kreuzzug wirken freier, wirkmächtiger als die im ersten Teil noch sehr begrenzten und immer auf die Abenteurerrolle beschränkten Interaktionen. Schnell wirkt Siege of Dragonspear so insgesamt deutlich moderner als Teil eins, gar stärker noch als Teil zwei.
Die Gefahren des Übertrumpfens
Doch andererseits sind manchmal solche Steigerungen kombiniert mit echten Macken.
So ist die Hauptgeschichte übertrieben bombastisch. Sie passt nicht zur kompakten Story des ersten Teils, passt nicht zum langsamen Beginn des zweiten, führt die Gruppe in Gebiete in denen sie nicht verloren hat. Der Kampf geht dabei gegen Gegner, gegen die keine Chance bestünde, wären sie universumsgetreu skaliert.
Treue zum Quellmaterial ist sowieso ein Problem: Wohl im Versuch, die Erweiterung dichter zu machen und mit dem zweiten Teil zu verknüpfen, werden in Begegnungen mit Irenicus (die schon alleine zwecks Konsistenz mit BG2 nie hätten stattfinden dürfen!) Geschehnisse um den Bhaal-Hintergrund eingeführt, die der Geschichte des Nachfolgers widersprechen. Mindestens aber Spoiler sind.
Die Massenschlachten sind cool, aber sie sind auch frustrierend, weil zielgerichtetes Handeln – und als Konsequenz, zu gewinnen – in ihnen nicht einfach ist. Bei der ersten Massenschlacht kann sogar ein nicht vom Spieler gesteuerter potentieller Begleiter einfach sterben, je nach vorheriger Entscheidung weitab vom Bildausschnitt des Spielers, was ich erst durch seine herumliegende Ausrüstung bemerkte.
Genauso führt wohl dieser Drang, die alten Spiele zu übertrumpfen, das Spiel dazu einen mächtigen Drachen als optionalen Gegner zu präsentieren – aber der ist unbesiegbar für jede Gruppe, selbst bei erreichter momentaner Maximalstärke. Daher gibt es einen Trick, mit dem der Drache doch getötet werden kann, der wird aber im Spiel nie auch nur angedeutet und muss online nachgelesen werden. Wenn der Trick nötig ist, warum wird er dann nicht verraten? Wenn er nicht nötig ist, warum ist er dann im Spiel? Wenn man solche Fragen stellen muss, warum ist dann der ganze Drache da?
Handwerkliche Fehler
Kombiniert ist das mit handwerklichen Fehlern, die weder das Hauptspiel noch der Nachfolger so je gemacht hätten. Die haben dann nichts mehr mit übertrumpfen zu tun, sondern wären in jedem Kontext verkehrt.
Besonders auffällig: Die Hauptstory ist eine Idiotengeschichte – würden sie sich einmal hinsetzen und über das Problem reden wäre das Problem gelöst, aber das darf nicht geschehen, daher hört künstlich niemand zu. Wichtige Storytreiber verhalten sich wie Idioten, daher der Name. BG1 hatte in der Richtung auch ein paar Tendenzen mit der Handhabung der Doppelgänger, aber die Grundstory war solide. Bei BG2 ist sie über jeden solche Zweifel erhaben, die Handlungen und die Motivation von Irenicus sind klar und konsistent, genauso der allgemeine Verlauf der Geschichte.
Es gibt bei der Geschichte wieder einen Endgegner. In allen anderen Baldur's Gate ist davor ein ruhiger Moment, in der gespeichert und die Gruppe mit Tränken und Zaubern gestärkt werden kann. Nicht so hier, vor dem Endkampf gibt es andere Kämpfe und eine fremdgesteuerte Fahrt auf einem Aufzug, also ein Zeitlimit. So könnten Spieler in Sackgassen rennen.
Auch in anderen Aspekten läuft das Spiel wie auf Schienen. Die Story gaukelt einige Entscheidungen vor – ich durfte aber dem Gamestar-Test entnehmen, dass sie nicht echt sind. So wird über die Auslieferung des Spielercharakters entschieden, wobei man der Idee zustimmen oder ihr widersprechen kann, den Ausgang der Entscheidung beeinflusse das nicht. Dass die Haupthandlung linear ist: Geschenkt, das war bei Baldur's Gate immer so, auch wenn dort das Zurückreisen möglich war, aber gut. Aber solche Einflussmöglichkeiten anbieten, aber nicht umsetzen? Das geht nicht.
Manche der Gespräche und Begegnungen sind besser gelungen. Ich fand zum Beispiel die Goblinschamanin und ihre Interaktionen nicht verkehrt. Andere sind unverzeihlich fehl am Platz, wie eine Trans-Priesterin als NPC, die nach einem Quest diese ihre darauf basierende Lebensgeschichte der Spielergruppe erzählt und die dann klatschen darf. Das in einer Welt der Götter und Magie, in der ein verfluchter Geschlechtswechsel-Gürtel einer der ersten findbaren magischen Gegenstände ist, ein Geschlechtswechsel komplett normal ist. Es hier zu etwas anderem zu machen ist peinlich und störend.
Und klar, auch die Originale haben diese Lücke gelassen, aber Thema peinlich: Wenn man schon ein Spiel um eine Handlungslücke strickt, wäre es dann nicht gut sie auszufüllen? SoD macht das nicht, stattdessen ist die Gefangennahme wieder ein unerklärt gelungener Überfall im Wald. Welch krasser Gegensatz zur Stärke der Gruppe im ersten Teil das schon war, wie noch unpassender es zu den Geschehnissen in dieser Erweiterung wirkt!
Ein zu negatives Bild?
Auch wenn das jetzt viel negatives war, so ganz verkehrt ist Siege of Dragonspear doch nicht.
Es ist zum einen schlicht neues Material in der alten Engine und dem alten Universum, was sich oft toll anfühlt, weil es Spaß macht zwischen all den vertrauten Elementen neues zu entdecken. Dabei sind die Quests und Kämpfe qualitativ deutlich über dem, was früher von Moddern für Baldur's Gate abgeliefert wurde. Und man merkt ja auch die Experimentierfreude, auch, dass hier eine Verneigung vor dem Grundspiel geschaffen werden sollte. Nur doof, dass ein in Teilen das Grundspiel übertrumpfendes (bei den Massenschlachten z.B.), storytechnisch komplett verwirrtes und mit handwerklichen Fehlern gespicktes Spiel so gar nicht in die anvisierte Lücke passt, ausgerechnet in der Rolle als Mittler zwischen BG1 und BG2 arg fehlplatziert ist. Das Gegenteil hätte es gebraucht: Eine dezente, konservative, mit einem Maximum auf Konsistenz in Ton und Handlung bedachte und handwerklich perfekte Erweiterung, die mit einem geschickten Dreh das Original mit seinem Nachfolger verbindet.
Siege of Dragonspear ist von diesem Ideal leider zu weit entfernt.
Linksammlung 48/2022
Friday, 2. December 2022
Diese Woche fand ich besonders erwähnensert:
In concerns about Trustcor wurde die Zukunft einer Web-Zertifikatsstelle diskutiert, die jetzt tatsächlich aufgrund dieser Bedenken aus den Browsern als Autorität entfernt wird. Es ist eine wilde Story um Verwicklungen mit Abhörsoftware, unbekannten Firmenbesitzern im Hintergrund und Verbindungen zur NSA.
Verfassungsschutz: Wer delegitimiert hier wen? Die Frage zeigt den schmalen Grat der vermeintlichen Verfassungsschützer, wenn Regierungskritik verfolgt werden soll. Wobei der Ansatz angesichts der extremen Regierungskritik aus dem AFD-Nazispektrum ja erstmal sinnvoll scheint, weil sich die Kritik dort ja tatsächlich mit Verfassungsfeindlichkeit mischt (via).
Letzte Woche verlinkte ich OKSolar, diesmal will ich das in meinen Augen noch besser gelungene selenized verlinken. Bei OKSolar wurde der Kontrast der Textfarben angeglichen, bei selenized wurde stattdessen der Kontrast generell erhöht. Das Ergebnis erscheint mir tatsächlich besser lesbar, besser als OKSolar und als solarized natürlich auch. Jetzt fehlt nur noch ein Mix aus OKSolar und selenized und wir hätten die perfekte Version von solarized.
Die Mastodon-Optimierungen der kanoa.de-Instanz erklären schön, welche Herausforderungen das Hosten solcher Software hat. Es hakt auch lehrreich ein paar Stichpunkte ab, die bei jedem nicht ganz kleinem Serversetup bedacht werden sollten: Caching und Einstellen der Datenbankverbindungen beispielsweise.
Lakritze: Fazer Salmiakki Cola-Ginger
Wednesday, 30. November 2022
Als ich in Helsinki war habe ich mir eine breite Auswahl an Lakritzbonbons gekauft. Die möchte ich nun ab und an kurz ins Blog bringen, als unregelmäßige Serie. Heute: Fazer Salmiakki Cola-Ginger.
Das schmeckte besser als erwartet, wobei ich mit etwas fürchterlichem rechnete. Doch die Mischung funktioniert erstaunlich gut. Der Ingwer-Geschmack ist da, aber nicht übermächtig. Die Cola ist auch klar herausschmeckbar und angenehm süß. Die Lakritze ist nur leicht im Hintergrund, aber noch präsent, sodass es nicht ganz ein Zuckerbonbon wird.
Wertung: Hätte ich mehr von kaufen sollen.
Reacher
Monday, 28. November 2022
Die Amazonserie sollte einfach nur das kostenlose Prime-Abo füllen, in das ich für einen nicht anders bewerkstelligbaren Amazonkauf gerutscht war. Nur solide mittelmäßige Unterhaltung braucht es dafür, genau das dürfte hier doch geboten werden, ähnlich wie bei den okayen (aber eben nicht guten) Filmen. Da schockte mich die sehr positive IMDB-Bewertung (~8.0) etwas, die auf eine viel bessere Serie verwies.
Ich würde behaupten, Mittelmäßigkeit zu erwarten war angemessener als die hohe Bewertung. Reacher startet immerhin geschickt mit einem Verweis auf die Filme, wenn der (nun hünenhafte) Ex-Militär Jack wieder in einem amerikanischem Diner verhaftet wird. Dann beginnt eine Verschwörungsstory mit vielen Actionszenen. Beides ist teils sehr brutal, beides schwankt in seiner Qualität. Stellenweise sind die Actionszenen super, unzerschnitten, brutal und realistisch – dann explodiert ein Haus und Jack läuft direkt nach der Explosion raus, gibt es wieder die Tom-Cruise-typische Scientology-Auferstehungsszene unter Wasser, können die Protagonisten durch dichten Feuerqualm laufen ohne auch nur Anzeichen einer Rauchvergiftung, läuft Jack so stocksteif eine Straße entlang, dass mehr als Vorwärtszulaufen von ihm zu erwarten absolut unrealistisch erscheint. Die Verschwörungsstory ist fesselnd genug um die Staffel zuende schauen zu wollen, gibt den Protagonisten überzeugende Motivation und wirkt durchaus gefährlich – dann sind Teile von ihr von Anfang an vorhersehbar, ist das Handeln so mancher Person völlig überzogen, gibt es nicht so viele Psychopathen auf der ganzen Welt wie in dieser Kleinstadt.
Die Besetzung gefiel mir. Alan Ritchson spielt Reacher mit einem ähnlich limitierten Repertoire wie Tom Cruise, gibt ihm aber als Bodybuilder (der natürlich die ganze Serie über keine Minute trainieren muss) eine andere Präsenz. Die übertriebene Intelligenz seiner Figur scheint dazu null zu passen, das macht sie aber eher interessant. Malcolm Goodwin wiederholt seine Rolle aus iZombie, ich mochte ihn hier wie dort, er wirkt sympathisch und kann seine Szenen tragen. Schließlich ausgerechnet die im Vergleich winzige Willa Fitzgerald als Reachers mögliche Herzensdame zu platzieren hat was, weil die Story ihr auch ausreichend eigenen Charakter und Fähigkeiten verleiht. Die Bösewichte dagegen bleiben komplett blass.
Aber das passt dann ja auch, interessante und mehr als gerade noch kompetent gespielte Gegenspieler zu erwarten wäre zu viel gewesen. Es ist eben doch nur solide, etwas überzogen brutale und auf IMDB massiv überbewertete Unterhaltung. Wieder: Kann man schauen, muss man nicht unbedingt, ich fühlte mich aber durchaus unterhalten.
Linksammlung 47/2022
Friday, 25. November 2022
Das große Thema der Woche war natürlich die Veröffentlichung der neuen Serendipity-Version. Ansonsten fand ich diese Woche noch erwähnenswert:
Es sei A Rocky Launch: Gamers Are Not Buying Nvidia's RTX 4080. Man kann nur hoffen, dass die Kaufzurückhaltung existiert und Nvidia wehtut, sonst wird der Preis für neue Hardware immer weiter steigen.
Die neue Version des Ruby-Webframeworks wurde veröffentlicht, Hanami 2.0: Better, Faster, Stronger. Der Ansatz mit den alternativen Gems als Grundlage gefällt mir. Alternativ meint hier alternativ zu Rails, aus dessen Umfeld normalerweise die populären Libraries kommen.
Solarized ist ein von mir gern genutztes Farbschema für den Editor, davon gibt es jetzt mit OKSolar eine weitere Variante. Bei ihr wurde versucht, mittels einem dafür geeigneten Farbmodell den Kontrast der verschiedenen Textfarben anzugleichen.
Ein ganz anderes Spiel mit Farben ist das Named Colors Wheel, bei dem nur benannte HTML-Farben verwendet wurden. Sieht erstaunlich gut aus; Es könnte eine angenehme Disziplinübung sein, die Farbauswahl bei einem Webprojekt mal hierdrauf zu beschränken.
Twitters Untergang hat unerwartete Auswirkungen. Mastodons zunehmende Popularität war ja fast zu erwarten, aber mit Tumblr Gets the Last Laugh hätte ich nie gerechnet.
Serendipity 2.4.0 ist draußen (das stabile Release für PHP 8.0)
Wednesday, 23. November 2022
Hier lief schon eine Weile die Beta der nun als Version 2.4.0 fertiggestellten neuen Serendipityversion, die jetzt auch hier der neuen Version weichen musste.
Hauptthema PHP 8
Was hat sich seit der Beta getan? Ein paar Beispiele:
Stephan hatte erkannt, dass das automatische Speichern des Editorinhalts kaputt war. Der Fix folgte. Matthias reparierte einen Fehler im Installer. Markus beschleunigte die Permalinkerstellung. Hanno machte die Artikelseite Spec-konformer. Mich nervte, dass beim Speichern von Templatekonfigurationen dessen Cache nicht immer neu erstellt wurde, und änderte das.
Aber der Fokus lag klar auf dem PHP-Support, wozu es auch seit Betabeginn noch einige Fixes gab. Im Kern und bei den Plugins.
Denn PHP 7.4 wird bei vielen Hostern nun abgeschaltet, deswegen auch das Release jetzt. Wir hätten ansonsten länger warten können und sicher noch mehr Bugs gefunden, nach Fehlerberichten mehr Plugins zum Laufen gebracht. Aber nun ging Warten nicht mehr und es ist auch gut, dass irgendwann der Zahn gezogen wird.
Führt euch bitte kurz vor Augen, was das für ein Projekt war und damit, was das für ein Release ist. PHP 8.0 hat vielen alten PHP-Code invalid gemacht, als altes Projekt besteht Serendipity zu 100% aus alten Code. Praktisch jede Codedatei schmiss nun Warnungen, es war anfangs erschreckend überfordernd. Im Kern wurden schließlich in einer gemeinsamen Anstrengung alle solchen Vorkommnisse angepasst, aber bei den Plugins war das illusorisch: Weder kennen wir alle Plugins, noch haben auch nur alle Spartacus-Plugins aktive Maintainer (hier wird Hilfe gebraucht!). Daher werden diese zumeist irrelevanten Warnungen nun unterdrückt (nur bei Beta und stabilen Versionen von Serendipity), was allerdings bei einem Serendipity-Teammitglied nicht zuverlässig funktioniert. Wenn es funktioniert macht es automatisch viele Plugins wieder kompatibel, ohne störende Warnmeldungen.
Allerdings hatte PHP 8.0 auch komplett inkompatible Änderungen, z.B. wenn von PHP-Kernfunktionen die Reihenfolge der Parameter geändert wurde. Das könnte gerade noch manche weniger populäre Plugins betreffen, die trotz der langen Betalaufzeit nicht getestet wurden und dann echte Fehler werfen. Da hilft nur Fehler melden, Fehler reparieren oder das Plugin zu löschen.
Ich wünsche viel Spaß mit der neuen Version und viel Erfolg beim Upgrade. Aktualisiert alle Plugins, deaktiviert das Kommentar-Seitenleistenplugin (wegen diesem Bug), habt während des Upgrades PHP 7.3 oder PHP 7.4 am Laufen. Das Upgrade sollte kein Problem sein (außer vll, wenn MySQL benutzt wird und die Beta noch nicht lief, wegen der utf8mb4-Umstellung, da braucht es dann ein Datenbankbackup), kritischer wird der dann folgende Test mit PHP 8.0. Wenn es dabei Probleme gibt hilft das Forum, aber bei inkompatiblen Plugins sollte wer kann die Ärmel hochkrempeln und sie (per Pull-Request im Github-Repo) für alle reparieren.
Tatsächlich freu ich mich riesig, dass dieses Release nun erschienen ist und auch über die drumherum gesehene Projektaktivität :) Auf dieser Grundlage wird das nächste Release (für PHP 8.1?) wohl wesentlich einfacher werden.
Linksammlung 46/2022
Friday, 18. November 2022
Diese Woche fand ich besonders erwähnenswert:
Ich bin nicht ganz sicher, ob ich den Ansatz in Resolving the Great Undo-Redo Quandary richtig verstehe. Aber Redos nicht mehr verlieren zu können ist verlockend, generell ein faszinierendes Usability-Problem.
Die Schilderung in Warum Starbuntu klingt toll. Die Distro scheint einen Test wert zu sein und die beschriebenen Bugs einfach selbst anzugehen ist eine bewundernswerte Initiative. Toll auch, dass dem ganzen in meinem Sinne ein eigener Desktop zusammengestellt wurde.
Estudiantes mexicanos denuncian agresiones racistas en Alemania ist mal wieder ein Thema, anlässlich dem anderswo ausführlich über Deutschland diskutiert wird, was es hier aber nichtmal in die Nachrichten schafft. Mexikanische Studenten erleben Naziangriffe in Cottbus, die Polizei unternimmt nichts, daher haben sie mexikanische Stellen eingeschaltet und das landete dann in der mexikanischen Presse. Es ist furchtbar, dass der Osten so naziverseucht ist, dass man den Studenten nur raten kann schnellstmöglich von dort wegzugehen.
Wer auch RSS-Feeds abonniert sollte die RSS-Bridge im Blick haben, die im verlinkten Artikel kurz vorgestellt wird. Die Kommentare erwähnen Alternativen.
Unverstelltes Routing in Flutter: NamedRoutes mit Animationen
Tuesday, 15. November 2022
Wie man in Flutter von einer Seite zur nächsten wechselt ist nicht ganz einfach, obwohl es eigentlich doch ganz simpel ist. Der Navigationsaufruf ist schnell geschrieben. Doch primär definiert man keine Seiten, sondern Widgets, und ob die jetzt alleine angezeigt werden oder nur als Teil eines anderen Widgets hängt einzig von ihrer Verwendung ab – schon das ist verwirrend. Dazu gibt es nicht den einen Weg, das Routing aufzusetzen, sondern viele: Das Flutter-Projekt selbst kennt mit dem Navigator (1.0), dem Router (2.0) und jetzt mit dem go_router (2.0+ ?) direkt drei offizielle Möglichkeiten, wobei sich hinter dem Navigator gleich mehrere Möglichkeiten verbergen und der Router 2.0 komplett vage ist. Dazu kommen die vielen Routing-Plugins auf pub.dev, alle mit ihren eigenen Vor- und Nachteilen.
Dieser Artikel wird nicht alle diese Möglichkeiten vorstellen. Stattdessen zeige ich einen Weg, wie man völlig ohne Plugins strukturiert die Routen anlegen und ihre Übergänge animieren kann. Dazu gehört dieses Git-Repo, in dem der Code komplett nachvollzogen werden kann.
Das Grundlagenbeispiel
Ich werde hier mit einer einfachen Flutter-Anwendung mit einer main.dart und drei Widgets view[123].dart benutzen. Die drei Widgets sollen jeweils als eigene Seiten aufgerufen werden. Sie sind so definiert:
import 'package:flutter/material.dart'; class View2 extends StatelessWidget { const View2({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('View 2')), body: const Center( child: Text('View 2'), ), ); } }
Und der Startbildschirm zeigt drei Buttons untereinander in einer Reihe:
class MyHomePage extends StatelessWidget { final String title; const MyHomePage({super.key, required this.title}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text(title)), body: Column( crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ Center( child: Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () => null, child: const Text('View 1')), ), ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () => null, child: const Text('View 2')), ), Padding( padding: const EdgeInsets.all(8.0), child: ElevatedButton( onPressed: () => null, child: const Text('View 3')), ), ], ), ); } }
v0.1: Simple NamedRoutes
Diese drei Buttons sollen nun jeweils zu ihrem Widget navigieren.
Schauen wir uns also erstmal an, wie einfache NamedRoutes funktionieren. Den Namen entsprechend bekommt hier eine Route einen Namen und wird darüber aufgerufen, ähnlich einer URL. Man erstellt dafür eine routes.dart mit einem solchen Inhalt:
import 'package:flutter/material.dart'; import 'package:flutter_navigation/view1.dart'; import 'package:flutter_navigation/view2.dart'; import 'package:flutter_navigation/view3.dart'; final routes = { '/view1': (BuildContext context) => const View1(), '/view2': (BuildContext context) => const View2(), '/view3': (BuildContext context) => const View3(), };
Die muss nun der Flutteranwendung zugewiesen werden:
class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), routes: routes, // genau hier ); } }
Und schon können die Buttons eine Funktion bekommen:
ElevatedButton( onPressed: () => Navigator.of(context).pushNamed('/view2'), child: const Text('View 2'), )
Navigator.of(context)
holt sich den Navigator, pushNamed
navigiert zum zuvor angelegten Widget. Mit einem Navigator.of(context).pop()
würde man wieder zurückkommen, es gibt hier also einen Navigations-Stack.
v0.2: NamedRoutes mit Argumenten
Was aber, wenn bei der Navigation auch Daten an das Widget gegeben werden sollen?
Dafür gibt es Argumente. Um genau zu sein gibt es ein Arguments-Objekt, in das alles beliebige gespeichert werden kann. Zum Beispiel hier eine Map mit einem String, den das Widget dann anzeigen soll:
ElevatedButton( onPressed: () => Navigator.of(context).pushNamed('/view1', arguments: { 'content': 'Dynamic text', }),
Damit das Widget es auch bekommt, müssen wir seine Route in der routes.dart ändern:
'/view1': (BuildContext context) { final args = ModalRoute.of(context)!.settings.arguments as Map<String, dynamic>; return View1( content: args['content']!, ); },
Wir machen uns hier zunutze, dass Dart (zumindest seit der Version für Flutter 3) mit den Typen recht flexibel umgehen kann, sodass content
nicht manuell in einen String umgewandelt werden muss. Das Widget kann mit dem Parameter direkt arbeiten:
import 'package:flutter/material.dart'; class View1 extends StatelessWidget { final String content; const View1({super.key, required this.content}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('View 1'), ), body: Center( child: Text(content), ), ); } }
Das Widget kann nun dynamisch bei jedem Navigationsaufruf mit einem anderen Text befüllt werden.
Schön an diesem Ansatz ist der geringe Aufwand. Es braucht keine eigene Klasse für die Widget-Argumente, weil sie einfach in eine Map gepackt werden. Und da bei der Map der Value-Typ auf dynamic
gesetzt wurde kann beliebiges übertragen werden. Eine automatische Codegenerierung wie bei auto_route hier draufzusetzen scheint unnötig.
v0.3: NamedRoutes mit wählbaren Animationen (und Argumenten)
Die Animationen anpassen zu können dagegen wird für manche Anwendungen nötig sein. Ich zeige einen erweiterbaren Ansatz. Er wird mit onGenerateRoute
der MaterialApp arbeiten.
Das ist jetzt viel auf einmal:
return MaterialApp( title: 'Flutter Demo', theme: ThemeData( primarySwatch: Colors.blue, ), home: const MyHomePage(title: 'Flutter Demo Home Page'), onGenerateRoute: (settings) { if (routes.containsKey(settings.name)) { final args = settings.arguments as Map<String, dynamic>?; return PageRouteBuilder( settings: settings, pageBuilder: (context, animation, secondaryAnimation) => routes[settings.name]!(context), transitionsBuilder: (context, animation, secondaryAnimation, child) { switch (args?['transition']) { case Transitions.scale: return ScaleTransition(scale: animation, child: child); case Transitions.fade: return FadeTransition(opacity: animation, child: child); default: return SlideTransition( position: Tween<Offset>( begin: const Offset(0.0, 1.0), end: Offset.zero, ).animate(animation), child: child, ); } }); } // Unknown route return MaterialPageRoute(builder: (context) => Container()); }, );
Also, was ist hier passiert:
Zuerst wurde der routes:
-Parameter entfernt. Wäre er noch da, hätte er Priorität und unser neuer Code in onGenerateRoute
würde ignoriert.
Der nächste Kniff ist pageBuilder: (context, animation, secondaryAnimation) => routes[settings.name]!(context)
. Hiermit wird beim Seitenbau in routes
nach einer Route gesucht. So kann die alte routes.dart weiterbenutzt werden, sie muss nichtmal editiert werden. Gut so, denn dadurch behalten wir einen festen Ort für übersichtliche Routendefinitionen.
Es folgt der transitionsBuilder
. Der schaut, ob bei dieser Navigation das Argument transition
übergeben wurde. Wenn ja und es einen bestimmten Wert hat, wird eine der vordefinierten Übergangsanimationen gesetzt. Dafür wurde ein bislang nicht gezeigtes Enum angelegt:
enum Transitions { scale, fade }
Die Navigation mit Animationsauswahl sähe nun nicht viel anders aus als zuvor:
ElevatedButton( onPressed: () => Navigator.of(context).pushNamed('/view1', arguments: { 'content': 'Dynamic text', 'transition': Transitions.scale }), child: const Text('View 1')), ),
Das ginge natürlich auch anders – so hatte ich eine Variante mit dem Plugin page_transition entwickelt, die brauchte aber Änderungen an der routes.dart.
Dieser Artikel und Ansatz ist ein Nebenprodukt meiner Analyse von Flutters Routingsituation. Die vielen Lösungen waren chaotisch präsentiert, viele erschienen mir auch unnötig kompliziert. NamedRoutes stachen direkt als klar verständliche Lösung heraus, aber wie sie gut zu benutzen sind, samt Argumenten und Animationen, sah ich nicht erklärt. Ob sie dazu überhaupt taugen? Sie tun es, wie hoffentlich deutlich wurde.
Laut Dokumentation sollen NamedRoutes Nachteile haben – bei Push-Benachrichtigungen würden sie immer ihr Ziel öffnen, selbst wenn es schon offen sei (ist das in der onGenerateRoute wirklich nicht abfangbar?) und bei Webanwendungen als Kompilierziel der Vorwärtsbutton im Browser mit ihnen nicht funktionieren (kein Problem für mich, da ich Flutter für Webanwendungen ungeeignet finde). Wer darüber stolpern würde sollte sich wahrscheinlich als zweitbeste Lösung den go_router ansehen.
Linksammlung 45/2022
Friday, 11. November 2022
Diese Woche fand ich besonders erwähnenswert:
Allen Kunden soll gekündigt werden: Diese deutsche Bank wird in Kürze aufgelöst heißt es über Fidor. Auch ein Zeichen der Zeit, dass die vorher so boomenden Onlinebanken jetzt pleite gehen. Alle Geschäftsmodelle ohne direkten Profit sterben gerade.
Musk’s First Email to Twitter Staff Ends Remote Work kam raus und beerdigt Twitter endgültig. Entwickler lassen so nicht mit sich umgehen, nur noch absolut verzweifelte oder unfähige werden bei der Firma bleiben. Selbst wenn es durch die Krise jetzt noch eine Weile genug Twitter-Sklaven gibt, kann kein Entwickler unter solchen Bedingungen etwas vernünftiges produzieren. Das meint nicht die Arbeit im Büro, sondern die geforderten Arbeitszeiten, die Feindseligkeit und den Druck.
Das wird auch Auswirkungen auf den Rest der Welt haben: FTC Restores Rigorous Enforcement of Law Banning Unfair Methods of Competition. Apple beispielsweise kann seinem Appstore-Monopol Adieu sagen.
Als politisch denkender Mensch kann man an Deutschlands Gesellschaft nur verzweifeln, man kann sie nur kurzfristig verstehen. Das ist eine historische Konstante und wird als Phänomen derzeit in Die letzte Irritation wieder sichtbar.
Beinahe hätte auch Intel etwas beerdigt, keine Firma, sondern ihre Grafikkarten. Laut
Intel Clarifies HECI Usage For Arc Graphics' GSC haben sie gerade nochmal die Kurve gekriegt. Initial hatten sie geschrieben, Firmwareupdates nur mit Intelprozessoren möglich zu machen. Bei einem sowieso ziemlich desaströsen Start wären solche Plattformsperenzchen das Ende gewesen, der derzeit völlig unterlegene Herausforderer kann sich nicht wie ein Monopolist à la Apple gebären.