Flutter 101: Membuat widget Rating Star


Halo selamat pagi, siang, malam, bagi Anda semua pembaca setia blog ini. Pada kesempatan kali ini mari kita membahas Flutter lagi. Tema yang tadinya mau rutin ditulis mumpung lagi belajar membuat aplikasi lewat framework hype ini, yang kemudian berakhir menjadi #wacana.

Tadinya mau melanjutkan tulisan ini, tapi karena kodenya ada di laptop sebelah, jadi mending menulis tema baru saja: membuat rating star di Flutter.

Misalkan kita akan membuat playlist lagu, dan pengguna dapat menilai kualitas lagu tersebut, mulai dari bintang 1 alias jelek hingga bintang lima alias sangat bagus sekali. Salah satu cara memberikan rating adalah dengan menampilkan… widget rating, tentu saja.

Kalau ngoding di Java/Kotlin, membuat layout rating rasanya tidak terlalu sulit, karena ada widget bawaan: RatingBar.  Tapi bagaimana dengan Flutter? Sependek pengetahuan saya, pengembang belum membuat widget tersendiri untuk rating.

Tapi jangan khawatir, karena semua hal di Flutter adalah widget, maka kita bisa dengan (agak) mudah membuat widget sendiri (dengan bantuan google dan stackoverflow).

Setelah googling, salah satu cara paling mudah adalah dengan membuat kelas tersendiri khusus untuk widget rating. Dengan begitu, (harusnya) widget itu bisa digunakan kembali di screen lain. Kalau kata pengembang Django: DRY! Begini kira-kira kodenya:

import 'package:flutter/material.dart';

typedef void RatingChangeCallback(double rating);

class RatingStar extends StatelessWidget {

  final int starCount;
  final double rating;
  final RatingChangeCallback onRatingChanged;
  final Color color;


  RatingStar({this.starCount, this.rating, this.onRatingChanged, this.color});

  Widget buildStar(BuildContext context, int index) {
    Icon icon;
    if (index >= rating) {
      icon = new Icon(
        Icons.star_border,
        color: Theme.of(context).buttonColor,
      );
    }
    else if (index > rating - 1 && index < rating) {
      icon = new Icon(
        Icons.star_half,
        color: color ?? Theme.of(context).primaryColor,
      );
    } else {
      icon = new Icon(
        Icons.star,
        color: color ?? Theme.of(context).primaryColor,
      );
    }
    return new GestureDetector(
      onTap: onRatingChanged == null ? null : () => onRatingChanged(index + 1.0),
      child: icon,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: new List.generate(starCount, (index) => buildStar(context, index)
        )
    );
  }
}

Setelah itu, kelas tersebut kita masukkan di screen yang akan menampilkan widget rating. Begini kira-kira kodenya: 

Widget build(BuildContext context) {
    return Center(
      child: Container(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text("Rating:"),
            Padding(padding: EdgeInsets.only(top: 16.0),),
            RatingStar(
              rating: _rating,
              starCount: 5,
              onRatingChanged: (rat) {
                setState(() {
                  this._rating = rat;
                });
              },
            ),
            Padding(padding: EdgeInsets.only(top: 16.0),),
            Text("${_nilaiRating(this._rating.ceil())}"),
          ],
        ),
      ),
    );
  }

Kalau tidak ada halangan, nanti hasilnya seperti gambar di bawah ini (kode ini disimpan di kelas Statefull). 

Hasil akhir widget ratingstar

Misalkan kita inginnya menampilkan rating di widget Dialog, apakah bisa? Tentu saja (harusnya) bisa. Masalahnya adalah, kelas Rating yang extends dari Stateless itu kelakuannya berbeda dengan jika kita mengaplikasikan di layout biasa seperti di atas. Karena tidak ada state, maka ketika pengguna mengeklik gambar bintang, tampilannya tidak berubah (misalnya klik bintang 1, bintang yang berwarna masih 4).

Dari hasil googling, hal ini karena ada masalah di bagian state. Untuk itu, kita buat kelas baru dengan sedikit mengubah kode kelas rating di atas.  

class RatingStarFull extends StatefulWidget {

  final int starCount;
  final double rating;
  final RatingChangeCallback onRatingChanged;
  final Color color;

  RatingStarFull({this.starCount, this.rating, this.onRatingChanged, this.color});

  @override
  State<StatefulWidget> createState() {
    return _RatingStarState(starCount, rating, onRatingChanged, color);
  }

}

class _RatingStarState extends State<RatingStarFull> {

  final int starCount;
  double rating;
  final RatingChangeCallback onRatingChanged;
  final Color color;


  _RatingStarState(this.starCount, this.rating, this.onRatingChanged,
      this.color);

  Widget buildStar(BuildContext context, int index) {
    Icon icon;
    if (index >= rating) {
      icon = new Icon(
        Icons.star_border,
        color: Theme.of(context).buttonColor,
      );
    }
    else if (index > rating - 1 && index < rating) {
      icon = new Icon(
        Icons.star_half,
        color: color ?? Theme.of(context).primaryColor,
      );
    } else {
      icon = new Icon(
        Icons.star,
        color: color ?? Theme.of(context).primaryColor,
      );
    }
    return new GestureDetector(
      onTap: onRatingChanged == null ? null : () {
        onRatingChanged(index + 1.0);
        setState(() {
          this.rating = index + 1.0;
        });
      },
      child: icon,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Row(
        mainAxisAlignment: MainAxisAlignment.center,
        children: new List.generate(starCount, (index) => buildStar(context, index)
        )
    );
  }
}

Ada beberapa yang berbeda dengan kelas sebelumnya. di kelas ini, variabel rating tidak lagi dideklarasikan final karena nilainya akan diupdate dari bagian GestureDetector. Hasilnya nanti akan seperti ini:

Ya sudah, untuk sementara sekian dulu. Selamat ngoding!


Ada komentar?

This site uses Akismet to reduce spam. Learn how your comment data is processed.