flutter_next_base 0.1.0
flutter_next_base: ^0.1.0 copied to clipboard
A comprehensive Flutter package for interacting with Frappe/ERPNext REST APIs. Provides easy-to-use methods for resource operations, doctype queries, and custom method calls.
Flutter Next Base #
A comprehensive Flutter package for interacting with Frappe/ERPNext REST APIs. This package provides easy-to-use methods for resource operations, doctype queries, and custom method calls.
Features #
- π Resource Operations: Full CRUD operations for any Frappe DocType
- π DocType Operations: Get documents, field values, and update values
- π― Custom Methods: Call any custom Frappe server-side method
- π Advanced Queries: Filter, sort, and paginate resource lists
- π‘οΈ Type Safety: Strong typing with Dart generics
- π Comprehensive Logging: Built-in logging for debugging
- πͺ Cookie Support: Seamless authentication cookie management
- β‘ Result Wrapper: Clean error handling with
ApiResult<T>
Installation #
Add this to your package's pubspec.yaml file:
dependencies:
flutter_next_base:
git:
url: https://github.com/handoud/flutter_next_base.git
Or if published to pub.flutter-io.cn:
dependencies:
flutter_next_base: ^0.1.0
Then run:
flutter pub get
Quick Start #
Initialize the Client #
import 'package:flutter_next_base/flutter_next_base.dart';
final client = FlutterNextBaseClient(
baseUrl: 'https://your-frappe-site.com',
cookieManager: yourCookieManager, // Optional
);
Resource Operations #
Get a List of Resources
final result = await client.getResourceList(
'Delivery Request',
options: QueryOptions(
filters: '[["status", "=", "Open"]]',
fields: ['name', 'customer', 'delivery_date', 'status'],
orderBy: 'creation desc',
limitPageLength: 20,
limitStart: 0,
),
);
if (result.isSuccess) {
final data = result.data!['data'] as List;
print('Found ${data.length} delivery requests');
} else {
print('Error: ${result.error!.message}');
}
Get a Single Resource
final result = await client.getResource('Delivery Request', 'DR-00001');
if (result.isSuccess) {
final doc = result.data!['data'];
print('Customer: ${doc['customer']}');
}
Create a Resource
final result = await client.createResource('Delivery Request', {
'customer': 'CUST-00001',
'delivery_date': '2025-12-15',
'status': 'Open',
});
if (result.isSuccess) {
final newDoc = result.data!['data'];
print('Created: ${newDoc['name']}');
}
Update a Resource
final result = await client.updateResource(
'Delivery Request',
'DR-00001',
{'status': 'In Transit'},
);
Delete a Resource
final result = await client.deleteResource('Delivery Request', 'DR-00001');
DocType Operations #
Get Complete Document
Use getDoc when you need full document details including child tables:
final result = await client.getDoc('Delivery Trip', 'DT-00001');
if (result.isSuccess) {
final doc = result.data!['docs'][0];
print('Trip: ${doc['name']}');
print('Stops: ${doc['stops'].length}');
}
Get Field Value
final result = await client.getValue(
'User',
'user@example.com',
'full_name',
);
if (result.isSuccess) {
final value = result.data!['message']['full_name'];
print('Name: $value');
}
Set Field Value
final result = await client.setValue(
'Delivery Request',
'DR-00001',
'status',
'Completed',
);
Custom Method Calls #
For custom server-side methods created in your Frappe app:
// POST request
final result = await client.callMethod(
'delivery.api.update_status',
data: {
'delivery_request': 'DR-00001',
'status': 'In Transit',
'notes': 'Out for delivery',
},
);
// GET request
final result = await client.callMethodGet(
'delivery.api.get_statistics',
queryParams: {'date': '2025-12-14'},
);
Advanced Usage #
Filtering Resources #
Frappe uses JSON arrays for filters. Each filter is: [fieldname, operator, value]
// Single filter
filters: '[["status", "=", "Open"]]'
// Multiple filters (AND condition)
filters: '[["status", "=", "Open"], ["delivery_date", ">=", "2025-12-01"]]'
// Using different operators
filters: '[["amount", ">", 1000], ["customer", "like", "%Corp%"]]'
Available Operators: =, !=, >, <, >=, <=, like, not like, in, not in, is, is not
Pagination #
final options = QueryOptions(
limitPageLength: 20, // Records per page
limitStart: 0, // Starting index (0 for first page, 20 for second, etc.)
);
// For second page:
limitStart: 20
Sorting #
// Ascending
orderBy: 'creation asc'
// Descending
orderBy: 'creation desc'
// Multiple fields
orderBy: 'status asc, creation desc'
Field Selection #
Reduce response size by selecting specific fields:
fields: ['name', 'customer', 'status'] // Only these fields will be returned
Error Handling #
final result = await client.getResource('Delivery Request', 'DR-00001');
if (result.isSuccess) {
// Handle success
final doc = result.data!['data'];
print(doc);
} else {
// Handle error
final error = result.error!;
print('Error: ${error.message}');
print('Status Code: ${error.statusCode}');
print('Data: ${error.data}');
}
Result Mapping #
Transform API responses easily:
final result = await client.getResource('Delivery Request', 'DR-00001');
final mappedResult = result.map((data) {
final doc = data['data'] as Map<String, dynamic>;
return DeliveryRequest.fromJson(doc);
});
if (mappedResult.isSuccess) {
final deliveryRequest = mappedResult.data!;
print(deliveryRequest.customer);
}
Cookie Management #
Implement the CookieManager interface for authentication:
class MyCookieManager implements CookieManager {
final FlutterSecureStorage _storage;
MyCookieManager(this._storage);
@override
Future<String> getCookies(String url) async {
final sid = await _storage.read(key: 'sid');
final userId = await _storage.read(key: 'user_id');
if (sid != null && userId != null) {
return 'sid=$sid; user_id=$userId';
}
return '';
}
@override
Future<void> saveCookies(String url, List<String> cookies) async {
for (final cookie in cookies) {
if (cookie.startsWith('sid=')) {
final sid = cookie.split(';')[0].split('=')[1];
await _storage.write(key: 'sid', value: sid);
}
if (cookie.startsWith('user_id=')) {
final userId = cookie.split(';')[0].split('=')[1];
await _storage.write(key: 'user_id', value: userId);
}
}
}
}
// Use it:
final client = FlutterNextBaseClient(
baseUrl: 'https://your-site.com',
cookieManager: MyCookieManager(secureStorage),
);
Logging #
Enable logging to debug API calls:
import 'package:logging/logging.dart';
void setupLogging() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
print('${record.level.name}: ${record.time}: ${record.message}');
});
}
API Reference #
FlutterNextBaseClient #
Main client class for API operations.
Constructor:
baseUrl: Your Frappe site URLcookieManager: Optional cookie manager for authenticationdefaultHeaders: Optional default headers for all requestsclient: Optional custom HTTP client
Resource Methods:
getResourceList(docType, {options}): Get list of resourcesgetResource(docType, name): Get single resourcecreateResource(docType, data): Create new resourceupdateResource(docType, name, data): Update resourcedeleteResource(docType, name): Delete resource
DocType Methods:
getDoc(docType, name): Get complete document with child tablesgetValue(docType, name, fieldName): Get specific field valuesetValue(docType, name, fieldName, value): Set field value
Custom Methods:
callMethod(methodPath, {data, queryParams}): POST to custom methodcallMethodGet(methodPath, {queryParams}): GET to custom method
QueryOptions #
Options for filtering and sorting resource queries.
Properties:
filters: JSON filter stringfields: List of field names to returnorderBy: Sort orderlimitPageLength: Records per pagelimitStart: Starting index for pagination
ApiResult #
Result wrapper for API operations.
Properties:
data: The returned data (if successful)error: The error (if failed)isSuccess: Whether operation succeededisError: Whether operation failed
Methods:
map<R>(mapper): Transform the data to another type
FrappeException #
Exception thrown when API request fails.
Properties:
message: Error messagestatusCode: HTTP status codedata: Additional error data
Common Use Cases #
1. Building a List Screen with Pagination #
class DeliveryListScreen extends StatefulWidget {
@override
_DeliveryListScreenState createState() => _DeliveryListScreenState();
}
class _DeliveryListScreenState extends State<DeliveryListScreen> {
List<Map<String, dynamic>> deliveries = [];
int currentPage = 0;
final pageSize = 20;
Future<void> loadDeliveries() async {
final result = await client.getResourceList(
'Delivery Request',
options: QueryOptions(
filters: '[["status", "!=", "Cancelled"]]',
fields: ['name', 'customer', 'status', 'delivery_date'],
orderBy: 'creation desc',
limitPageLength: pageSize,
limitStart: currentPage * pageSize,
),
);
if (result.isSuccess) {
setState(() {
deliveries.addAll(
List<Map<String, dynamic>>.from(result.data!['data']),
);
currentPage++;
});
}
}
}
2. Updating Status with Custom Method #
Future<void> updateDeliveryStatus(String requestId, String status) async {
final result = await client.callMethod(
'delivery.api.update_status',
data: {
'delivery_request': requestId,
'status': status,
'timestamp': DateTime.now().toIso8601String(),
},
);
if (result.isSuccess) {
print('Status updated successfully');
} else {
print('Failed: ${result.error!.message}');
}
}
3. Search with Filters #
Future<List<Map<String, dynamic>>> searchCustomers(String query) async {
final result = await client.getResourceList(
'Customer',
options: QueryOptions(
filters: '[["customer_name", "like", "%$query%"]]',
fields: ['name', 'customer_name', 'email'],
limitPageLength: 10,
),
);
if (result.isSuccess) {
return List<Map<String, dynamic>>.from(result.data!['data']);
}
return [];
}
Frappe API Documentation #
For more information about Frappe's REST API:
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request to https://github.com/handoud/flutter_next_base.
License #
This project is licensed under the MIT License - see the LICENSE file for details.
Support #
For issues, questions, or contributions, please visit: https://github.com/handoud/flutter_next_base/issues