Skip to main content

ListAppi

A flexible and performant list widget that provides customizable scrolling behavior and layout options for displaying dynamic content.

Features

  • Custom Item Builder: Build items dynamically with a function-based approach
  • Scroll Control: Full control over scrolling behavior and physics
  • Direction Support: Horizontal and vertical scrolling modes
  • Empty State: Built-in empty state handling with customizable text
  • Performance Optimized: Uses SliverList for efficient rendering
  • Flexible Layout: Supports shrink wrapping and custom padding
  • RTL Support: Right-to-left language support

Usage

Basic List

ListAppi(
count: items.length,
child: (index) => ListTile(
title: Text('Item ${index + 1}'),
subtitle: Text('Description for item ${index + 1}'),
),
)

Horizontal List

ListAppi(
count: categories.length,
horizondal: true,
padding: EdgeInsets.symmetric(horizontal: 16),
child: (index) => Container(
width: 120,
margin: EdgeInsets.only(right: 12),
child: Card(
child: Center(
child: Text(categories[index]),
),
),
),
)

List with Custom Scroll Controller

class MyListWidget extends StatefulWidget {

_MyListWidgetState createState() => _MyListWidgetState();
}

class _MyListWidgetState extends State<MyListWidget> {
final ScrollController _scrollController = ScrollController();


Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: () {
_scrollController.animateTo(
0,
duration: Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Text('Scroll to Top'),
),
Expanded(
child: ListAppi(
count: 100,
controller: _scrollController,
child: (index) => Card(
margin: EdgeInsets.all(8),
child: ListTile(
leading: CircleAvatar(child: Text('${index + 1}')),
title: Text('Item ${index + 1}'),
subtitle: Text('This is item number ${index + 1}'),
),
),
),
),
],
);
}
}

Parameters

NameTypeDescriptionDefault
childFunction(int index)Required. Builder function that returns a widget for each index-
countintRequired. Number of items in the list-
controllerScrollController?Optional scroll controller for programmatic scrollingnull
reversedbool?Whether to reverse the scroll directionfalse
shrinkbool?Whether the list should shrink-wrap its contentfalse
physicsScrollPhysics?The scroll physics to usenull
primarybool?Whether this is the primary scroll viewnull
paddingEdgeInsetsGeometry?Padding around the list contentEdgeInsets.zero
horizondalbool?Whether to scroll horizontally instead of verticallyfalse
defaultTextString?Text to show when the list is empty'Empty'

Behavior

Dynamic Content

  • The child function is called for each index from 0 to count - 1
  • Items are built on-demand for optimal performance
  • Supports any widget type as list items

Scroll Direction

  • Vertical (default): Items scroll up and down
  • Horizontal: Items scroll left and right
  • Direction is controlled by the horizondal parameter

Empty State

  • When count is 0, displays the defaultText message
  • Empty state is centered and styled consistently
  • Customizable through the defaultText parameter

Best Practices

  1. Performance: Use the builder pattern for large lists to ensure optimal performance
  2. Controllers: Use scroll controllers when you need programmatic scroll control
  3. Padding: Apply appropriate padding for visual spacing
  4. Empty States: Provide meaningful empty state messages
  5. Item Keys: Consider using keys for list items when order might change
  6. Memory: The widget automatically handles memory management for large lists

Examples

Product List

ListAppi(
count: products.length,
padding: EdgeInsets.all(16),
child: (index) {
final product = products[index];
return Card(
margin: EdgeInsets.only(bottom: 12),
child: ListTile(
leading: Image.network(
product.imageUrl,
width: 50,
height: 50,
fit: BoxFit.cover,
),
title: Text(product.name),
subtitle: Text('\$${product.price}'),
trailing: IconButton(
icon: Icon(Icons.add_shopping_cart),
onPressed: () => _addToCart(product),
),
),
);
},
)

Chat Messages

ListAppi(
count: messages.length,
reversed: true, // Show newest messages at bottom
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: (index) {
final message = messages[index];
return Align(
alignment: message.isMe
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
margin: EdgeInsets.only(bottom: 8),
padding: EdgeInsets.all(12),
decoration: BoxDecoration(
color: message.isMe ? Colors.blue : Colors.grey[300],
borderRadius: BorderRadius.circular(16),
),
child: Text(
message.text,
style: TextStyle(
color: message.isMe ? Colors.white : Colors.black,
),
),
),
);
},
)

Category Chips (Horizontal)

ListAppi(
count: categories.length,
horizondal: true,
shrink: true,
padding: EdgeInsets.symmetric(horizontal: 16),
child: (index) {
final category = categories[index];
final isSelected = selectedCategory == category;
return Container(
margin: EdgeInsets.only(right: 8),
child: FilterChip(
label: Text(category),
selected: isSelected,
onSelected: (selected) {
setState(() => selectedCategory = category);
},
),
);
},
)

Settings List

ListAppi(
count: settingsItems.length,
child: (index) {
final item = settingsItems[index];
return ListTile(
leading: Icon(item.icon),
title: Text(item.title),
subtitle: item.subtitle != null ? Text(item.subtitle!) : null,
trailing: item.hasSwitch
? Switch(
value: item.isEnabled,
onChanged: (value) => _toggleSetting(item, value),
)
: Icon(Icons.chevron_right),
onTap: item.hasSwitch ? null : () => _navigateToSetting(item),
);
},
)

Infinite Scroll List

class InfiniteListWidget extends StatefulWidget {

_InfiniteListWidgetState createState() => _InfiniteListWidgetState();
}

class _InfiniteListWidgetState extends State<InfiniteListWidget> {
final ScrollController _controller = ScrollController();
List<String> items = List.generate(20, (index) => 'Item $index');
bool isLoading = false;


void initState() {
super.initState();
_controller.addListener(_scrollListener);
}

void _scrollListener() {
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
_loadMoreItems();
}
}

void _loadMoreItems() async {
if (isLoading) return;
setState(() => isLoading = true);

// Simulate network delay
await Future.delayed(Duration(seconds: 1));

setState(() {
items.addAll(List.generate(10, (index) => 'Item ${items.length + index}'));
isLoading = false;
});
}


Widget build(BuildContext context) {
return ListAppi(
count: items.length + (isLoading ? 1 : 0),
controller: _controller,
child: (index) {
if (index == items.length) {
return Center(
child: Padding(
padding: EdgeInsets.all(16),
child: CircularProgressIndicator(),
),
);
}
return ListTile(
title: Text(items[index]),
subtitle: Text('Description for ${items[index]}'),
);
},
);
}
}

See Also