Widgets

CollectionView

Pengantar

Widget CollectionView adalah pembungkus yang kuat dan type-safe untuk menampilkan daftar data di proyek Nylo Website Anda. Widget ini menyederhanakan bekerja dengan ListView, ListView.separated, dan tata letak grid sambil menyediakan dukungan bawaan untuk:

  • Pemuatan data async dengan status loading otomatis
  • Pull-to-refresh dan paginasi
  • Builder item type-safe dengan helper posisi
  • Penanganan status kosong
  • Pengurutan dan transformasi data

Penggunaan Dasar

Berikut contoh sederhana menampilkan daftar item:

@override
Widget build(BuildContext context) {
  return Scaffold(
    body: CollectionView<String>(
      data: () => ['Item 1', 'Item 2', 'Item 3'],
      builder: (context, item) {
        return ListTile(
          title: Text(item.data),
        );
      },
    ),
  );
}

Dengan data async dari API:

CollectionView<Todo>(
  data: () async {
    return await api<ApiService>((request) =>
      request.get('https://jsonplaceholder.typicode.com/todos')
    );
  },
  builder: (context, item) {
    return ListTile(
      title: Text(item.data.title),
      subtitle: Text(item.data.completed ? 'Done' : 'Pending'),
    );
  },
)

Helper CollectionItem

Callback builder menerima objek CollectionItem<T> yang membungkus data Anda dengan helper posisi yang berguna:

CollectionView<String>(
  data: () => ['First', 'Second', 'Third', 'Fourth'],
  builder: (context, item) {
    return Container(
      color: item.isEven ? Colors.grey[100] : Colors.white,
      child: ListTile(
        title: Text('${item.data} (index: ${item.index})'),
        subtitle: Text('Progress: ${(item.progress * 100).toInt()}%'),
      ),
    );
  },
)

Properti CollectionItem

Properti Tipe Deskripsi
data T Data item yang sebenarnya
index int Indeks saat ini dalam daftar
totalItems int Jumlah total item
isFirst bool True jika ini adalah item pertama
isLast bool True jika ini adalah item terakhir
isOdd bool True jika indeks ganjil
isEven bool True jika indeks genap
progress double Progres melalui daftar (0.0 sampai 1.0)

Method CollectionItem

Method Deskripsi
isAt(int position) Memeriksa apakah item berada di posisi tertentu
isInRange(int start, int end) Memeriksa apakah indeks berada dalam rentang (inklusif)
isMultipleOf(int divisor) Memeriksa apakah indeks adalah kelipatan dari pembagi

CollectionView

Konstruktor default membuat tampilan daftar standar:

CollectionView<Map<String, dynamic>>(
  data: () async {
    return [
      {"title": "Clean Room"},
      {"title": "Go shopping"},
      {"title": "Buy groceries"},
    ];
  },
  builder: (context, item) {
    return ListTile(title: Text(item.data['title']));
  },
  spacing: 8.0, // Tambahkan jarak antar item
  padding: EdgeInsets.all(16),
)

CollectionView.separated

Membuat daftar dengan pemisah antar item:

CollectionView<User>.separated(
  data: () async => await fetchUsers(),
  builder: (context, item) {
    return ListTile(
      title: Text(item.data.name),
      subtitle: Text(item.data.email),
    );
  },
  separatorBuilder: (context, index) {
    return Divider(height: 1);
  },
)

CollectionView.grid

Membuat tata letak grid menggunakan staggered grid:

CollectionView<Product>.grid(
  data: () async => await fetchProducts(),
  builder: (context, item) {
    return ProductCard(product: item.data);
  },
  crossAxisCount: 2,
  mainAxisSpacing: 8.0,
  crossAxisSpacing: 8.0,
  padding: EdgeInsets.all(16),
)

CollectionView.pullable

Membuat daftar dengan pull-to-refresh dan paginasi scroll tak terbatas:

CollectionView<Post>.pullable(
  data: (int iteration) async {
    // iteration dimulai dari 1 dan bertambah setiap pemuatan
    return await api<ApiService>((request) =>
      request.get('/posts?page=$iteration')
    );
  },
  builder: (context, item) {
    return PostCard(post: item.data);
  },
  onRefresh: () {
    print('Daftar telah di-refresh!');
  },
  headerStyle: 'WaterDropHeader', // Gaya indikator pull
)

Gaya Header

Parameter headerStyle menerima:

  • 'WaterDropHeader' (default) - Animasi tetesan air
  • 'ClassicHeader' - Indikator pull klasik
  • 'MaterialClassicHeader' - Gaya Material Design
  • 'WaterDropMaterialHeader' - Tetesan air Material
  • 'BezierHeader' - Animasi kurva Bezier

Callback Paginasi

Callback Deskripsi
beforeRefresh Dipanggil sebelum refresh dimulai
onRefresh Dipanggil saat refresh selesai
afterRefresh Dipanggil setelah data dimuat, menerima data untuk transformasi

CollectionView.pullableSeparated

Menggabungkan pull-to-refresh dengan daftar terpisah:

CollectionView<Message>.pullableSeparated(
  data: (iteration) async => await fetchMessages(page: iteration),
  builder: (context, item) {
    return MessageTile(message: item.data);
  },
  separatorBuilder: (context, index) => Divider(),
)

CollectionView.pullableGrid

Menggabungkan pull-to-refresh dengan tata letak grid:

CollectionView<Photo>.pullableGrid(
  data: (iteration) async => await fetchPhotos(page: iteration),
  builder: (context, item) {
    return Image.network(item.data.url);
  },
  crossAxisCount: 3,
  mainAxisSpacing: 4,
  crossAxisSpacing: 4,
)

Gaya Loading

Sesuaikan indikator loading menggunakan loadingStyle:

// Loading normal dengan widget kustom
CollectionView<Item>(
  data: () async => await fetchItems(),
  builder: (context, item) => ItemTile(item: item.data),
  loadingStyle: LoadingStyle.normal(
    child: Text("Memuat item..."),
  ),
)

// Efek loading skeletonizer
CollectionView<User>(
  data: () async => await fetchUsers(),
  builder: (context, item) => UserCard(user: item.data),
  loadingStyle: LoadingStyle.skeletonizer(
    child: UserCard(user: User.placeholder()),
    effect: SkeletonizerEffect.shimmer,
  ),
)

Status Kosong

Tampilkan widget kustom saat daftar kosong:

CollectionView<Item>(
  data: () async => await fetchItems(),
  builder: (context, item) => ItemTile(item: item.data),
  empty: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(Icons.inbox, size: 64, color: Colors.grey),
        SizedBox(height: 16),
        Text('Tidak ada item ditemukan'),
      ],
    ),
  ),
)

Mengurutkan dan Mentransformasi Data

Pengurutan

Urutkan item sebelum ditampilkan:

CollectionView<Task>(
  data: () async => await fetchTasks(),
  builder: (context, item) => TaskTile(task: item.data),
  sort: (List<Task> items) {
    items.sort((a, b) => a.dueDate.compareTo(b.dueDate));
    return items;
  },
)

Transformasi

Transformasi data setelah dimuat:

CollectionView<User>(
  data: () async => await fetchUsers(),
  builder: (context, item) => UserTile(user: item.data),
  transform: (List<User> users) {
    // Filter hanya pengguna aktif
    return users.where((u) => u.isActive).toList();
  },
)

Memperbarui State

Anda dapat memperbarui atau mereset CollectionView dengan memberikannya stateName:

CollectionView<Todo>(
  stateName: "my_todo_list",
  data: () async => await fetchTodos(),
  builder: (context, item) => TodoTile(todo: item.data),
)

Mereset daftar

// Mereset dan memuat ulang data dari awal
CollectionView.stateReset("my_todo_list");

Menghapus item

// Hapus item di indeks 2
CollectionView.removeFromIndex("my_todo_list", 2);

Memicu pembaruan umum

// Menggunakan helper updateState global
updateState("my_todo_list");

Referensi Parameter

Parameter Umum

Parameter Tipe Deskripsi
data Function() Fungsi yang mengembalikan List<T> atau Future<List<T>>
builder CollectionItemBuilder<T> Fungsi builder untuk setiap item
empty Widget? Widget yang ditampilkan saat daftar kosong
loadingStyle LoadingStyle? Sesuaikan indikator loading
header Widget? Widget header di atas daftar
stateName String? Nama untuk manajemen state
sort Function(List<T>)? Fungsi pengurutan untuk item
transform Function(List<T>)? Fungsi transformasi untuk data
spacing double? Jarak antar item

Parameter Khusus Pullable

Parameter Tipe Deskripsi
data Function(int iteration) Fungsi data terpaginasi
onRefresh Function()? Callback saat refresh selesai
beforeRefresh Function()? Callback sebelum refresh
afterRefresh Function(dynamic)? Callback setelah data dimuat
headerStyle String? Gaya indikator pull
footerLoadingIcon Widget? Indikator loading kustom untuk paginasi

Parameter Khusus Grid

Parameter Tipe Deskripsi
crossAxisCount int Jumlah kolom (default: 2)
mainAxisSpacing double Jarak vertikal antar item
crossAxisSpacing double Jarak horizontal antar item

Parameter ListView

Semua parameter ListView standar juga didukung: scrollDirection, reverse, controller, physics, shrinkWrap, padding, cacheExtent, dan lainnya.