Skip to content

Stateful Widgets

Di dalam Flutter terdapat dua tipe widget yaitu stateless dan stateful widget. Stateless widget adalah widget statis atau tidak akan berubah, sedangkan stateful widget adalah widget yang dinamis atau dapat berubah.

Stateless Statefull

Gambar di sebelah kiri adalah contoh stateless widgets, karena pada aplikasi tersebut, semua widget bersifat statis. Sedangkan pada gambar sebelah kanan, angka 0 akan berubah menjadi 1 jika tombol ditekan, artinya widget tersebut adalah stateful.

Dice

Kita akan membuat sebuah aplikasi dengan stateful widget seperti berikut ini.

Dadu

Aplikasi diawali dengan menampilkan gambar dua buah dadu 1 titik. Saat salah satu dadu ditekan, maka kedua dadu akan menampilkan gambar dadu dengan jumlah titik secara acak.

New Project

Buat sebuah proyek Flutter baru dan sesuaikan kodenya seperti berikut ini

main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.red),
      home: Scaffold(
        backgroundColor: Colors.red,
        appBar: AppBar(
          title: const Text('Dadu'),
          centerTitle: true,
        ),
        body: DicePage(),
      ),
    );
  }
}

Buat folder images dan letakkan file gambar ini ke dalam folder images. Kemudian tambahkan folder images ke assets pada file pubspec.yaml.

pubspec.yaml
  assets:
    - images/

Class DicePage

Pada baris 20 kita membuat objek DicePage namun kita belum membuat class DicePage. Karena gambar dadu pada aplikasi akan berubah saat ditekan, maka kita harus membuat class DicePage yang extend class StatefulWidget.

Buat class DicePage seperti dibawah ini. Untuk mempercepat penulisan kode, kita bisa menuliskan stlu pilih Flutter Stateful Widget dan tekan enter. Kemudian ubah MyWidget menjadi DicePage.

main.dart
class DicePage extends StatefulWidget {
  const DicePage({Key? key}) : super(key: key);

  @override
  State<DicePage> createState() => _DicePageState();
}

class _DicePageState extends State<DicePage> {
  @override
  Widget build(BuildContext context) {

  }
}

Semua widget yang diletakkan pada baris ke 36 sifatnya adalah stateful, widget-widget tersebut akan dirender ulang setiap kali ada perubahan state.

Design

Kita akan membuat widget gambar dadu sebanyak 2 buah yang posisinya berada di tengah layar. Untuk itu kita memerlukan widget Center dan Row.

main.dart
class _DicePageState extends State<DicePage> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        children: [
          Image.asset('images/dice1.png'),
          Image.asset('images/dice1.png'),
        ],
      ),
    );
  }
}

Karena gambar dadu terlalu besar, makan akan terjadi exception yang memberi tahu bahwa widget terlalu besar dan melewati batas layar.

Overflow

Untuk memperbaiki itu kita bisa menggunakan widget Expanded. Widget ini akan menyesuaikan ukuran gambar dadu dan memenuhi ruang yang tersedia.

main.dart
Expanded(child: Image.asset('images/dice1.png')),
Expanded(child: Image.asset('images/dice1.png')),

Expanded

Untuk membuat jarak antar gambar dadu kita bisa menggunakan widget Container atau Padding dan mengatur properti paddingnya.

main.dart
Expanded(
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Image.asset('images/dice1.png'),
  ),
),
Expanded(
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: Image.asset('images/dice1.png'),
  ),
),

Padding

Coding

Untuk membuat gambar dadu bisa ditekan, kita harus bungkus gambar dadu kedalam widget tombol. Widget yang kita gunakan adalah TextButton. Widget TextButton memiliki method onPressed yang digunakan untuk mengeksekusi kode program saat tombol ditekan.

main.dart
Expanded(
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: TextButton(
      onPressed: () {},
      child: Image.asset('images/dice1.png'),
    ),
  ),
),
Expanded(
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: TextButton(
      onPressed: () {},
      child: Image.asset('images/dice1.png'),
    ),
  ),
),

Berikutnya kita akan membuat dua buah variabel untuk menampung nilai masing-masing dadu dan sebuah fungsi untuk mengacak nilai dari 1 sampai 6.

main.dart
class _DicePageState extends State<DicePage> {
  var leftDice = 1;
  var rightDice = 1;

