prodobit_flutter_sdk 0.11.3 copy "prodobit_flutter_sdk: ^0.11.3" to clipboard
prodobit_flutter_sdk: ^0.11.3 copied to clipboard

Flutter SDK for Prodobit framework - Build manufacturing and business applications with modern Flutter patterns

Prodobit Flutter SDK #

A modern, type-safe Flutter SDK for building manufacturing and business applications with the Prodobit framework.

🚀 Features #

  • Modern Architecture: Built with latest Flutter patterns and best practices
  • Type Safety: Full TypeScript-equivalent type safety with Freezed models
  • State Management: Riverpod for reactive state management
  • Authentication: Secure JWT-based authentication with token refresh
  • Networking: Robust HTTP client with error handling and retry logic
  • Caching: Intelligent caching for better performance
  • File Upload: Built-in file upload with progress tracking
  • Multi-tenancy: Organization context management
  • Error Handling: Comprehensive error handling with custom exceptions

📦 Installation #

Add to your pubspec.yaml:

dependencies:
  prodobit_flutter_sdk: ^0.1.0
  flutter_riverpod: ^2.5.1

🛠 Setup #

1. Initialize the Client #

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:prodobit_flutter_sdk/prodobit_flutter_sdk.dart';

// Override the client provider with your configuration
final prodobitClientProvider = Provider<ProdobitClient>((ref) {
  return ProdobitClient(
    baseUrl: 'https://your-api.prodobit.com',
    enableLogging: kDebugMode,
  );
});

2. Wrap Your App #

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

void main() {
  runApp(
    ProviderScope(
      child: MyApp(),
    ),
  );
}

class MyApp extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      title: 'My Manufacturing App',
      home: AuthWrapper(),
    );
  }
}

3. Initialize Authentication #

class AuthWrapper extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final authState = ref.watch(authProvider);
    
    return authState.when(
      data: (state) => state.when(
        authenticated: (user, org) => HomeScreen(),
        unauthenticated: () => LoginScreen(),
        loading: () => LoadingScreen(),
        error: (message) => ErrorScreen(message: message),
        initial: () => SplashScreen(),
      ),
      loading: () => LoadingScreen(),
      error: (error, stack) => ErrorScreen(message: error.toString()),
    );
  }
}

🔐 Authentication #

OTP Email Authentication #

Prodobit uses OTP (One-Time Password) email authentication. Users receive a verification code via email.

class LoginScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: Text('Login')),
      body: LoginFlow(),
    );
  }
}

class LoginFlow extends ConsumerStatefulWidget {
  @override
  ConsumerState<LoginFlow> createState() => _LoginFlowState();
}

class _LoginFlowState extends ConsumerState<LoginFlow> {
  final _emailController = TextEditingController();
  final _otpController = TextEditingController();
  bool _otpSent = false;

  @override
  Widget build(BuildContext context) {
    final authNotifier = ref.read(authProvider.notifier);
    
    return Padding(
      padding: EdgeInsets.all(16),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          if (!_otpSent) ...[
            // Email input step
            TextField(
              controller: _emailController,
              decoration: InputDecoration(
                labelText: 'Email Address',
                hintText: 'Enter your email',
              ),
              keyboardType: TextInputType.emailAddress,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                try {
                  await authNotifier.sendOTP(_emailController.text);
                  setState(() {
                    _otpSent = true;
                  });
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('OTP sent to your email')),
                  );
                } catch (e) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Failed to send OTP: $e')),
                  );
                }
              },
              child: Text('Send OTP'),
            ),
          ] else ...[
            // OTP verification step
            Text(
              'Enter the verification code sent to ${_emailController.text}',
              textAlign: TextAlign.center,
              style: TextStyle(fontSize: 16),
            ),
            SizedBox(height: 20),
            TextField(
              controller: _otpController,
              decoration: InputDecoration(
                labelText: 'Verification Code',
                hintText: 'Enter 6-digit code',
              ),
              keyboardType: TextInputType.number,
              maxLength: 6,
            ),
            SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                try {
                  await authNotifier.verifyOTP(
                    email: _emailController.text,
                    otp: _otpController.text,
                  );
                } catch (e) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Invalid OTP: $e')),
                  );
                }
              },
              child: Text('Verify & Login'),
            ),
            SizedBox(height: 10),
            TextButton(
              onPressed: () async {
                try {
                  await authNotifier.sendOTP(_emailController.text);
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('New OTP sent')),
                  );
                } catch (e) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Failed to resend OTP: $e')),
                  );
                }
              },
              child: Text('Resend OTP'),
            ),
            TextButton(
              onPressed: () {
                setState(() {
                  _otpSent = false;
                  _otpController.clear();
                });
              },
              child: Text('Change Email'),
            ),
          ],
        ],
      ),
    );
  }
}

Token Management #

After successful OTP verification, the SDK automatically handles JWT tokens:

// Tokens are automatically stored securely and refreshed when needed
// No manual token management required

Logout #

ElevatedButton(
  onPressed: () async {
    final authNotifier = ref.read(authProvider.notifier);
    await authNotifier.logout();
  },
  child: Text('Logout'),
),

📦 Inventory Management #

List Items #

class ItemsScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final itemsAsync = ref.watch(itemsProvider());
    
    return Scaffold(
      appBar: AppBar(title: Text('Items')),
      body: itemsAsync.when(
        data: (paginatedItems) => ListView.builder(
          itemCount: paginatedItems.data.length,
          itemBuilder: (context, index) {
            final item = paginatedItems.data[index];
            return ListTile(
              title: Text(item.displayName),
              subtitle: Text(item.description ?? ''),
              leading: item.hasImage 
                ? Image.network(item.imageUrl!)
                : Icon(Icons.inventory),
              onTap: () => Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ItemDetailScreen(itemId: item.id),
                ),
              ),
            );
          },
        ),
        loading: () => Center(child: CircularProgressIndicator()),
        error: (error, stack) => ErrorWidget(error),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => CreateItemScreen()),
        ),
        child: Icon(Icons.add),
      ),
    );
  }
}

Create Item #

class CreateItemScreen extends ConsumerWidget {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _codeController = TextEditingController();
  final _descriptionController = TextEditingController();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final operations = ref.read(inventoryOperationsProvider.notifier);
    
