CollectionView
Introduction
The CollectionView widget is a powerful, type-safe wrapper for displaying lists of data in your Nylo Website projects. It simplifies working with ListView, ListView.separated, and grid layouts while providing built-in support for:
- Async data loading with automatic loading states
- Pull-to-refresh and pagination
- Type-safe item builders with position helpers
- Empty state handling
- Data sorting and transformation
Basic Usage
Here's a simple example displaying a list of items:
@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),
);
},
),
);
}
With async data from an 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'),
);
},
)
CollectionItem Helper
The builder callback receives a CollectionItem<T> object that wraps your data with useful position helpers:
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()}%'),
),
);
},
)
CollectionItem Properties
| Property | Type | Description |
|---|---|---|
data |
T |
The actual item data |
index |
int |
Current index in the list |
totalItems |
int |
Total number of items |
isFirst |
bool |
True if this is the first item |
isLast |
bool |
True if this is the last item |
isOdd |
bool |
True if index is odd |
isEven |
bool |
True if index is even |
progress |
double |
Progress through list (0.0 to 1.0) |
CollectionItem Methods
| Method | Description |
|---|---|
isAt(int position) |
Check if item is at specific position |
isInRange(int start, int end) |
Check if index is within range (inclusive) |
isMultipleOf(int divisor) |
Check if index is multiple of divisor |
CollectionView
The default constructor creates a standard list view:
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, // Add spacing between items
padding: EdgeInsets.all(16),
)
CollectionView.separated
Creates a list with separators between items:
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
Creates a grid layout using 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
Creates a list with pull-to-refresh and infinite scroll pagination:
CollectionView<Post>.pullable(
data: (int iteration) async {
// iteration starts at 1 and increments on each load
return await api<ApiService>((request) =>
request.get('/posts?page=$iteration')
);
},
builder: (context, item) {
return PostCard(post: item.data);
},
onRefresh: () {
print('List was refreshed!');
},
headerStyle: 'WaterDropHeader', // Pull indicator style
)
Header Styles
The headerStyle parameter accepts:
'WaterDropHeader'(default) - Water drop animation'ClassicHeader'- Classic pull indicator'MaterialClassicHeader'- Material design style'WaterDropMaterialHeader'- Material water drop'BezierHeader'- Bezier curve animation
Pagination Callbacks
| Callback | Description |
|---|---|
beforeRefresh |
Called before refresh starts |
onRefresh |
Called when refresh completes |
afterRefresh |
Called after data loads, receives data for transformation |
CollectionView.pullableSeparated
Combines pull-to-refresh with separated list:
CollectionView<Message>.pullableSeparated(
data: (iteration) async => await fetchMessages(page: iteration),
builder: (context, item) {
return MessageTile(message: item.data);
},
separatorBuilder: (context, index) => Divider(),
)
CollectionView.pullableGrid
Combines pull-to-refresh with grid layout:
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,
)
Loading Styles
Customize the loading indicator using loadingStyle:
// Normal loading with custom widget
CollectionView<Item>(
data: () async => await fetchItems(),
builder: (context, item) => ItemTile(item: item.data),
loadingStyle: LoadingStyle.normal(
child: Text("Loading items..."),
),
)
// Skeletonizer loading effect
CollectionView<User>(
data: () async => await fetchUsers(),
builder: (context, item) => UserCard(user: item.data),
loadingStyle: LoadingStyle.skeletonizer(
child: UserCard(user: User.placeholder()),
effect: SkeletonizerEffect.shimmer,
),
)
Empty State
Display a custom widget when the list is empty:
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('No items found'),
],
),
),
)
Sorting and Transforming Data
Sorting
Sort items before display:
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;
},
)
Transform
Transform the data after loading:
CollectionView<User>(
data: () async => await fetchUsers(),
builder: (context, item) => UserTile(user: item.data),
transform: (List<User> users) {
// Filter to only active users
return users.where((u) => u.isActive).toList();
},
)
Updating the State
You can update or reset a CollectionView by giving it a stateName:
CollectionView<Todo>(
stateName: "my_todo_list",
data: () async => await fetchTodos(),
builder: (context, item) => TodoTile(todo: item.data),
)
Reset the list
// Resets and reloads data from scratch
CollectionView.stateReset("my_todo_list");
Remove an item
// Remove item at index 2
CollectionView.removeFromIndex("my_todo_list", 2);
Trigger a general update
// Using the global updateState helper
updateState("my_todo_list");
Parameters Reference
Common Parameters
| Parameter | Type | Description |
|---|---|---|
data |
Function() |
Function returning List<T> or Future<List<T>> |
builder |
CollectionItemBuilder<T> |
Builder function for each item |
empty |
Widget? |
Widget shown when list is empty |
loadingStyle |
LoadingStyle? |
Customize loading indicator |
header |
Widget? |
Header widget above the list |
stateName |
String? |
Name for state management |
sort |
Function(List<T>)? |
Sort function for items |
transform |
Function(List<T>)? |
Transform function for data |
spacing |
double? |
Spacing between items |
Pullable-Specific Parameters
| Parameter | Type | Description |
|---|---|---|
data |
Function(int iteration) |
Paginated data function |
onRefresh |
Function()? |
Callback when refresh completes |
beforeRefresh |
Function()? |
Callback before refresh |
afterRefresh |
Function(dynamic)? |
Callback after data loads |
headerStyle |
String? |
Pull indicator style |
footerLoadingIcon |
Widget? |
Custom loading indicator for pagination |
Grid-Specific Parameters
| Parameter | Type | Description |
|---|---|---|
crossAxisCount |
int |
Number of columns (default: 2) |
mainAxisSpacing |
double |
Vertical spacing between items |
crossAxisSpacing |
double |
Horizontal spacing between items |
ListView Parameters
All standard ListView parameters are also supported: scrollDirection, reverse, controller, physics, shrinkWrap, padding, cacheExtent, and more.