This new unified API system eliminates 90% of the boilerplate code while maintaining clean architecture principles.
To add a single API endpoint, you needed to create:
features/my_feature/
├── data/
│ ├── datasources/
│ │ └── my_remote_data_source.dart (50+ lines)
│ ├── models/
│ │ └── my_model.dart (30+ lines)
│ └── repositories/
│ └── my_repository_impl.dart (60+ lines)
├── domain/
│ ├── entities/
│ │ └── my_entity.dart (20+ lines)
│ ├── repositories/
│ │ └── my_repository.dart (15+ lines)
│ └── usecases/
│ ├── get_my_items.dart (15+ lines)
│ ├── create_my_item.dart (15+ lines)
│ └── delete_my_item.dart (15+ lines)
└── presentation/
├── providers/
│ ├── my_providers.dart (40+ lines)
│ └── my_state_provider.dart (80+ lines)
└── pages/
└── my_page.dart (100+ lines)
Total: ~12 files, 440+ lines of boilerplate code! 😱
Now you only need:
// 1. Define your model (20 lines)classMyItem {
finalint id;
finalString name;
MyItem({requiredthis.id, requiredthis.name});
factoryMyItem.fromJson(Map<String, dynamic> json) =>MyItem(
id: json['id'],
name: json['name'],
);
Map<String, dynamic> toJson() => {
'id': id,
'name': name,
};
}
// 2. Configure the API (5 lines)final myApiConfig =ApiConfig<MyItem>(
endpoint:'/my-items',
fromJson:MyItem.fromJson,
toJson: (item) => item.toJson(),
getId: (item) => item.id,
);
// 3. Create providers (2 lines)final myItemsProvider =ApiServiceFactory.createListProvider(myApiConfig);
final myItemProvider = (int id) =>ApiServiceFactory.createItemProvider(myApiConfig, id);
// 4. Use in your UI (30 lines)classMyPageextendsConsumerWidget {
@overrideWidgetbuild(BuildContext context, WidgetRef ref) {
final state = ref.watch(myItemsProvider);
returnScaffold(
body: state.isLoading ?CircularProgressIndicator()
:ListView.builder(
itemCount: state.items.length,
itemBuilder: (context, index) =>ListTile(
title:Text(state.items[index].name),
onTap: () => ref.read(myItemsProvider.notifier).delete(state.items[index]),
),
),
floatingActionButton:FloatingActionButton(
onPressed: () => ref.read(myItemsProvider.notifier).create({
'name':'New Item',
}),
),
);
}
}Total: 1 file, ~57 lines of actual code! 🎉
- No more data sources, repository implementations, use cases
- No more complex provider setup
- Just define your model and configuration
final notifier = ref.read(myItemsProvider.notifier);
// All these work automatically:await notifier.loadData(); // GET /my-itemsawait notifier.refresh(); // GET /my-items (with refresh indicator)await notifier.create(data); // POST /my-itemsawait notifier.update(item, data); // PUT /my-items/{id}await notifier.delete(item); // DELETE /my-items/{id}final state = ref.watch(myItemsProvider);
// Automatic state handling:
state.items; // List of your items
state.isLoading; // Loading indicator
state.isRefreshing; // Pull-to-refresh state
state.error; // Error messagesEverything is fully type-safe with generics. No casting or runtime errors.
Automatic error handling with proper user-friendly messages:
- Network timeouts
- Server errors
- Connection issues
- Validation errors
// Read-only API (GET operations only)final readOnlyConfig =ApiConfig<MyItem>(
endpoint:'/my-items',
fromJson:MyItem.fromJson,
// No toJson or getId needed
);
// Full CRUD APIfinal crudConfig =ApiConfig<MyItem>(
endpoint:'/my-items',
fromJson:MyItem.fromJson,
toJson: (item) => item.toJson(),
getId: (item) => item.id,
);classItemsPageextendsConsumerWidget {
@overrideWidgetbuild(BuildContext context, WidgetRef ref) {
final state = ref.watch(itemsProvider);
if (state.isLoading) returnLoadingWidget();
if (state.error !=null) returnErrorWidget(state.error);
returnRefreshIndicator(
onRefresh: () => ref.read(itemsProvider.notifier).refresh(),
child:ListView.builder(
itemCount: state.items.length,
itemBuilder: (context, index) =>ItemTile(state.items[index]),
),
);
}
}// Create new item
ref.read(itemsProvider.notifier).create({
'name':'New Item',
'description':'Item description',
});
// Update existing item
ref.read(itemsProvider.notifier).update(item, {
'name':'Updated Name',
});
// Delete item
ref.read(itemsProvider.notifier).delete(item);// If you need custom operations beyond CRUDfinal apiService = ref.read(itemApiServiceProvider);
// Custom endpoint callfinal result =await apiService.getList(
endpoint:'/my-items/featured',
queryParams: {'limit':10},
);Don't delete anything yet. The new system works alongside the old one.
For each feature, create a simple model with fromJson/toJson methods.
Create ApiConfig instances for your endpoints.
Use ApiServiceFactory to create providers with one line each.
Replace old provider usage with new simplified providers.
Once everything works, delete the old feature folders.
- Check out the working examples in
lib/pages/users_page.dartandlib/pages/posts_page.dart - Look at the model definitions in
lib/models/user.dartandlib/models/post.dart - Review the centralized providers in
lib/core/providers/api_providers.dart - Try creating your own API integration using the same pattern
The new system maintains all the benefits of clean architecture while dramatically reducing complexity!