flutter_data_kit 1.0.0 copy "flutter_data_kit: ^1.0.0" to clipboard
flutter_data_kit: ^1.0.0 copied to clipboard

Backend contracts, PagedList, cacheFor, and repository patterns for Lemsa Flutter apps. Vendor SDKs live in adapter packages.

example/lib/main.dart

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_data_kit/flutter_data_kit.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

void main() {
  runApp(const ProviderScope(child: DemoApp()));
}

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: DemoListPage(),
    );
  }
}

@immutable
class DemoItem implements Identifiable {
  const DemoItem({required this.id, required this.title});

  @override
  final String id;
  final String title;

  DemoItem copyWith({String? title}) =>
      DemoItem(id: id, title: title ?? this.title);

  @override
  bool operator ==(Object other) =>
      other is DemoItem && other.id == id && other.title == title;

  @override
  int get hashCode => Object.hash(id, title);
}

class DemoQuery extends PagedQuery {
  const DemoQuery({
    super.page,
    super.pageSize = 12,
    this.search = '',
  });

  final String search;
}

class FakeDemoSource implements PagedSource<DemoItem, DemoQuery> {
  FakeDemoSource() {
    _all = [
      for (var i = 1; i <= 40; i++) DemoItem(id: '$i', title: 'Item $i'),
    ];
  }

  late final List<DemoItem> _all;

  @override
  Future<PagedResult<DemoItem>> fetch(DemoQuery query) async {
    await Future<void>.delayed(const Duration(milliseconds: 250));
    final q = query.search.trim().toLowerCase();
    final filtered =
        q.isEmpty
            ? _all
            : _all.where((e) => e.title.toLowerCase().contains(q)).toList();
    final start = (query.page - 1) * query.pageSize;
    if (start >= filtered.length) {
      return const PagedResult(items: [], hasMore: false);
    }
    final end = (start + query.pageSize).clamp(0, filtered.length);
    return PagedResult(
      items: filtered.sublist(start, end),
      hasMore: end < filtered.length,
    );
  }
}

final demoSourceProvider = Provider<PagedSource<DemoItem, DemoQuery>>(
  (ref) => FakeDemoSource(),
);

final demoListProvider = AsyncNotifierProvider<DemoList, PagedState<DemoItem>>(
  DemoList.new,
);

class DemoList extends AsyncNotifier<PagedState<DemoItem>>
    with PagedList<DemoItem, DemoQuery> {
  @override
  PagedSource<DemoItem, DemoQuery> get source => ref.watch(demoSourceProvider);

  @override
  DemoQuery get initialQuery => const DemoQuery();

  @override
  DemoQuery queryWithPage(DemoQuery query, int page) {
    return DemoQuery(
      page: page,
      pageSize: query.pageSize,
      search: query.search,
    );
  }

  @override
  Future<PagedState<DemoItem>> build() => fetchFirstPage();
}

class DemoListPage extends ConsumerWidget {
  const DemoListPage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final async = ref.watch(demoListProvider);
    final notifier = ref.read(demoListProvider.notifier);

    return Scaffold(
      appBar: AppBar(title: const Text('flutter_data_kit PagedList')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: TextField(
              decoration: const InputDecoration(
                hintText: 'Filter (server-side setQuery)',
                border: OutlineInputBorder(),
              ),
              onSubmitted: (value) {
                unawaited(notifier.setQuery(DemoQuery(search: value)));
              },
            ),
          ),
          Expanded(
            child: async.when(
              loading: () => const Center(child: CircularProgressIndicator()),
              error: (e, _) => Center(child: Text('$e')),
              data: (page) {
                return ListView.builder(
                  itemCount: page.items.length + (page.hasMore ? 1 : 0),
                  itemBuilder: (context, index) {
                    if (index == page.items.length) {
                      return TextButton(
                        onPressed:
                            page.isLoadingMore
                                ? null
                                : () => unawaited(notifier.loadMore()),
                        child: Text(
                          page.isLoadingMore ? 'Loading…' : 'Load more',
                        ),
                      );
                    }
                    final item = page.items[index];
                    return ListTile(
                      title: Text(item.title),
                      trailing: IconButton(
                        icon: const Icon(Icons.delete_outline),
                        onPressed: () => notifier.removeById(item.id),
                      ),
                      onTap:
                          () => notifier.patch(
                            item.id,
                            (current) =>
                                current.copyWith(title: '${current.title} ✓'),
                          ),
                    );
                  },
                );
              },
            ),
          ),
        ],
      ),
      floatingActionButton: FloatingActionButton(
        onPressed:
            () => notifier.upsert(
              DemoItem(
                id: DateTime.now().millisecondsSinceEpoch.toString(),
                title: 'Local upsert',
              ),
            ),
        child: const Icon(Icons.add),
      ),
    );
  }
}
0
likes
150
points
--
downloads

Documentation

Documentation
API reference

Publisher

unverified uploader

Backend contracts, PagedList, cacheFor, and repository patterns for Lemsa Flutter apps. Vendor SDKs live in adapter packages.

Repository (GitHub)
View/report issues

Topics

#flutter #pagination #riverpod #repository

License

MIT (license)

Dependencies

flutter, flutter_riverpod, lemsa_core_kit

More

Packages that depend on flutter_data_kit