riverpod_crud 0.0.1
riverpod_crud: ^0.0.1 copied to clipboard
A lightweight Riverpod CRUD helper for loading, error, refresh, pagination, create, update, and delete states.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_crud/riverpod_crud.dart';
void main() {
runApp(const ProviderScope(child: UserApp()));
}
class User {
final int id;
final String name;
const User({
required this.id,
required this.name,
});
User copyWith({
int? id,
String? name,
}) {
return User(
id: id ?? this.id,
name: name ?? this.name,
);
}
}
class FakeUserRepository implements CrudRepository<User, int> {
final List<User> _users = List.generate(
34,
(index) => User(id: index + 1, name: 'User ${index + 1}'),
);
// In a real app, these methods would call an API, database, or service.
@override
Future<List<User>> fetch({
int page = 1,
int limit = 20,
}) async {
await Future<void>.delayed(const Duration(milliseconds: 350));
final start = (page - 1) * limit;
if (start >= _users.length) {
return [];
}
final end = start + limit;
return _users.sublist(start, end > _users.length ? _users.length : end);
}
@override
Future<User> create(User item) async {
await Future<void>.delayed(const Duration(milliseconds: 250));
_users.insert(0, item);
return item;
}
@override
Future<User> update(int id, User item) async {
await Future<void>.delayed(const Duration(milliseconds: 250));
final index = _users.indexWhere((user) => user.id == id);
if (index == -1) {
throw const CrudException('User not found');
}
_users[index] = item;
return item;
}
@override
Future<void> delete(int id) async {
await Future<void>.delayed(const Duration(milliseconds: 250));
_users.removeWhere((user) => user.id == id);
}
}
// The provider wires the generic CRUD notifier to the user repository.
final usersProvider = crudNotifierProvider<User, int>(
repository: FakeUserRepository(),
getId: (user) => user.id,
paginationConfig: const PaginationConfig(limit: 10),
);
class UserApp extends StatelessWidget {
const UserApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'riverpod_crud example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
useMaterial3: true,
),
home: const UserPage(),
);
}
}
class UserPage extends ConsumerWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final state = ref.watch(usersProvider);
final notifier = ref.read(usersProvider.notifier);
return Scaffold(
appBar: AppBar(
title: const Text('Users'),
actions: [
IconButton(
tooltip: 'Refresh',
onPressed: state.isRefreshing ? null : notifier.refresh,
icon: const Icon(Icons.refresh),
),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: () {
final id = DateTime.now().millisecondsSinceEpoch;
notifier.create(User(id: id, name: 'New user $id'));
},
icon: const Icon(Icons.add),
label: const Text('Add'),
),
body: Column(
children: [
if (state.error != null)
MaterialBanner(
content: Text(state.error!),
actions: [
TextButton(
onPressed: notifier.clearError,
child: const Text('Dismiss'),
),
],
),
if (state.isLoading)
const Expanded(
child: Center(child: CircularProgressIndicator()),
)
else
Expanded(
child: RefreshIndicator(
onRefresh: notifier.refresh,
child: ListView.separated(
padding: const EdgeInsets.fromLTRB(16, 12, 16, 88),
itemCount: state.items.length + 1,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
if (index == state.items.length) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 16),
child: Center(
child: FilledButton.icon(
onPressed: state.hasMore && !state.isLoadingMore
? notifier.loadMore
: null,
icon: state.isLoadingMore
? const SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
),
)
: const Icon(Icons.expand_more),
label: Text(
state.hasMore ? 'Load more' : 'No more users',
),
),
),
);
}
final user = state.items[index];
return ListTile(
title: Text(user.name),
subtitle: Text('ID: ${user.id}'),
trailing: Wrap(
spacing: 4,
children: [
IconButton(
tooltip: 'Update',
onPressed: () {
notifier.update(
user.id,
user.copyWith(name: '${user.name} updated'),
);
},
icon: const Icon(Icons.edit),
),
IconButton(
tooltip: 'Delete',
onPressed: () => notifier.delete(user.id),
icon: const Icon(Icons.delete_outline),
),
],
),
);
},
),
),
),
],
),
);
}
}