  void randomDice() {
    leftDice = Random().nextInt(6) + 1;
    rightDice = Random().nextInt(6) + 1;
  }

  @override
  Widget build(BuildContext context) {

Karena kita menggunakan class Random maka kita perlu mengimpor library dart:math.

main.dart
import 'dart:math';
import 'package:flutter/material.dart';

Ubah kode untuk menampilkan gambar dadu agar bisa berubah sesuai dengan nilai variabel leftDice dan rightDice.

Image.asset('images/dice1.png') # dadu kiri
Image.asset('images/dice1.png') # dadu kanan

menjadi

Image.asset('images/dice$leftDice.png') # dadu kiri
Image.asset('images/dice$rightDice.png') # dadu kanan

Pada stateful widget, Flutter akan merender ulang widget-widget saat ada perubahan state. Untuk mengubah state, kita gunakan fungsi setState() didalam method onPressed di TextButton. State yang kita ubah adalah mengubah data di variabel leftDice dan rightDice secara acak menggunakan fungsi randomDice().

main.dart
Expanded(
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: TextButton(
      onPressed: () {
        setState(() {
          randomDice();
        });
      },
      child: Image.asset('images/dice$leftDice.png'),
    ),
  ),
),
Expanded(
  child: Padding(
    padding: const EdgeInsets.all(16.0),
    child: TextButton(
      onPressed: () {
        setState(() {
          randomDice();
        });
      },
      child: Image.asset('images/dice$rightDice.png'),
    ),
  ),
),

Karena ada state yang diubah, maka jalankan aplikasi dengan menekan tombol Restart bukan Hot Reload.

Restart

Final Code

Berikut ini adalah kode lengkap dari main.dart

main.dart
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(primarySwatch: Colors.red),
      home: Scaffold(
        backgroundColor: Colors.red,
        appBar: AppBar(
          title: const Text('Dadu'),
          centerTitle: true,
        ),
        body: const DicePage(),
      ),
    );
  }
}

class DicePage extends StatefulWidget {
  const DicePage({Key? key}) : super(key: key);

  @override
  State<DicePage> createState() => _DicePageState();
}

class _DicePageState extends State<DicePage> {
  var leftDice = 1;
  var rightDice = 1;

  void randomDice() {
    leftDice = Random().nextInt(6) + 1;
    rightDice = Random().nextInt(6) + 1;
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Row(
        children: [
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: TextButton(
                onPressed: () {
                  setState(() {
                    randomDice();
                  });
                },
                child: Image.asset('images/dice$leftDice.png'),
              ),
            ),
          ),
          Expanded(
            child: Padding(
              padding: const EdgeInsets.all(16.0),
              child: TextButton(
                onPressed: () {
                  setState(() {
                    randomDice();
                  });
                },
                child: Image.asset('images/dice$rightDice.png'),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

Simple Calculator

Kita akan membuat kalkulator sederhana seperti berikut ini

Calculator

main.dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: CalculatorPage(),
      );
  }
}

class CalculatorPage extends StatefulWidget {
  const CalculatorPage({super.key});

  @override
  State<CalculatorPage> createState() => _CalculatorPageState();
}

class _CalculatorPageState extends State<CalculatorPage> {
  final TextEditingController _num1Controller = TextEditingController();
  final TextEditingController _num2Controller = TextEditingController();
  double? _result;

  void _calculateSum() {
    final num1 = double.tryParse(_num1Controller.text) ?? 0;
    final num2 = double.tryParse(_num2Controller.text) ?? 0;
    setState(() {
      _result = num1 + num2;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Kalkulator Sederhana')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            TextField(
              controller: _num1Controller,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(labelText: 'Angka 1'),
            ),
            TextField(
              controller: _num2Controller,
              keyboardType: TextInputType.number,
              decoration: const InputDecoration(labelText: 'Angka 2'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: _calculateSum,
              child: const Text('Hitung'),
            ),
            const SizedBox(height: 20),
            Text(
              _result == null ? '' : 'Hasil: $_result',
              style: const TextStyle(fontSize: 20),
            ),
          ],
        ),
      ),
    );
  }
}

Chalange

  • Tambahkan tombol untuk tambah, kurang, kali dan bagi.
  • Validasi input agar pengguna hanya bisa memasukkan angka.