Néha előfordul, hogy egy StatelessWidget-et át kell írnunk StatefulWidget-re.
Nem árt átgondolni, hogy vajon az egész widgetnek kezelnie kell állapotokat, vagy csak bizonyos részeinek?
Itt egy példa:
import 'package:flutter/material.dart';
const startAlignment = Alignment.topLeft;
const endAlignment = Alignment.bottomRight;
class GradientContainer extends StatelessWidget {
GradientContainer(this.color1, this.color2, {super.key});
GradientContainer.purple({super.key})
: color1 = Colors.deepPurple,
color2 = Colors.indigo;
final Color color1;
final Color color2;
var activeDiceImage = 'assets/images/dice-3.png';
void rollDice() {
activeDiceImage = 'assets/images/dice-1.png';
}
@override
Widget build(context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [color1, color2],
begin: startAlignment,
end: endAlignment,
),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
activeDiceImage,
width: 200,
),
//const SizedBox(height: 20),
TextButton(
onPressed: rollDice,
style: TextButton.styleFrom(
padding: const EdgeInsets.only(
top: 20,
),
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 28),
),
child: const Text('Roll dice'),
)
],
),
),
);
}
}
A feladat az, hogy a TextButton lenyomásakor változzon meg az Image widget-en belül megjelenített kép.
Ez tipikusan jó példa arra, hogy a gombot és a képet kiszervezzük egy StatefulWidget-be, a többit pedig hagyjuk StatelessWidget-en belül.
StatefulWidget készítése
Az első lépés, hogy importáljuk a material.dart fájlt:
import 'package:flutter/material.dart';
Aztán ami biztos, hogy az osztályunknak ki kell terjesztenie a StatefulWidget class-t:
class DiceRoller extends StatefulWidget {
}
Egy StatefulWidget-ben nem használunk build metódust. Helyette meg kell adnunk egy createState() metódust. A createState nem kér és nem is kap paramétert, helyette viszont visszaad egy State típusú objektumot. A State csakúgy, mint a List egy generikus típus. Éppen ezért a State után <> jelek között meg kell adni azt, ahogy milyen típusú state-et kell visszaadni. Esetünkben az osztály nevét kell beírni:
class DiceRoller extends StatefulWidget {
@override
State<DiceRoller> createState() {
return
}
}
A createState törzsében vissza kell adni egy olyan state értéket, aminek a DiceRoller a típusa. Ezt az értéket egy másik osztály készíti el, tehát kell csinálnunk még egy osztály definíciót a fájlon belül. Ugyanis és ezt meg kell szokni / tanulni, hogy amikor StatefulWidget-ekkel dolgozunk, akkor mindig két osztályt kell definiálni.
A második osztály
A második osztály nevét úgy képezzük, hogy aláhúzás jellel kell kezdődik, jön az előző osztálynak a neve, aztán a State szót fűzzük a végére:
class _DiceRollerState
Az aláhúzás jelnek fontos jelentése van a Dart-ban. Privát osztályt jelöl, amit csak az adott .dart fájlon belül használhatunk még akkor is, ha ezt a fájlt egy másikban importáljuk.
Amiért ez az osztály privát, annak az az oka, hogy máshol nem is igazán van értelme használni.
A privát osztály a State-et terjeszti ki, ami DiceRoller típusú:
class _DiceRollerState extends State<DiceRoller> {
}
Aztán ebben az osztályban viszont használjuk a jó öreg build metódust, ami egy widgetet ad vissza:
@override
Widget build(context) {
return
}
A DiceRoller osztály createState() metódusában meghívjuk a _DiceRollerState konstruktorát:
class DiceRoller extends StatefulWidget {
@override
State<DiceRoller> createState() {
return _DiceRollerState();
}
}
Igaz ez annak ellenére is, hogy a _DiceRollerState osztály nem tartalmaz konstruktort. Ami nem baj, hiszen ilyen esetben a Flutter automatikusan létrehoz egyet.
Ha már konstrukor, akkor viszont a DiceRoller osztályba kell egy:
const DiceRoller({super.key});
A StatelessWidget teljes kódja
import 'package:flutter/material.dart';
import 'package:roll_dice_app/dice_roller.dart';
const startAlignment = Alignment.topLeft;
const endAlignment = Alignment.bottomRight;
class GradientContainer extends StatelessWidget {
const GradientContainer(this.color1, this.color2, {super.key});
const GradientContainer.purple({super.key})
: color1 = Colors.deepPurple,
color2 = Colors.indigo;
final Color color1;
final Color color2;
@override
Widget build(context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [color1, color2],
begin: startAlignment,
end: endAlignment,
),
),
child: const Center(child: DiceRoller()),
);
}
}
Lent ebből hívjuk meg a DiceRoller widgetet, ami Stateful.
A StatefulWidget teljes kódja
import 'package:flutter/material.dart';
class DiceRoller extends StatefulWidget {
const DiceRoller({super.key});
@override
State<DiceRoller> createState() {
return _DiceRollerState();
}
}
class _DiceRollerState extends State<DiceRoller> {
var activeDiceImage = 'assets/images/dice-3.png';
void rollDice() {
activeDiceImage = 'assets/images/dice-1.png';
}
@override
Widget build(context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
activeDiceImage,
width: 200,
),
TextButton(
onPressed: rollDice,
style: TextButton.styleFrom(
padding: const EdgeInsets.only(
top: 20,
),
foregroundColor: Colors.white,
textStyle: const TextStyle(fontSize: 28),
),
child: const Text('Roll dice'),
)
],
);
}
}
Mi kell még?
Önmagában ennyi még nem elég a state kezeléshez.
Ahhoz, hogy a változások látszódjanak a képernyőn, amikor lenyomjuk a gombot, újra kell futtatni a build metódust.
Ugyanis a stateful widgetben levő activeDiceImage változó egyszer mindenképpen megjelenik a képernyőn, amikor is a build metódus, amiben a változó szerepel lefut. A build metódus egyszer mindenképpen lefut, amikor a widget-et megjeleníti a képernyő. De aztán amikor a gomb lenyomására az activeDiceImage értéke megváltozik, ez nem eredményezi azt, hogy automatikusan lefut mégegyszer a build metódus.
Szóval, ha azt akarjuk, hogy amikor a rollDice metódus lefut és kap egy új értéket az activeDiceImage változó, akkor ezzel együtt fusson le a build metódus is mégegyszer, akkor kell nekünk egy ún. setState metódust írni a _DiceRollerState class-on belül.
A setState-nek át kell adni egy függvényt, ami tipikusan egy névtelen függvény. És ezen a függvényen belül végünk el mindenféle state frissítést. Tehát:
class _DiceRollerState extends State<DiceRoller> {
var activeDiceImage = 'assets/images/dice-3.png';
void rollDice() {
setState(() {
activeDiceImage = 'assets/images/dice-1.png';
});
}