riverpod_crud 0.0.2 copy "riverpod_crud: ^0.0.2" to clipboard
riverpod_crud: ^0.0.2 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:riverpod/riverpod.dart';
import 'package:riverpod_crud/riverpod_crud.dart';

void main() {
  runApp(const 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 StatefulWidget {
  const UserPage({super.key});

  @override
  State<UserPage> createState() => _UserPageState();
}

class _UserPageState extends State<UserPage> {
  late final ProviderContainer _container;
  late final ProviderSubscription<CrudState<User>> _subscription;
  late final CrudNotifier<User, int> _notifier;

  CrudState<User> _state = const CrudState<User>();

  @override
  void initState() {
    super.initState();
    _container = ProviderContainer();
    _notifier = _container.read(usersProvider.notifier);
    _subscription = _container.listen<CrudState<User>>(
      usersProvider,
      (_, next) {
        if (mounted) {
          setState(() => _state = next);
        }
      },
      fireImmediately: true,
    );
    Future<void>.microtask(
      _notifier.fetchInitial,
    );
  }

  @override
  void dispose() {
    _subscription.close();
    _container.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    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
160
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.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

riverpod

More

Packages that depend on riverpod_crud