Skip to content
Kezdőlap » StatelessWidget átírása StatefulWidget-re

StatelessWidget átírása StatefulWidget-re

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';
    });
  }

Vélemény, hozzászólás?

Az e-mail címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük