riverpod_crud 0.0.1 copy "riverpod_crud: ^0.0.1" to clipboard
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),
                          ),
                        ],
                      ),
                    );
                  },
                ),
              ),
            ),
        ],
      ),
    );
  }
}
2
likes
150
points
106
downloads

Documentation

API reference

Publisher

verified publishercsjotlab.com

Weekly Downloads

A lightweight Riverpod CRUD helper for loading, error, refresh, pagination, create, update, and delete states.

Homepage
Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

riverpod

More

Packages that depend on riverpod_crud