Getting started
A flexible and customizable list widget for Flutter applications using the BLoC pattern for state management. It offers dynamic data loading, state-dependent rendering, custom loader support, and error handling, ideal for creating responsive and user-friendly list views.
Usage
TODO: Quick examples
Using flutter_bloc
void main() {
runApp(MultiBlocProvider(
providers: [
BlocProvider(create: (_) => TodoBloc(DataService())),
],
child: const MainApp(),
));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Todo List')),
floatingActionButton: FloatingActionButton(
shape: const CircleBorder(),
onPressed: () => _addOrUpdateTodoItem(context),
backgroundColor: Theme.of(context).primaryColor,
child: const Icon(Icons.add, color: Colors.white),
),
body: BlocList<TodoModel, TodoBloc, ListState>(
emptyBuilder: (context, state) {
return const Center(child: Text('Please add a todo item'));
},
loadBuilder: (context, state) {
return const Center(
child: CircularProgressIndicator(
color: Colors.black,
));
},
onItemError: (errorType, item, errorMessage) {
item.isBusy = false;
_displaySnackBar(context, errorMessage);
},
onItemAdding: (addingItem) {
addingItem.isBusy = true;
},
onItemDeleting: (deletingItem) {
deletingItem.isBusy = true;
},
onItemUpdating: (newItem, oldItem) {
oldItem.isBusy = true;
},
onItemDeleted: (deletedItem) {
_displaySnackBar(context, "Deleted ${deletedItem.description}");
},
onItemUpdated: (newItem, oldItem) {
_displaySnackBar(context,
"Updated ${oldItem.description} to ${newItem.description}");
},
onItemAdded: (addedItem) {
addedItem.isBusy = false;
_displaySnackBar(context, "Added ${addedItem.description}");
},
itemBuilder: (context, index, item) {
var outputFormat = DateFormat('dd MMM yyyy HH:mm');
var date = outputFormat.format(item.created);
return ListTile(
title: Text(
item.description,
style:
TextStyle(color: item.isBusy ? Colors.grey : Colors.black),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: Icon(Icons.edit,
color: item.isBusy ? Colors.grey : Colors.black),
onPressed: () {
_addOrUpdateTodoItem(context, index: index, item: item);
},
),
IconButton(
icon: Icon(Icons.delete,
color: item.isBusy ? Colors.grey : Colors.black),
onPressed: () {
_deleteDialog(context, () {
BlocProvider.of<TodoBloc>(context)
.add(DeleteDataEvent(item));
});
},
),
],
),
subtitle: Padding(
padding: const EdgeInsets.only(top: 5.0),
child: Text(
date,
style: TextStyle(
color: item.isBusy ? Colors.grey : Colors.black54),
),
),
);
},
loadData: () async {
BlocProvider.of<TodoBloc>(context).add(LoadDataEvent());
},
stateCondition: (state) => state,
));
}
class TodoBloc extends ListBloc<TodoModel> {
final DataService dataService;
TodoBloc(this.dataService)
: super(
dataProvider: ([id]) => dataService.getTodoList(id),
dataAdder: (item) => dataService.addTodo(item),
dataDeleter: (item) => dataService.deleteTodo(item),
dataSorter: (items, [compare]) =>
items.sort((a, b) => b.created.compareTo(a.created)),
dataUpdater: (item) => dataService.updateTodo(item),
);
}
Future<BlocResponse<List<TodoModel>>> getTodoList([int? id]) async {
final response = await http
.get(Uri.parse('https://example.com/todos'));
if (response.statusCode == 200) {
List<TodoModel> todos = (jsonDecode(response.body) as List<dynamic>)
.map((e) => TodoModel.fromJson(e as Map<String, dynamic>))
.toList();
return BlocResponse<List<TodoModel>>(success: true, data: todos);
} else {
return BlocResponse<List<TodoModel>>(
success: false, message: "Error loading todos");
}
}