Dart SDK
The official Dart SDK provides full type safety, async support, and convenient methods for all API operations. Ideal for Flutter mobile and web applications.
Installation
Section titled “Installation”Add to your pubspec.yaml:
dependencies: mando_sdk: ^1.0.0Then run:
flutter pub get# ordart pub getRequirements
Section titled “Requirements”- Dart 3.0+
- Flutter 3.10+ (for Flutter apps)
Quick Start
Section titled “Quick Start”import 'dart:io';import 'package:mando_sdk/mando_sdk.dart';
void main() async { final client = MandoClient( apiKey: Platform.environment['MANDO_API_KEY']!, baseUrl: 'https://www.mando.fi/api', // optional, this is the default );
// List all products final products = await client.plu.list(); print(products.data);}Configuration
Section titled “Configuration”final client = MandoClient( // Required apiKey: 'your-api-key',
// Optional baseUrl: 'https://www.mando.fi/api', timeout: Duration(seconds: 30), retries: 3, retryDelay: Duration(seconds: 1),);CRUD Operations
Section titled “CRUD Operations”List Entities
Section titled “List Entities”// List all productsfinal products = await client.plu.list();
// With paginationfinal page = await client.plu.list( page: 1, pageSize: 50,);
// With filtersfinal active = await client.plu.list( active: true, groupId: 'beverages',);Get Single Entity
Section titled “Get Single Entity”final product = await client.plu.get('550e8400-e29b-41d4-a716-446655440000');print(product.data.name);Create Entity
Section titled “Create Entity”final newProduct = await client.plu.create({ 'name': 'Espresso', 'price': 3.50, 'groupId': 'beverages', 'taxId': 'standard-vat',});print(newProduct.data.id);Update Entity
Section titled “Update Entity”final updated = await client.plu.update('product-id', { 'price': 4.00, 'active': true,});Error Handling
Section titled “Error Handling”import 'package:mando_sdk/mando_sdk.dart';
try { final product = await client.plu.get('invalid-id');} on MandoError catch (e) { print('Error ${e.status}: ${e.message}'); print('Code: ${e.code}');
// Handle specific errors switch (e.status) { case 404: print('Product not found'); break; case 429: // Rate limited - retry after delay final retryAfter = e.retryAfter ?? 60; print('Rate limited. Retry after $retryAfter seconds'); await Future.delayed(Duration(seconds: retryAfter)); break; }} catch (e) { print('Unexpected error: $e');}Pagination Helper
Section titled “Pagination Helper”Iterate through all pages automatically:
// Stream for all productsawait for (final product in client.plu.listAll()) { print(product.name);}
// Or collect all at oncefinal allProducts = await client.plu.listAll().toList();Type Safety
Section titled “Type Safety”All responses are fully typed:
import 'package:mando_sdk/mando_sdk.dart';
Future<List<String>> getProductNames(MandoClient client) async { final response = await client.plu.list(); return response.data.map((plu) => plu.name).toList();}Flutter Usage
Section titled “Flutter Usage”Provider Setup
Section titled “Provider Setup”import 'package:flutter/material.dart';import 'package:provider/provider.dart';import 'package:mando_sdk/mando_sdk.dart';
void main() { runApp( Provider<MandoClient>( create: (_) => MandoClient( apiKey: const String.fromEnvironment('MANDO_API_KEY'), ), child: const MyApp(), ), );}
class ProductListScreen extends StatelessWidget { const ProductListScreen({super.key});
@override Widget build(BuildContext context) { final client = context.read<MandoClient>();
return FutureBuilder( future: client.plu.list(), builder: (context, snapshot) { if (snapshot.hasError) { return Center(child: Text('Error: ${snapshot.error}')); } if (!snapshot.hasData) { return const Center(child: CircularProgressIndicator()); }
final products = snapshot.data!.data; return ListView.builder( itemCount: products.length, itemBuilder: (context, index) { final product = products[index]; return ListTile( title: Text(product.name), subtitle: Text('\$${product.price.toStringAsFixed(2)}'), ); }, ); }, ); }}Riverpod Setup
Section titled “Riverpod Setup”import 'package:flutter_riverpod/flutter_riverpod.dart';import 'package:mando_sdk/mando_sdk.dart';
final mandoClientProvider = Provider<MandoClient>((ref) { return MandoClient( apiKey: const String.fromEnvironment('MANDO_API_KEY'), );});
final productsProvider = FutureProvider<List<Plu>>((ref) async { final client = ref.watch(mandoClientProvider); final response = await client.plu.list(); return response.data;});
class ProductListScreen extends ConsumerWidget { const ProductListScreen({super.key});
@override Widget build(BuildContext context, WidgetRef ref) { final productsAsync = ref.watch(productsProvider);
return productsAsync.when( data: (products) => ListView.builder( itemCount: products.length, itemBuilder: (context, index) => ListTile( title: Text(products[index].name), ), ), loading: () => const Center(child: CircularProgressIndicator()), error: (error, stack) => Center(child: Text('Error: $error')), ); }}Advanced Usage
Section titled “Advanced Usage”Custom Headers
Section titled “Custom Headers”final client = MandoClient( apiKey: 'your-key', headers: { 'X-Custom-Header': 'value', },);Request Interceptors
Section titled “Request Interceptors”final client = MandoClient( apiKey: 'your-key', onRequest: (request) { print('Making request to ${request.url}'); return request; }, onResponse: (response) { print('Received ${response.statusCode}'); return response; },);Cancel Requests
Section titled “Cancel Requests”import 'package:dio/dio.dart';
final cancelToken = CancelToken();
// Cancel after 5 secondsFuture.delayed(Duration(seconds: 5), () { cancelToken.cancel('Timeout');});
try { final products = await client.plu.list(cancelToken: cancelToken);} on DioException catch (e) { if (CancelToken.isCancel(e)) { print('Request was cancelled'); }}Example: Product Search Screen
Section titled “Example: Product Search Screen”import 'package:flutter/material.dart';import 'package:mando_sdk/mando_sdk.dart';
class ProductSearchScreen extends StatefulWidget { final MandoClient client;
const ProductSearchScreen({super.key, required this.client});
@override State<ProductSearchScreen> createState() => _ProductSearchScreenState();}
class _ProductSearchScreenState extends State<ProductSearchScreen> { final _searchController = TextEditingController(); List<Plu> _products = []; bool _loading = false;
Future<void> _search(String query) async { setState(() => _loading = true);
try { final response = await widget.client.plu.list( search: query, active: true, ); setState(() => _products = response.data); } on MandoError catch (e) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Error: ${e.message}')), ); } finally { setState(() => _loading = false); } }
@override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Product Search')), body: Column( children: [ Padding( padding: const EdgeInsets.all(16), child: TextField( controller: _searchController, decoration: const InputDecoration( hintText: 'Search products...', prefixIcon: Icon(Icons.search), ), onSubmitted: _search, ), ), if (_loading) const Center(child: CircularProgressIndicator()) else Expanded( child: ListView.builder( itemCount: _products.length, itemBuilder: (context, index) { final product = _products[index]; return ListTile( title: Text(product.name), subtitle: Text('\$${product.price.toStringAsFixed(2)}'), trailing: product.active ? const Icon(Icons.check_circle, color: Colors.green) : const Icon(Icons.cancel, color: Colors.red), ); }, ), ), ], ), ); }}