    return Scaffold(
      appBar: AppBar(title: Text('Create Item')),
      body: Form(
        key: _formKey,
        child: Padding(
          padding: EdgeInsets.all(16),
          child: Column(
            children: [
              TextFormField(
                controller: _nameController,
                decoration: InputDecoration(labelText: 'Name'),
                validator: (value) => Validators.required(value, fieldName: 'Name'),
              ),
              TextFormField(
                controller: _codeController,
                decoration: InputDecoration(labelText: 'Code'),
                validator: Validators.itemCode,
              ),
              TextFormField(
                controller: _descriptionController,
                decoration: InputDecoration(labelText: 'Description'),
                maxLines: 3,
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () async {
                  if (_formKey.currentState!.validate()) {
                    try {
                      await operations.createItem(
                        ItemRequest(
                          name: _nameController.text,
                          code: _codeController.text,
                          description: _descriptionController.text.isEmpty 
                            ? null : _descriptionController.text,
                          type: ItemTypes.product,
                          unit: Defaults.unit,
                        ),
                      );
                      Navigator.pop(context);
                    } catch (e) {
                      ScaffoldMessenger.of(context).showSnackBar(
                        SnackBar(content: Text('Error: ${e.toString()}')),
                      );
                    }
                  }
                },
                child: Text('Create Item'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

Stock Management #

class StockScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final lowStockAsync = ref.watch(lowStockItemsProvider);
    
    return Scaffold(
      appBar: AppBar(title: Text('Stock Management')),
      body: Column(
        children: [
          // Low Stock Alert
          Container(
            padding: EdgeInsets.all(16),
            color: Colors.orange.shade100,
            child: lowStockAsync.when(
              data: (lowStockItems) => lowStockItems.isEmpty
                ? Text('All items are well stocked')
                : Text('${lowStockItems.length} items are low on stock'),
              loading: () => Text('Checking stock levels...'),
              error: (_, __) => Text('Error checking stock levels'),
            ),
          ),
          
          // Stock Actions
          Expanded(
            child: GridView.count(
              crossAxisCount: 2,
              padding: EdgeInsets.all(16),
              children: [
                _buildActionCard(
                  icon: Icons.add_box,
                  title: 'Stock In',
                  onTap: () => _showStockAdjustment(context, ref, isIncrease: true),
                ),
                _buildActionCard(
                  icon: Icons.remove_circle,
                  title: 'Stock Out', 
                  onTap: () => _showStockAdjustment(context, ref, isIncrease: false),
                ),
                _buildActionCard(
                  icon: Icons.swap_horiz,
                  title: 'Transfer',
                  onTap: () => _showStockTransfer(context, ref),
                ),
                _buildActionCard(
                  icon: Icons.history,
                  title: 'Movements',
                  onTap: () => Navigator.push(
                    context,
                    MaterialPageRoute(
                      builder: (context) => StockMovementsScreen(),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildActionCard({
    required IconData icon,
    required String title,
    required VoidCallback onTap,
  }) {
    return Card(
      child: InkWell(
        onTap: onTap,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(icon, size: 48),
            SizedBox(height: 8),
            Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
          ],
        ),
      ),
    );
  }

  void _showStockAdjustment(
    BuildContext context, 
    WidgetRef ref, {
    required bool isIncrease,
  }) {
    // Implementation for stock adjustment dialog
  }

  void _showStockTransfer(BuildContext context, WidgetRef ref) {
    // Implementation for stock transfer dialog
  }
}

🔍 Advanced Usage #

Custom Error Handling #

class CustomErrorHandler {
  static void handle(Object error, WidgetRef ref) {
    if (error is AuthException) {
      // Handle authentication errors
      ref.read(authProvider.notifier).logout();
      // Navigate to login
    } else if (error is ApiException) {
      // Handle API errors
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(error.message)),
      );
    } else {
      // Handle other errors
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('An unexpected error occurred')),
      );
    }
  }
}

File Upload #

class FileUploadWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return ElevatedButton(
      onPressed: () async {
        final picker = ImagePicker();
        final image = await picker.pickImage(source: ImageSource.gallery);
        
        if (image != null) {
          try {
            final client = ref.read(prodobitClientProvider);
            final uploadResponse = await client.file.uploadFile(
              image.path,
              onProgress: (progress) {
                // Update progress indicator
                print('Upload progress: ${(progress * 100).toInt()}%');
              },
            );
            
            print('File uploaded: ${uploadResponse.url}');
          } catch (e) {
            print('Upload failed: $e');
          }
        }
      },
      child: Text('Upload Image'),
    );
  }
}

Search & Filtering #

class ItemSearchScreen extends ConsumerStatefulWidget {
  @override
  ConsumerState<ItemSearchScreen> createState() => _ItemSearchScreenState();
}

class _ItemSearchScreenState extends ConsumerState<ItemSearchScreen> {
  final _searchController = TextEditingController();
  String _searchQuery = '';

  @override
  Widget build(BuildContext context) {
    final itemsAsync = ref.watch(itemsProvider(
      query: QueryParams(
        search: _searchQuery,
        limit: 50,
      ),
    ));

    return Scaffold(
      appBar: AppBar(
        title: TextField(
          controller: _searchController,
          decoration: InputDecoration(
            hintText: 'Search items...',
            border: InputBorder.none,
          ),
          onChanged: (value) {
            setState(() {
              _searchQuery = value;
            });
          },
        ),
      ),
      body: itemsAsync.when(
        data: (paginatedItems) => ListView.builder(
          itemCount: paginatedItems.data.length,
          itemBuilder: (context, index) {
            final item = paginatedItems.data[index];
            return ListTile(
              title: Text(item.displayName),
              subtitle: Text(item.description ?? ''),
            );
          },
        ),
        loading: () => Center(child: CircularProgressIndicator()),
        error: (error, stack) => ErrorWidget(error),
      ),
    );
  }
}

📝 Best Practices #

1. Error Handling #

Always wrap async operations in try-catch blocks and handle different exception types appropriately.

2. State Management #

Use Riverpod providers consistently and invalidate them when data changes.

3. Type Safety #

Leverage Freezed models for type safety and use the built-in validators for form validation.

4. Performance #

Use pagination for large datasets and implement proper loading states.

5. Security #

Never store sensitive data in plain text. The SDK handles token storage securely.

🏗 Architecture #

The SDK follows clean architecture principles:

  • Core: API client, configuration, and base types
  • Models: Freezed data models with JSON serialization
  • Services: Business logic and API interactions
  • Providers: Riverpod state management
  • Utils: Helpers, extensions, and utilities

🤝 Contributing #

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

📄 License #

This project is licensed under the MIT License - see the LICENSE file for details.

🆘 Support #

For support, please visit Prodobit Documentation or open an issue on GitHub.


Built with ❤️ by the Prodobit team

0
likes
0
points
88
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for Prodobit framework - Build manufacturing and business applications with modern Flutter patterns

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

collection, dio, equatable, flutter, flutter_riverpod, flutter_secure_storage, freezed_annotation, hive, hive_flutter, http_parser, json_annotation, logger, meta, mime, pretty_dio_logger, retrofit, riverpod_annotation, rxdart, shared_preferences, stack_trace

More

Packages that depend on prodobit_flutter_sdk