### Initialize SDK and Perform CRUD Operations Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.1 Shows the setup for the Frappe client, authentication, and basic document manipulation (create, update, get, list, delete). ```dart import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; // Initialize database (required for stateless login) final database = await AppDatabase.getInstance(); // Initialize client with database final client = FrappeClient('https://your-frappe-site.com'); final authService = AuthService(); authService.initialize('https://your-frappe-site.com', database: database); // Login (stateless - tokens stored in database) final loginResponse = await authService.login('username', 'password'); // Returns: { access_token, refresh_token, user, full_name, mobile_form_names } // Restore session on app launch (keeps user logged in) final isAuthenticated = await authService.restoreSession(); // CRUD Operations final doc = await client.document.createDocument('Customer', { 'customer_name': 'John Doe', 'email': 'john@example.com', }); await client.document.updateDocument('Customer', doc['name'], { 'phone': '1234567890', }); final customer = await client.doctype.getByName('Customer', doc['name']); final customers = await client.doctype.list('Customer', fields: ['*']); await client.document.deleteDocument('Customer', doc['name']); ``` -------------------------------- ### Example pubspec.yaml Entry Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.1/install This is an example of how the `frappe_mobile_sdk` dependency will appear in your `pubspec.yaml` file after running the add command. ```yaml dependencies: frappe_mobile_sdk: ^1.1.1 ``` -------------------------------- ### Copy Example App Configuration File Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Copies the example app configuration file to the project's configuration directory. This file contains essential app constants like base URL and OAuth credentials. ```bash cp example/lib/config/app_config.example.dart example/lib/config/app_config.dart ``` -------------------------------- ### Copy Example App Config Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Copy the example configuration file to create your own app_config.dart. ```bash cp example/lib/config/app_config.example.dart example/lib/config/app_config.dart ``` -------------------------------- ### File Upload Example Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.1 Demonstrates how to upload a file using the SDK. ```dart // File Upload final file = File('/path/to/file.pdf'); final uploaded = await client.attachment.uploadFile(file); ``` -------------------------------- ### Install Frappe Mobile Control Server App Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Installs the companion Frappe Mobile Control app on your Frappe/ERPNext server using bench. This is required for mobile authentication and app status control. ```bash cd /path/to/your/frappe-bench bench get-app https://github.com/dhwani-ris/frappe-mobile-control bench install-app frappe-mobile-control bench migrate ``` -------------------------------- ### Query Builder Example Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.1 Illustrates how to construct and execute a query using the SDK's query builder. ```dart // Query Builder final todos = await client.doc('ToDo') .where('status', 'Open') .orderBy('creation', descending: true) .limit(10) .get(); ``` -------------------------------- ### Run Flutter Pub Get Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Executes the flutter pub get command to fetch and install all project dependencies, including the frappe_mobile_sdk, after updating pubspec.yaml. ```bash flutter pub get ``` -------------------------------- ### Loading Metadata and Navigating to Form Screen Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/example This example demonstrates fetching metadata for a given doctype and then navigating to a form screen to display it. It includes loading indicators and error handling. ```dart ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Loading metadata...')), ); try { final meta = await _metaService!.getMeta(doctype); if (mounted) { final ctx = context; ScaffoldMessenger.of(ctx).hideCurrentSnackBar(); Navigator.push( ctx, MaterialPageRoute( builder: (context) => FormScreen( meta: meta, repository: _repository!, syncService: _syncService!, linkOptionService: _linkOptionService, metaService: _metaService, api: _authService?.client, onSaveSuccess: () => Navigator.pop(ctx), getMobileUuid: () => _authService!.getOrCreateMobileUuid(), // style: DefaultFormStyle.material, ), ), ); } } catch (e) { if (mounted) { final ctx = context; ScaffoldMessenger.of(ctx).hideCurrentSnackBar(); ScaffoldMessenger.of(ctx).showSnackBar( SnackBar( content: Text( 'Failed to load metadata: ${e.toString().split(':').last.trim()}', ), backgroundColor: Colors.red, duration: const Duration(seconds: 5), ), ); } } ``` -------------------------------- ### Frappe Mobile SDK Initialization and API Usage Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Shows how to initialize the SDK, log in, restore sessions, and perform basic CRUD operations on documents. Includes file upload and query builder examples. ```dart import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; // Initialize database (required for stateless login) final database = await AppDatabase.getInstance(); // Initialize client with database final client = FrappeClient('https://your-frappe-site.com'); final authService = AuthService(); authService.initialize('https://your-frappe-site.com', database: database); // Login (stateless - tokens stored in database) final loginResponse = await authService.login('username', 'password'); // Returns: { access_token, refresh_token, user, full_name, mobile_form_names } // Restore session on app launch (keeps user logged in) final isAuthenticated = await authService.restoreSession(); // CRUD Operations final doc = await client.document.createDocument('Customer', { 'customer_name': 'John Doe', 'email': 'john@example.com', }); await client.document.updateDocument('Customer', doc['name'], { 'phone': '1234567890', }); final customer = await client.doctype.getByName('Customer', doc['name']); final customers = await client.doctype.list('Customer', fields: ['*']); await client.document.deleteDocument('Customer', doc['name']); // File Upload final file = File('/path/to/file.pdf'); final uploaded = await client.attachment.uploadFile(file); // Query Builder final todos = await client.doc('ToDo') .where('status', 'Open') .orderBy('creation', descending: true) .limit(10) .get(); ``` -------------------------------- ### Install Frappe Mobile Control App Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Install the companion Frappe Mobile Control app on your Frappe/ERPNext server using bench. This app provides essential APIs for mobile authentication and app status control. ```bash cd /path/to/your/frappe-bench bench get-app https://github.com/dhwani-ris/frappe-mobile-control bench install-app frappe-mobile-control bench migrate ``` -------------------------------- ### Example pubspec.yaml dependency Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/install This is how the dependency will appear in your pubspec.yaml file after running the add command. ```yaml dependencies: frappe_mobile_sdk: ^1.0.0 ``` -------------------------------- ### SQLite Installation Hint Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.0/example This conditional widget displays a message prompting the user to install SQLite if an error related to 'libsqlite3.so' is detected. This is useful for debugging environment issues. ```dart if (_errorMessage!.contains('libsqlite3.so')) const Text( 'Please install SQLite:\n sudo apt-get install libsqlite3-dev', textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.bold), ), ``` -------------------------------- ### Initialize SDK, Login, and API Access Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 This snippet shows how to initialize the SDK, perform a stateless login, restore a session, and interact with Frappe's API for document creation, retrieval, update, and deletion. It also includes an example of file upload and using the query builder. ```dart import 'dart:io'; import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; // Initialize database (required for stateless login) final database = await AppDatabase.getInstance(); // Auth service + client final authService = AuthService(); authService.initialize('https://your-frappe-site.com', database: database); // Stateless login via mobile_auth.login final loginResponse = await authService.login('username', 'password'); // Restore session on app launch final isAuthenticated = await authService.restoreSession(); // Create FrappeClient final client = FrappeClient('https://your-frappe-site.com'); await client.initialize(); // CRUD final doc = await client.document.createDocument('Customer', { 'customer_name': 'John Doe', 'email': 'john@example.com', }); await client.document.updateDocument('Customer', doc['name'], { 'phone': '1234567890', }); final customer = await client.doctype.getByName('Customer', doc['name']); final customers = await client.doctype.list('Customer', fields: ['*']); await client.document.deleteDocument('Customer', doc['name']); // File upload final file = File('/path/to/file.pdf'); final uploaded = await client.attachment.uploadFile(file); // Query builder final todos = await client .doc('ToDo') .where('status', 'Open') .orderBy('creation', descending: true) .limit(10) .get(); ``` -------------------------------- ### File Upload with Frappe SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Example of uploading a file and associating it with a specific document (e.g., Customer) using the Frappe SDK client. ```dart // File Upload client.attachment.uploadFile(file, doctype: 'Customer', docname: 'CUST-001'); ``` -------------------------------- ### Use Query Builder Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Utilize the query builder to construct and execute complex queries against your Frappe data. This example fetches 'ToDo' items with specific filtering and ordering. ```dart // Query Builder final todos = await client.doc('ToDo') .where('status', 'Open') .orderBy('creation', descending: true) .limit(10) .get(); ``` -------------------------------- ### Creating a Custom Customer Form Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Build a custom form UI using the FrappeClient for document creation. This example demonstrates a simple form with a text field and a save button. ```dart import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; class CustomCustomerForm extends StatefulWidget { const CustomCustomerForm({super.key, required this.client}); final FrappeClient client; @override State createState() => _CustomCustomerFormState(); } class _CustomCustomerFormState extends State { final Map _data = {}; @override Widget build(BuildContext context) { return Column( children: [ TextField( decoration: const InputDecoration(labelText: 'Customer Name'), onChanged: (value) => _data['customer_name'] = value, ), ElevatedButton( onPressed: () async { await widget.client.document.createDocument('Customer', _data); }, child: const Text('Save'), ), ], ); } } ``` -------------------------------- ### DocType Operations with Frappe SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Shows how to retrieve DocType metadata, list documents, and get a document by name using the Frappe SDK client. ```dart // DocType Operations client.doctype.getDocTypeMeta(doctype); client.doctype.list(doctype, fields: ['*'], filters: [...]); client.doctype.getByName(doctype, name); ``` -------------------------------- ### Display SQLite Installation Error Source: https://pub.dev/packages/frappe_mobile_sdk/example Conditionally displays an error message prompting the user to install SQLite if the error message contains 'libsqlite3.so'. This is useful for guiding users on resolving dependency issues. ```dart const SizedBox(height: 24), if (_errorMessage!.contains('libsqlite3.so')) const Text( 'Please install SQLite:\nsudo apt-get install libsqlite3-dev', textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.bold), ), ], ), ), ); } // Check authentication first (matching frappe_huf pattern) if (!_isAuthenticated) { if (_authService == null) { return const Scaffold( body: Center(child: Text('Auth service not initialized')), ); } return LoginScreen( authService: _authService!, appConfig: _appConfig, initialBaseUrl: _appConfig?.baseUrl, onLoginSuccess: _handleLoginSuccess, database: _database, passwordLogin: _sdk != null ? (u, p) => _sdk!.login(u, p) : null, sendLoginOtp: _sdk != null ? (m) => _sdk!.sendLoginOtp(m) : null, verifyLoginOtp: _sdk != null ? (t, o) => _sdk!.verifyLoginOtp(t, o) : null, ); } // After authentication, ensure services are initialized if (_authService == null || _authService!.client == null) { return const Scaffold( body: Center(child: Text('Auth service not available')), ); } // Initialize services if not already done (should happen in _handleLoginSuccess, but double-check) if (_metaService == null || _repository == null || _syncService == null || _linkOptionService == null) { if (_database != null && _authService!.client != null) { _metaService = MetaService(_authService!.client!, _database!) _repository = OfflineRepository(_database!) _syncService = SyncService( _authService!.client!, _repository!, _database!, getMobileUuid: () => _authService!.getOrCreateMobileUuid(), ) _linkOptionService = LinkOptionService(_authService!.client!) } else { return const Scaffold( body: Center( child: Text('Services not initialized. Please restart the app.'), ), ); } } if (_appConfig == null) { return const Scaffold(body: Center(child: Text('App config not loaded'))) } return Scaffold( appBar: AppBar( title: Text(config.AppConstants.appName), actions: [ // Sync status button with badge if (_syncService != null && _repository != null) IconButton( icon: Stack( children: [ const Icon(Icons.sync), // Show badge if there are dirty documents FutureBuilder( future: _getDirtyCount(), builder: (context, snapshot) { final count = snapshot.data ?? 0; if (count == 0) return const SizedBox.shrink(); return Positioned( right: 0, top: 0, child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( color: Colors.red, borderRadius: BorderRadius.circular(6), ), constraints: const BoxConstraints( minWidth: 12, minHeight: 12, ), child: Text( count > 9 ? '9+' : '$count', style: const TextStyle( color: Colors.white, fontSize: 8, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ); }, ), ], ), onPressed: () { Navigator.push( context, MaterialPageRoute( builder: (context) => SyncStatusScreen( syncService: _syncService!, repository: _repository!, ), ), ); }, tooltip: 'Sync Status', ), IconButton( icon: const Icon(Icons.logout), onPressed: _handleLogout, tooltip: 'Logout', ), ], ), body: FutureBuilder<_HomeDoctypeData>( future: _loadHomeDoctypeData(), builder: (context, snapshot) { final homeData = snapshot.data ?? ``` -------------------------------- ### Initialize and Login with Frappe SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Initializes the Frappe SDK with your site details and doctypes, then logs in a user. Tokens are automatically stored and restored. ```dart import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; // Initialize SDK (database is created automatically) final sdk = FrappeSDK( baseUrl: 'https://your-frappe-site.com', doctypes: ['Customer', 'Lead', 'Item'], ); await sdk.initialize(); // Login (stateless - tokens stored in database automatically) final loginResponse = await sdk.login('username', 'password'); // Returns: { access_token, refresh_token, user, full_name, mobile_form_names } // User stays logged in automatically - tokens persist in database // On app restart, call restoreSession() to restore login state ``` -------------------------------- ### Initialize Frappe Client and Authenticate Source: https://pub.dev/packages/frappe_mobile_sdk Initialize the FrappeClient with your Frappe site URL and a database instance. Use AuthService to log in a user and optionally restore their session on app launch. Tokens are stored in the database for stateless authentication. ```dart import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; // Initialize database (required for stateless login) final database = await AppDatabase.getInstance(); // Initialize client with database final client = FrappeClient('https://your-frappe-site.com'); final authService = AuthService(); authService.initialize('https://your-frappe-site.com', database: database); // Login (stateless - tokens stored in database) final loginResponse = await authService.login('username', 'password'); // Returns: { access_token, refresh_token, user, full_name, mobile_form_names } // Restore session on app launch (keeps user logged in) final isAuthenticated = await authService.restoreSession(); ``` -------------------------------- ### Initialize and Use FrappeSDK Source: https://pub.dev/packages/frappe_mobile_sdk Initialize FrappeSDK with a base URL and doctypes. Handles automatic database creation and stateless login. ```dart final sdk = FrappeSDK(baseUrl: '...', doctypes: ['...']); await sdk.initialize(); // Database created automatically // Login (stateless - tokens stored in database) final loginResponse = await sdk.login(username, password); // Returns: { access_token, refresh_token, user, full_name, mobile_form_names } // User stays logged in automatically // On app restart, call sdk.auth.restoreSession() to restore login state ``` -------------------------------- ### FrappeClient DocType Operations Source: https://pub.dev/packages/frappe_mobile_sdk Interact with DocTypes using FrappeClient. Retrieve DocType metadata, list documents, and get a document by name. ```dart // DocType Operations client.doctype.getDocTypeMeta(doctype); client.doctype.list(doctype, fields: ['*'], filters: [...]); client.doctype.getByName(doctype, name); ``` -------------------------------- ### Initialize and Use FrappeSDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Initialize FrappeSDK with a base URL and doctypes. Handles automatic database creation and stateless login. ```dart final sdk = FrappeSDK(baseUrl: '...', doctypes: ['...']); await sdk.initialize(); final loginResponse = await sdk.login(username, password); // On app restart, call sdk.auth.restoreSession() to restore login state ``` -------------------------------- ### Service Initialization Logic Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.0/example This code block demonstrates the initialization of various services (MetaService, OfflineRepository, SyncService, LinkOptionService) after successful authentication. It ensures all dependencies are met before proceeding. ```dart _metaService = MetaService(_authService!.client!, _database!); _repository = OfflineRepository(_database!); _syncService = SyncService( _authService!.client!, _repository!, _database!, getMobileUuid: () => _authService!.getOrCreateMobileUuid(), ); _linkOptionService = LinkOptionService(_authService!.client!); ``` -------------------------------- ### Import frappe_mobile_sdk in Dart Code Source: https://pub.dev/packages/frappe_mobile_sdk/install Import the SDK into your Dart files to start using its functionalities. This line should be placed at the top of your Dart files. ```dart import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; ``` -------------------------------- ### Initialize Frappe SDK and Auto-Restore Session Source: https://pub.dev/packages/frappe_mobile_sdk/example Initializes the Frappe SDK with base URL and database name, then attempts to auto-restore the session. This is a crucial first step for SDK usage. ```dart final sdk = FrappeSDK( baseUrl: _appConfig!.baseUrl, databaseAppName: config.AppConstants.appName, ); await sdk.initialize(true); _sdk = sdk; _database = sdk.database; _authService = sdk.auth; _metaService = sdk.meta; _permissionService = sdk.permissions; _translationService = sdk.translations; _repository = sdk.repository; _syncService = sdk.sync; _linkOptionService = sdk.linkOptions; _isAuthenticated = sdk.isAuthenticated; ``` -------------------------------- ### Build UI: Post-Authentication Service Initialization Check Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/example Ensures that authentication services are available after login. If not, it displays a message indicating the service is not available. ```dart // After authentication, ensure services are initialized if (_authService == null || _authService!.client == null) { return const Scaffold( body: Center(child: Text('Auth service not available')), ); } ``` -------------------------------- ### Initialize and Use AuthService Source: https://pub.dev/packages/frappe_mobile_sdk Initialize AuthService with a database for stateless login. Demonstrates login, session restoration, API key login, OAuth login, and logout. ```dart // Initialize with database (required for stateless login) final database = await AppDatabase.getInstance(); final authService = AuthService(); authService.initialize(baseUrl, database: database); // Login (stateless - uses mobile_auth.login API) final response = await authService.login(username, password); // Returns: { access_token, refresh_token, user, full_name, mobile_form_names } // Restore session (keeps user logged in) final isAuthenticated = await authService.restoreSession(); // API key login (alternative) await authService.loginWithApiKey(apiKey, apiSecret); // OAuth login await authService.loginWithOAuth(...); // Logout (clears tokens from database) await authService.logout(); ``` -------------------------------- ### Initialize and Use AuthService Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Initialize AuthService with a database for stateless login. Demonstrates login, session restoration, API key login, OAuth login, and logout. ```dart final database = await AppDatabase.getInstance(); final authService = AuthService(); authService.initialize(baseUrl, database: database); final response = await authService.login(username, password); final isAuthenticated = await authService.restoreSession(); await authService.loginWithApiKey(apiKey, apiSecret); await authService.loginWithOAuth(...); await authService.logout(); ``` -------------------------------- ### Initialize FrappeSDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Initializes the FrappeSDK with a base URL and optionally restores a previous session and performs an initial sync. This is a fundamental step before using other SDK features. ```dart import 'package:flutter/material.dart'; import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); final sdk = FrappeSDK(baseUrl: 'https://your-frappe-site.com/'); // autoRestoreAndSync = true tries to restore a previous session and run initial sync await sdk.initialize(true); runApp(MyApp(sdk: sdk)); } class MyApp extends StatelessWidget { final FrappeSDK sdk; const MyApp({super.key, required this.sdk}); @override Widget build(BuildContext context) { return MaterialApp( title: 'Frappe Mobile App', home: HomeScreen(sdk: sdk), ); } } ``` -------------------------------- ### Initialize Frappe SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.0/example Initializes the Frappe SDK with base URL and app name. It also handles auto-restoring sessions and initial data synchronization. ```dart final sdk = FrappeSDK( baseUrl: _appConfig!.baseUrl, databaseAppName: config.AppConstants.appName, ); await sdk.initialize(true); _sdk = sdk; _database = sdk.database; _authService = sdk.auth; _metaService = sdk.meta; _permissionService = sdk.permissions; _translationService = sdk.translations; _repository = sdk.repository; _syncService = sdk.sync; _linkOptionService = sdk.linkOptions; _isAuthenticated = sdk.isAuthenticated; ``` -------------------------------- ### Document Operations with Frappe SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Demonstrates basic document operations including create, update, delete, submit, and cancel using the Frappe SDK client. ```dart // Documents client.document.createDocument(doctype, data); client.document.updateDocument(doctype, name, data); client.document.deleteDocument(doctype, name); client.document.submitDocument(doctype, name); client.document.cancelDocument(doctype, name); ``` -------------------------------- ### Configure FrappeAppGuard and Login Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Wrap your app with `FrappeAppGuard` to check app status on launch. Configure `AppConfig` with base URL, doctypes, and OAuth credentials for login. ```dart import 'config/app_config.dart' as config; // Wrap your app with FrappeAppGuard (checks app status on launch) MaterialApp( home: FrappeAppGuard( baseUrl: config.AppConfig.baseUrl, child: YourHomeWidget(), ), ) // Use in AppConfig AppConfig( baseUrl: config.AppConfig.baseUrl, doctypes: config.AppConfig.doctypes, loginConfig: LoginConfig( enableOAuth: true, oauthClientId: config.AppConfig.oauthClientId, oauthClientSecret: config.AppConfig.oauthClientSecret, ), ) ``` -------------------------------- ### Frappe Mobile SDK Architecture Overview Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Illustrates the layered architecture of the Frappe Mobile SDK, showing its interaction with a Flutter app and the Frappe Server. ```text ┌─────────────────────────────────────────┐ │ Your Flutter App │ ├─────────────────────────────────────────┤ │ Option 1: Direct API (FrappeClient) │ │ Option 2: Form Renderer (FrappeSDK) │ │ Option 3: Custom Forms (FrappeClient) │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ Frappe Mobile SDK │ ├─────────────────────────────────────────┤ │ API Layer (FrappeClient) │ │ Services Layer (Auth, Meta, Sync) │ │ Database Layer (SQLite) │ │ UI Layer (Form Renderer) │ └─────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────┐ │ Frappe Server │ └─────────────────────────────────────────┘ ``` -------------------------------- ### Upload a File Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Demonstrates how to upload a file using the Frappe SDK. Ensure the file path is correct. ```dart // File Upload final file = File('/path/to/file.pdf'); final uploaded = await client.attachment.uploadFile(file); ``` -------------------------------- ### Initialize Frappe Mobile SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/example Initializes the Frappe SDK with base URL and app name. It also handles auto-restoring sessions and performing initial meta and data synchronization. ```dart // ignore_for_file: use_build_context_synchronously import 'package:flutter/material.dart'; import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; import 'config/app_config.dart' as config; HomeScreenLayout _homeLayoutFromConfig(String value) { switch (value.toLowerCase().trim()) { case 'folder': case 'folder view': return HomeScreenLayout.folder; case 'list': case 'list view': default: return HomeScreenLayout.list; } } class _HomeDoctypeData { final List doctypes; final Map> groups; const _HomeDoctypeData({required this.doctypes, required this.groups}); } void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: config.AppConstants.appName, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: FrappeAppGuard( baseUrl: config.AppConstants.baseUrl, currentPackageName: config.AppConstants.packageName, currentVersion: config.AppConstants.appVersion, child: const HomeScreen(), ), ); } } class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { FrappeSDK? _sdk; AppDatabase? _database; AuthService? _authService; MetaService? _metaService; PermissionService? _permissionService; TranslationService? _translationService; OfflineRepository? _repository; SyncService? _syncService; LinkOptionService? _linkOptionService; AppConfig? _appConfig; final HomeScreenLayout _homeScreenLayout = _homeLayoutFromConfig( config.AppConstants.homeScreenLayout, ); bool _isInitialized = false; bool _isAuthenticated = false; String? _errorMessage; Future _getDirtyCount() async { if (_repository == null) return 0; try { final dirty = await _repository!.getDirtyDocuments(); return dirty.length; } catch (e) { return 0; } } @override void initState() { super.initState(); _initialize(); } Future _initialize() async { try { _appConfig = AppConfig( baseUrl: config.AppConstants.baseUrl, doctypes: const [], loginConfig: LoginConfig( enableMobileLogin: true, enablePasswordLogin: true, enableOAuth: true, oauthClientId: config.AppConstants.oauthClientId, oauthClientSecret: config.AppConstants.oauthClientSecret, ), ); // Initialize SDK and auto-restore session + initial meta/data sync final sdk = FrappeSDK( baseUrl: _appConfig!.baseUrl, databaseAppName: config.AppConstants.appName, ); await sdk.initialize(true); _sdk = sdk; _database = sdk.database; _authService = sdk.auth; _metaService = sdk.meta; _permissionService = sdk.permissions; _translationService = sdk.translations; _repository = sdk.repository; _syncService = sdk.sync; _linkOptionService = sdk.linkOptions; _isAuthenticated = sdk.isAuthenticated; setState(() { _isInitialized = true; }); } catch (e) { setState(() { _isInitialized = true; _errorMessage = e.toString(); }); } } /// Perform initial metadata and data sync for mobile forms. /// /// 1. Sync doctypes from login response (checkAndSyncDoctypes). /// 2. Resync configuration from server (mobile_auth.configuration). /// 3. Pull data for all mobile form doctypes so list counts are up to date. Future _initialMetaAndDataSync() async { if (_metaService == null || _syncService == null) { return; } // Step 1: sync doctypes from stored mobile_form_names try { await _metaService!.checkAndSyncDoctypes(); } catch (_) { // Ignore, configuration step may still refresh things } // Step 2: resync configuration from server try { await _metaService!.resyncMobileConfiguration(); } catch (_) { // If this fails, we still keep the previous configuration } // Step 3: pull data for all mobile form doctypes try { final doctypes = await _metaService!.getMobileFormDoctypeNames(); for (final doctype in doctypes) { try { await _syncService!.pullSync(doctype: doctype); } catch (_) { ``` -------------------------------- ### Custom Form Implementation with FrappeClient Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Demonstrates creating a custom form widget using FrappeClient for document operations. This allows for fully custom UI while leveraging SDK APIs. ```dart // Use FrappeClient for any custom form implementation final client = FrappeSDK(...).api; // Your custom form widget class CustomCustomerForm extends StatefulWidget { @override Widget build(BuildContext context) { return Column( children: [ TextField( onChanged: (value) => _data['customer_name'] = value, ), ElevatedButton( onPressed: () async { // Use same API await client.document.createDocument('Customer', _data); }, child: Text('Save'), ), ], ); } } ``` -------------------------------- ### Build UI: Re-initialize Services if Needed Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/example Double-checks and re-initializes SDK services if they were not properly set up during the login process. This ensures all necessary components are ready before proceeding. ```dart // Initialize services if not already done (should happen in _handleLoginSuccess, but double-check) if (_metaService == null || _repository == null || _syncService == null || _linkOptionService == null) { if (_database != null && _authService!.client != null) { _metaService = MetaService(_authService!.client!, _database!); _repository = OfflineRepository(_database!); _syncService = SyncService( _authService!.client!, _repository!, _database!, getMobileUuid: () => _authService!.getOrCreateMobileUuid(), ); ``` -------------------------------- ### Handle Login Success and Initialize Services Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/example This function is called after a successful login. It initializes various services like MetaService, OfflineRepository, SyncService, and LinkOptionService. It also performs an initial metadata and data sync for mobile forms. ```dart Future _handleLoginSuccess() async { if (_authService == null || _authService!.client == null || _database == null) { return; } // Initialize services if not already done _metaService ??= MetaService(_authService!.client!, _database!); _repository ??= OfflineRepository(_database!); _syncService ??= SyncService( _authService!.client!, _repository!, _database!, ); _linkOptionService ??= LinkOptionService(_authService!.client!) // Initial metadata + data sync for mobile forms await _initialMetaAndDataSync(); setState(() { _isAuthenticated = true; }); if (_appConfig != null && _syncService != null && _metaService != null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Initial sync completed'), duration: Duration(seconds: 2), ), ); } } } ``` -------------------------------- ### Initialize Frappe Mobile SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.0/example This snippet shows the basic initialization of the Frappe SDK within a Flutter application. It sets up essential services like database, authentication, and metadata, and configures app-specific settings. ```dart // ignore_for_file: use_build_context_synchronously import 'package:flutter/material.dart'; import 'package:frappe_mobile_sdk/frappe_mobile_sdk.dart'; import 'config/app_config.dart' as config; HomeScreenLayout _homeLayoutFromConfig(String value) { switch (value.toLowerCase().trim()) { case 'folder': case 'folder view': return HomeScreenLayout.folder; case 'list': case 'list view': default: return HomeScreenLayout.list; } } DocumentListLayout _documentListLayoutFromConfig(String value) { switch (value.toLowerCase().trim()) { case 'card': case 'cards': case 'card view': return DocumentListLayout.card; case 'list': case 'list view': default: return DocumentListLayout.list; } } FormTabHeaderLayout _formTabHeaderLayoutFromConfig(String value) { switch (value.toLowerCase().trim()) { case 'stepper': case 'step': case 'steps': return FormTabHeaderLayout.stepper; case 'tabbar': case 'tabs': default: return FormTabHeaderLayout.tabBar; } } FrappeFormStyle _formStyleFromConfig() { final preset = config.AppConstants.formStylePreset.toLowerCase().trim(); final tabHeaderLayout = _formTabHeaderLayoutFromConfig( config.AppConstants.formTabHeaderLayout, ); final baseStyle = switch (preset) { 'compact' => DefaultFormStyle.compact, 'material' => DefaultFormStyle.material, _ => DefaultFormStyle.standard, }; return FrappeFormStyle( fieldDecoration: baseStyle.fieldDecoration, labelStyle: baseStyle.labelStyle, descriptionStyle: baseStyle.descriptionStyle, sectionTitleStyle: baseStyle.sectionTitleStyle, sectionMargin: baseStyle.sectionMargin, sectionPadding: baseStyle.sectionPadding, fieldPadding: baseStyle.fieldPadding, sectionTitleMaxLines: baseStyle.sectionTitleMaxLines, tabTitleMaxLines: baseStyle.tabTitleMaxLines, showFieldLabel: baseStyle.showFieldLabel, showFieldDescription: baseStyle.showFieldDescription, sectionCardColor: baseStyle.sectionCardColor, stepHeaderStyle: baseStyle.stepHeaderStyle, tabHeaderLayout: tabHeaderLayout, ); } class _HomeDoctypeData { final List doctypes; final Map> groups; const _HomeDoctypeData({required this.doctypes, required this.groups}); } void main() async { WidgetsFlutterBinding.ensureInitialized(); runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: config.AppConstants.appName, theme: ThemeData( colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue), useMaterial3: true, ), home: FrappeAppGuard( baseUrl: config.AppConstants.baseUrl, currentPackageName: config.AppConstants.packageName, currentVersion: config.AppConstants.appVersion, child: const HomeScreen(), ), ); } } class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State createState() => _HomeScreenState(); } class _HomeScreenState extends State { FrappeSDK? _sdk; AppDatabase? _database; AuthService? _authService; MetaService? _metaService; PermissionService? _permissionService; TranslationService? _translationService; OfflineRepository? _repository; SyncService? _syncService; LinkOptionService? _linkOptionService; AppConfig? _appConfig; final HomeScreenLayout _homeScreenLayout = _homeLayoutFromConfig( config.AppConstants.homeScreenLayout, ); final DocumentListStyle _documentListStyle = DocumentListStyle( layout: _documentListLayoutFromConfig( config.AppConstants.documentListLayout, ), ); final FrappeFormStyle _formStyle = _formStyleFromConfig(); final FormScreenStyle _formScreenStyle = const FormScreenStyle(); bool _isInitialized = false; bool _isAuthenticated = false; String? _errorMessage; Future _getDirtyCount() async { if (_repository == null) return 0; try { final dirty = await _repository!.getDirtyDocuments(); return dirty.length; } catch (e) { return 0; } } @override void initState() { super.initState(); _initialize(); } Future _initialize() async { try { _appConfig = AppConfig( baseUrl: config.AppConstants.baseUrl, doctypes: const [], loginConfig: LoginConfig( enableMobileLogin: true, enablePasswordLogin: true, enableOAuth: true, // Temporarily disable social login for this release. enableSocialLogin: false, ``` -------------------------------- ### Add frappe_mobile_sdk with Dart Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/install Use this command to add the SDK as a dependency in a Dart project. ```bash $ dart pub add frappe_mobile_sdk ``` -------------------------------- ### Run Pre-commit Hooks Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Execute all pre-commit hooks to ensure code quality and consistency across the project. ```bash pre-commit run --all-files ``` -------------------------------- ### Handle Login Success Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.1.0/example Initializes necessary services upon successful login and performs an initial metadata and data sync for mobile forms. Shows a snackbar upon completion. ```dart Future _handleLoginSuccess() async { if (_authService == null || _authService!.client == null || _database == null) { return; } // Initialize services if not already done _metaService ??= MetaService(_authService!.client!, _database!); _repository ??= OfflineRepository(_database!) _syncService ??= SyncService( _authService!.client!, _repository!, _database!, ); _linkOptionService ??= LinkOptionService(_authService!.client!) // Initial metadata + data sync for mobile forms await _initialMetaAndDataSync(); setState(() { _isAuthenticated = true; }); if (_appConfig != null && _syncService != null && _metaService != null) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('Initial sync completed'), duration: Duration(seconds: 2), ), ); } } } ``` -------------------------------- ### Query Builder Usage with Frappe SDK Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0 Illustrates how to use the query builder to fetch documents based on specific criteria, such as status, using the Frappe SDK client. ```dart // Query Builder client.doc('ToDo').where('status', 'Open').get(); ``` -------------------------------- ### Build and Execute Queries Source: https://pub.dev/packages/frappe_mobile_sdk Construct and execute queries using the query builder. You can specify conditions, order results, and limit the number of returned documents. ```dart final todos = await client.doc('ToDo') .where('status', 'Open') .orderBy('creation', descending: true) .limit(10) .get(); ``` -------------------------------- ### Displaying and Navigating Form Screens Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Use ready-made screens for a quick mobile experience. This snippet shows how to list mobile doctypes and navigate to document lists or new form creation. ```dart final doctypes = await sdk.meta.getMobileFormDoctypeNames(); Navigator.push( context, MaterialPageRoute( builder: (context) => DoctypeListScreen( appConfig: AppConfig( baseUrl: 'https://your-frappe-site.com', doctypes: doctypes, loginConfig: LoginConfig( enableMobileLogin: true, enablePasswordLogin: true, enableOAuth: true, oauthClientId: 'your_oauth_client_id', oauthClientSecret: 'your_oauth_client_secret', ), ), repository: sdk.repository, doctypes: doctypes, onDoctypeSelected: (doctype) async { final meta = await sdk.meta.getMeta(doctype); if (await sdk.sync.isOnline()) { await sdk.sync.pullSync(doctype: doctype); } final docs = await sdk.repository.getDocumentsByDoctype(doctype); Navigator.push( context, MaterialPageRoute( builder: (_) => DocumentListScreen( doctype: doctype, meta: meta, repository: sdk.repository, syncService: sdk.sync, metaService: sdk.meta, linkOptionService: sdk.linkOptions, api: sdk.api, getMobileUuid: () => sdk.getMobileUuid(), initialDocuments: docs, userRoles: sdk.roles, permissionService: sdk.permissions, translate: (s) => sdk.translations.translate(s), ), ), ); }, onNewDocument: (doctype) async { final meta = await sdk.meta.getMeta(doctype); Navigator.push( context, MaterialPageRoute( builder: (_) => FormScreen( meta: meta, repository: sdk.repository, syncService: sdk.sync, linkOptionService: sdk.linkOptions, metaService: sdk.meta, api: sdk.api, getMobileUuid: () => sdk.getMobileUuid(), onSaveSuccess: () => Navigator.pop(context), ), ), ); }, ), ), ); ``` -------------------------------- ### Initialize and Login with Stateless Authentication Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Initializes the AuthService with a database instance and performs a stateless login. Tokens are automatically stored in the database for persistence. ```dart // Initialize with database (required) final database = await AppDatabase.getInstance(); final authService = AuthService(); authService.initialize(baseUrl, database: database); // Login - tokens stored in database automatically final response = await authService.login(username, password); // Response includes: access_token, refresh_token, user, full_name, mobile_form_names // User stays logged in - tokens persist in database ``` -------------------------------- ### Use AppConfig in Your App Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.2.0 Import and utilize the AppConfig constants for initializing Frappe SDK components like FrappeAppGuard and LoginConfig. ```dart import 'config/app_config.dart' as config; // Wrap your app with FrappeAppGuard (checks app status on launch) MaterialApp( home: FrappeAppGuard( baseUrl: config.AppConfig.baseUrl, child: YourHomeWidget(), ), ) // Use in AppConfig AppConfig( baseUrl: config.AppConfig.baseUrl, doctypes: config.AppConfig.doctypes, loginConfig: LoginConfig( enableOAuth: true, oauthClientId: config.AppConfig.oauthClientId, oauthClientSecret: config.AppConfig.oauthClientSecret, ), ) ``` -------------------------------- ### FrappeClient Query Builder Source: https://pub.dev/packages/frappe_mobile_sdk Build and execute queries to retrieve documents using the Query Builder. ```dart // Query Builder client.doc('ToDo').where('status', 'Open').get(); ``` -------------------------------- ### Build UI: Initialization Loading State Source: https://pub.dev/packages/frappe_mobile_sdk/versions/1.0.0/example Displays a circular progress indicator while the application is initializing. ```dart if (!_isInitialized) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } ```