dynamic_schema_mapper 0.1.0+2 copy "dynamic_schema_mapper: ^0.1.0+2" to clipboard
dynamic_schema_mapper: ^0.1.0+2 copied to clipboard

Automatically map dynamic backend JSON to type-safe Dart structures at runtime without model classes. Perfect for evolving APIs.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:dynamic_schema_mapper/dynamic_schema_mapper.dart';
import 'dart:convert';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Dynamic Schema Mapper Demo',
      theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
      home: const HomeScreen(),
      debugShowCheckedModeBanner: false,
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dynamic Schema Mapper Examples'),
        elevation: 2,
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildExampleCard(
            context,
            'Basic Usage',
            'Simple JSON parsing with type-safe getters',
            Icons.code,
            Colors.blue,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const BasicUsageScreen()),
            ),
          ),
          const SizedBox(height: 16),
          _buildExampleCard(
            context,
            'Nested Objects',
            'Access deeply nested JSON structures',
            Icons.account_tree,
            Colors.green,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const NestedObjectsScreen()),
            ),
          ),
          const SizedBox(height: 16),
          _buildExampleCard(
            context,
            'Lists & Arrays',
            'Working with lists of objects',
            Icons.list,
            Colors.orange,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const ListsScreen()),
            ),
          ),
          const SizedBox(height: 16),
          _buildExampleCard(
            context,
            'Schema Detection',
            'Detect backend schema changes',
            Icons.notifications_active,
            Colors.red,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const SchemaDetectionScreen()),
            ),
          ),
          const SizedBox(height: 16),
          _buildExampleCard(
            context,
            'Real-World Example',
            'E-commerce product listing',
            Icons.shopping_cart,
            Colors.purple,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const ProductListingScreen()),
            ),
          ),
          const SizedBox(height: 16),
          _buildExampleCard(
            context,
            'User Dashboard',
            'Complex multi-source data',
            Icons.dashboard,
            Colors.teal,
            () => Navigator.push(
              context,
              MaterialPageRoute(builder: (_) => const UserDashboardScreen()),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildExampleCard(
    BuildContext context,
    String title,
    String description,
    IconData icon,
    Color color,
    VoidCallback onTap,
  ) {
    return Card(
      elevation: 2,
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: color.withValues(alpha: 0.1),
                  borderRadius: BorderRadius.circular(12),
                ),
                child: Icon(icon, color: color, size: 32),
              ),
              const SizedBox(width: 16),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      title,
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 4),
                    Text(
                      description,
                      style: TextStyle(fontSize: 14, color: Colors.grey[600]),
                    ),
                  ],
                ),
              ),
              Icon(Icons.arrow_forward_ios, color: Colors.grey[400], size: 20),
            ],
          ),
        ),
      ),
    );
  }
}

// ==================== Basic Usage Screen ====================
class BasicUsageScreen extends StatelessWidget {
  const BasicUsageScreen({super.key});

  @override
  Widget build(BuildContext context) {
    // Simulate API response
    final jsonResponse = {
      'id': 123,
      'name': 'John Doe',
      'email': 'john.doe@example.com',
      'age': 30,
      'isPremium': true,
      'balance': 1250.50,
      'lastLogin': '2024-03-15T10:30:00Z',
    };

    final schema = DynamicSchema.parse(jsonResponse);

    return Scaffold(
      appBar: AppBar(title: const Text('Basic Usage')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildSection('JSON Response', jsonEncode(jsonResponse)),
          const SizedBox(height: 24),
          _buildSection('Parsed Data', null),
          _buildDataRow('ID', schema.getInt('id').toString()),
          _buildDataRow('Name', schema.getString('name')),
          _buildDataRow('Email', schema.getString('email')),
          _buildDataRow('Age', schema.getInt('age').toString()),
          _buildDataRow('Premium', schema.getBool('isPremium') ? 'Yes' : 'No'),
          _buildDataRow('Balance', '\$${schema.getDouble('balance')}'),
          _buildDataRow('Last Login', schema.getString('lastLogin')),
          const SizedBox(height: 24),
          _buildSection('Default Values Demo', null),
          _buildDataRow(
            'Phone (not in JSON)',
            schema.getString('phone', defaultValue: 'Not provided'),
          ),
          _buildDataRow(
            'Score (not in JSON)',
            schema.getInt('score', defaultValue: 0).toString(),
          ),
        ],
      ),
    );
  }

  Widget _buildSection(String title, String? content) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Text(
          title,
          style: const TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
        ),
        const SizedBox(height: 8),
        if (content != null)
          Container(
            padding: const EdgeInsets.all(12),
            decoration: BoxDecoration(
              color: Colors.grey[100],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Text(
              content,
              style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
            ),
          ),
      ],
    );
  }

  Widget _buildDataRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          SizedBox(
            width: 120,
            child: Text(
              label,
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Expanded(child: Text(value)),
        ],
      ),
    );
  }
}

// ==================== Nested Objects Screen ====================
class NestedObjectsScreen extends StatelessWidget {
  const NestedObjectsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final jsonResponse = {
      'user': {
        'id': 1,
        'name': 'Alice Johnson',
        'contact': {
          'email': 'alice@example.com',
          'phone': '+1-555-0123',
          'address': {
            'street': '123 Main Street',
            'city': 'Springfield',
            'state': 'IL',
            'zipcode': 62701,
            'coordinates': {'lat': 39.7817, 'lng': -89.6501},
          },
        },
      },
    };

    final schema = DynamicSchema.parse(jsonResponse);
    final user = schema.getNested('user');
    final contact = user?.getNested('contact');
    final address = contact?.getNested('address');
    final coords = address?.getNested('coordinates');

    return Scaffold(
      appBar: AppBar(title: const Text('Nested Objects')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'User Information',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const Divider(),
                  _buildInfoRow('ID', user?.getInt('id').toString() ?? 'N/A'),
                  _buildInfoRow('Name', user?.getString('name') ?? 'N/A'),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Contact Details',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const Divider(),
                  _buildInfoRow('Email', contact?.getString('email') ?? 'N/A'),
                  _buildInfoRow('Phone', contact?.getString('phone') ?? 'N/A'),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Address',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const Divider(),
                  _buildInfoRow(
                    'Street',
                    address?.getString('street') ?? 'N/A',
                  ),
                  _buildInfoRow('City', address?.getString('city') ?? 'N/A'),
                  _buildInfoRow('State', address?.getString('state') ?? 'N/A'),
                  _buildInfoRow(
                    'Zipcode',
                    address?.getInt('zipcode').toString() ?? 'N/A',
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  const Text(
                    'Coordinates',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                  const Divider(),
                  _buildInfoRow(
                    'Latitude',
                    coords?.getDouble('lat').toString() ?? 'N/A',
                  ),
                  _buildInfoRow(
                    'Longitude',
                    coords?.getDouble('lng').toString() ?? 'N/A',
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 24),
          Container(
            padding: const EdgeInsets.all(16),
            decoration: BoxDecoration(
              color: Colors.blue[50],
              borderRadius: BorderRadius.circular(8),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text(
                  'Using Path Access:',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                const SizedBox(height: 8),
                Text(
                  'City: ${schema.getValueAtPath('user.contact.address.city')}',
                ),
                Text(
                  'Lat: ${schema.getValueAtPath('user.contact.address.coordinates.lat')}',
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 4),
      child: Row(
        children: [
          SizedBox(
            width: 100,
            child: Text(label, style: const TextStyle(color: Colors.grey)),
          ),
          Expanded(
            child: Text(
              value,
              style: const TextStyle(fontWeight: FontWeight.w500),
            ),
          ),
        ],
      ),
    );
  }
}

// ==================== Lists Screen ====================
class ListsScreen extends StatelessWidget {
  const ListsScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final jsonResponse = {
      'store': 'Tech World',
      'products': [
        {
          'id': 1,
          'name': 'Laptop',
          'price': 999.99,
          'inStock': true,
          'category': 'Electronics',
        },
        {
          'id': 2,
          'name': 'Wireless Mouse',
          'price': 29.99,
          'inStock': true,
          'category': 'Accessories',
        },
        {
          'id': 3,
          'name': 'Mechanical Keyboard',
          'price': 129.99,
          'inStock': false,
          'category': 'Accessories',
        },
        {
          'id': 4,
          'name': 'Monitor',
          'price': 299.99,
          'inStock': true,
          'category': 'Electronics',
        },
      ],
    };

    final schema = DynamicSchema.parse(jsonResponse);
    final products = schema.getList('products');

    return Scaffold(
      appBar: AppBar(title: const Text('Lists & Arrays')),
      body: Column(
        children: [
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            color: Colors.blue[50],
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  schema.getString('store'),
                  style: const TextStyle(
                    fontSize: 24,
                    fontWeight: FontWeight.bold,
                  ),
                ),
                Text(
                  '${products.length} products available',
                  style: TextStyle(color: Colors.grey[600]),
                ),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              padding: const EdgeInsets.all(16),
              itemCount: products.length,
              itemBuilder: (context, index) {
                final product = products[index];
                return Card(
                  margin: const EdgeInsets.only(bottom: 12),
                  child: ListTile(
                    leading: CircleAvatar(
                      backgroundColor: product.getBool('inStock')
                          ? Colors.green
                          : Colors.red,
                      child: Text(
                        '${product.getInt('id')}',
                        style: const TextStyle(color: Colors.white),
                      ),
                    ),
                    title: Text(
                      product.getString('name'),
                      style: const TextStyle(fontWeight: FontWeight.bold),
                    ),
                    subtitle: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        const SizedBox(height: 4),
                        Text(product.getString('category')),
                        Text(
                          product.getBool('inStock')
                              ? 'In Stock'
                              : 'Out of Stock',
                          style: TextStyle(
                            color: product.getBool('inStock')
                                ? Colors.green
                                : Colors.red,
                            fontWeight: FontWeight.w500,
                          ),
                        ),
                      ],
                    ),
                    trailing: Text(
                      '\$${product.getDouble('price').toStringAsFixed(2)}',
                      style: const TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                        color: Colors.blue,
                      ),
                    ),
                  ),
                );
              },
            ),
          ),
        ],
      ),
    );
  }
}

// ==================== Schema Detection Screen ====================
class SchemaDetectionScreen extends StatefulWidget {
  const SchemaDetectionScreen({super.key});

  @override
  State<SchemaDetectionScreen> createState() => _SchemaDetectionScreenState();
}

class _SchemaDetectionScreenState extends State<SchemaDetectionScreen> {
  final List<String> _logs = [];
  // int _version = 1;

  @override
  void initState() {
    super.initState();
    DynamicSchema.resetCache();
    DynamicSchema.enableSchemaDetection((changes) {
      setState(() {
        _logs.add('⚠️ Schema changed! Detected ${changes.length} changes:');
        for (final change in changes) {
          _logs.add('  • $change');
        }
        _logs.add('');
      });
    });
  }

  @override
  void dispose() {
    DynamicSchema.disableSchemaDetection();
    super.dispose();
  }

  void _simulateApiCall(int version) {
    setState(() {
      // _version = version;
      _logs.add('--- API Call Version $version ---');
    });

    Map<String, dynamic> json;

    switch (version) {
      case 1:
        json = {'id': 1, 'name': 'Product A', 'price': 99.99};
        _logs.add('Original schema: id, name, price');
        break;
      case 2:
        json = {'id': 2, 'name': 'Product B', 'price': 149.99};
        _logs.add('Same schema (no changes expected)');
        break;
      case 3:
        json = {
          'id': 3,
          'name': 'Product C',
          'price': 199.99,
          'category': 'Electronics',
          'inStock': true,
        };
        _logs.add('Added: category, inStock');
        break;
      case 4:
        json = {
          'id': 4,
          'name': 'Product D',
          'category': 'Electronics',
          'inStock': false,
        };
        _logs.add('Removed: price');
        break;
      default:
        json = {'id': 5, 'name': 'Product E', 'price': 'free'};
        _logs.add('Type changed: price (double → string)');
    }

    DynamicSchema.parse(json);
    _logs.add('');
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Schema Detection')),
      body: Column(
        children: [
          Container(
            width: double.infinity,
            padding: const EdgeInsets.all(16),
            color: Colors.orange[50],
            child: const Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Simulate API Evolution',
                  style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 8),
                Text(
                  'Click buttons to simulate different API responses and watch schema changes being detected automatically.',
                  style: TextStyle(fontSize: 14),
                ),
              ],
            ),
          ),
          Padding(
            padding: const EdgeInsets.all(16),
            child: Wrap(
              spacing: 8,
              runSpacing: 8,
              children: [
                ElevatedButton(
                  onPressed: () => _simulateApiCall(1),
                  child: const Text('V1: Original'),
                ),
                ElevatedButton(
                  onPressed: () => _simulateApiCall(2),
                  child: const Text('V2: Same Schema'),
                ),
                ElevatedButton(
                  onPressed: () => _simulateApiCall(3),
                  child: const Text('V3: Fields Added'),
                ),
                ElevatedButton(
                  onPressed: () => _simulateApiCall(4),
                  child: const Text('V4: Field Removed'),
                ),
                ElevatedButton(
                  onPressed: () => _simulateApiCall(5),
                  child: const Text('V5: Type Changed'),
                ),
                ElevatedButton(
                  onPressed: () {
                    setState(() {
                      _logs.clear();
                      DynamicSchema.resetCache();
                      _logs.add('Cache reset. Ready for new detection.\n');
                    });
                  },
                  style: ElevatedButton.styleFrom(
                    backgroundColor: Colors.red,
                    foregroundColor: Colors.white,
                  ),
                  child: const Text('Reset'),
                ),
              ],
            ),
          ),
          const Divider(),
          Expanded(
            child: _logs.isEmpty
                ? const Center(
                    child: Text(
                      'Click buttons above to see schema detection in action',
                      style: TextStyle(color: Colors.grey),
                    ),
                  )
                : ListView.builder(
                    padding: const EdgeInsets.all(16),
                    itemCount: _logs.length,
                    itemBuilder: (context, index) {
                      final log = _logs[index];
                      return Padding(
                        padding: const EdgeInsets.only(bottom: 4),
                        child: Text(
                          log,
                          style: TextStyle(
                            fontFamily: 'monospace',
                            fontSize: 12,
                            color: log.contains('⚠️')
                                ? Colors.orange
                                : log.startsWith('---')
                                ? Colors.blue
                                : Colors.black,
                            fontWeight:
                                log.contains('⚠️') || log.startsWith('---')
                                ? FontWeight.bold
                                : FontWeight.normal,
                          ),
                        ),
                      );
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

// ==================== Product Listing Screen ====================
class ProductListingScreen extends StatelessWidget {
  const ProductListingScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final apiResponse = {
      'store': {'name': 'TechHub Store', 'rating': 4.8, 'totalProducts': 150},
      'categories': ['Electronics', 'Accessories', 'Software'],
      'featured': [
        {
          'id': 'PROD-001',
          'name': 'Premium Wireless Headphones',
          'description': 'Noise-cancelling, 30hr battery',
          'price': 299.99,
          'originalPrice': 399.99,
          'discount': 25.0,
          'rating': 4.7,
          'reviews': 1234,
          'inStock': true,
          'images': ['url1', 'url2'],
          'badge': 'Best Seller',
        },
        {
          'id': 'PROD-002',
          'name': 'Ultra HD Gaming Monitor',
          'description': '27" 4K, 144Hz refresh rate',
          'price': 549.99,
          'originalPrice': 549.99,
          'discount': 0.0,
          'rating': 4.9,
          'reviews': 856,
          'inStock': true,
          'images': ['url1'],
          'badge': 'New Arrival',
        },
        {
          'id': 'PROD-003',
          'name': 'Mechanical Gaming Keyboard',
          'description': 'RGB backlit, Cherry MX switches',
          'price': 179.99,
          'originalPrice': 229.99,
          'discount': 22.0,
          'rating': 4.6,
          'reviews': 567,
          'inStock': false,
          'images': ['url1', 'url2', 'url3'],
          'badge': 'Hot Deal',
        },
      ],
    };

    final schema = DynamicSchema.parse(apiResponse);
    final store = schema.getNested('store');
    final products = schema.getList('featured');

    return Scaffold(
      appBar: AppBar(
        title: Text(store?.getString('name') ?? 'Store'),
        actions: [
          Padding(
            padding: const EdgeInsets.all(16),
            child: Row(
              children: [
                const Icon(Icons.star, color: Colors.amber, size: 20),
                const SizedBox(width: 4),
                Text(
                  store?.getDouble('rating').toString() ?? '0.0',
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
              ],
            ),
          ),
        ],
      ),
      body: ListView.builder(
        padding: const EdgeInsets.all(16),
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          final hasDiscount = product.getDouble('discount') > 0;
          final originalPrice = product.getDouble('originalPrice');
          final price = product.getDouble('price');
          final savings = originalPrice - price;

          return Card(
            margin: const EdgeInsets.only(bottom: 16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Stack(
                  children: [
                    Container(
                      height: 200,
                      width: double.infinity,
                      color: Colors.grey[200],
                      child: const Icon(
                        Icons.image,
                        size: 64,
                        color: Colors.grey,
                      ),
                    ),
                    Positioned(
                      top: 8,
                      right: 8,
                      child: Container(
                        padding: const EdgeInsets.symmetric(
                          horizontal: 8,
                          vertical: 4,
                        ),
                        decoration: BoxDecoration(
                          color: Colors.red,
                          borderRadius: BorderRadius.circular(4),
                        ),
                        child: Text(
                          product.getString('badge'),
                          style: const TextStyle(
                            color: Colors.white,
                            fontSize: 12,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                      ),
                    ),
                  ],
                ),
                Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        product.getString('name'),
                        style: const TextStyle(
                          fontSize: 18,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        product.getString('description'),
                        style: TextStyle(color: Colors.grey[600]),
                      ),
                      const SizedBox(height: 12),
                      Row(
                        children: [
                          const Icon(Icons.star, color: Colors.amber, size: 16),
                          const SizedBox(width: 4),
                          Text(
                            '${product.getDouble('rating')} (${product.getInt('reviews')} reviews)',
                            style: const TextStyle(fontSize: 12),
                          ),
                        ],
                      ),
                      const SizedBox(height: 12),
                      Row(
                        children: [
                          Text(
                            '\$${price.toStringAsFixed(2)}',
                            style: const TextStyle(
                              fontSize: 24,
                              fontWeight: FontWeight.bold,
                              color: Colors.blue,
                            ),
                          ),
                          if (hasDiscount) ...[
                            const SizedBox(width: 8),
                            Text(
                              '\$${originalPrice.toStringAsFixed(2)}',
                              style: TextStyle(
                                fontSize: 16,
                                decoration: TextDecoration.lineThrough,
                                color: Colors.grey[600],
                              ),
                            ),
                            const SizedBox(width: 8),
                            Container(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 6,
                                vertical: 2,
                              ),
                              decoration: BoxDecoration(
                                color: Colors.green,
                                borderRadius: BorderRadius.circular(4),
                              ),
                              child: Text(
                                'Save \$${savings.toStringAsFixed(2)}',
                                style: const TextStyle(
                                  color: Colors.white,
                                  fontSize: 12,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ),
                          ],
                        ],
                      ),
                      const SizedBox(height: 12),
                      Row(
                        children: [
                          Expanded(
                            child: ElevatedButton.icon(
                              onPressed: product.getBool('inStock')
                                  ? () {}
                                  : null,
                              icon: Icon(
                                product.getBool('inStock')
                                    ? Icons.shopping_cart
                                    : Icons.block,
                              ),
                              label: Text(
                                product.getBool('inStock')
                                    ? 'Add to Cart'
                                    : 'Out of Stock',
                              ),
                              style: ElevatedButton.styleFrom(
                                padding: const EdgeInsets.symmetric(
                                  vertical: 12,
                                ),
                              ),
                            ),
                          ),
                          const SizedBox(width: 8),
                          IconButton(
                            onPressed: () {},
                            icon: const Icon(Icons.favorite_border),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ],
            ),
          );
        },
      ),
    );
  }
}

// ==================== User Dashboard Screen ====================
class UserDashboardScreen extends StatelessWidget {
  const UserDashboardScreen({super.key});

  @override
  Widget build(BuildContext context) {
    final dashboardData = {
      'user': {
        'id': 'USR-789',
        'name': 'Sarah Williams',
        'avatar': 'https://example.com/avatar.jpg',
        'memberSince': '2023-01-15',
        'tier': 'Gold',
      },
      'stats': {
        'totalOrders': 45,
        'totalSpent': 3456.78,
        'pointsEarned': 8920,
        'activeSavings': 234.50,
      },
      'recentOrders': [
        {
          'orderId': 'ORD-2024-123',
          'date': '2024-03-15',
          'status': 'Delivered',
          'total': 299.99,
          'items': 3,
        },
        {
          'orderId': 'ORD-2024-122',
          'date': '2024-03-10',
          'status': 'In Transit',
          'total': 149.50,
          'items': 2,
        },
      ],
      'notifications': [
        {'type': 'promotion', 'message': 'Flash sale starts in 2 hours!'},
        {'type': 'order', 'message': 'Your order has been shipped'},
      ],
    };

    final schema = DynamicSchema.parse(dashboardData);
    final user = schema.getNested('user');
    final stats = schema.getNested('stats');
    final orders = schema.getList('recentOrders');

    return Scaffold(
      appBar: AppBar(
        title: const Text('Dashboard'),
        actions: [
          IconButton(icon: const Icon(Icons.notifications), onPressed: () {}),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // User Info Card
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Row(
                children: [
                  CircleAvatar(
                    radius: 40,
                    backgroundColor: Colors.blue[200],
                    child: Text(
                      user?.getString('name').substring(0, 1) ?? 'U',
                      style: const TextStyle(fontSize: 32, color: Colors.white),
                    ),
                  ),
                  const SizedBox(width: 16),
                  Expanded(
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(
                          user?.getString('name') ?? 'User',
                          style: const TextStyle(
                            fontSize: 20,
                            fontWeight: FontWeight.bold,
                          ),
                        ),
                        const SizedBox(height: 4),
                        Row(
                          children: [
                            Container(
                              padding: const EdgeInsets.symmetric(
                                horizontal: 8,
                                vertical: 2,
                              ),
                              decoration: BoxDecoration(
                                color: Colors.amber,
                                borderRadius: BorderRadius.circular(4),
                              ),
                              child: Text(
                                '${user?.getString('tier')} Member',
                                style: const TextStyle(
                                  fontSize: 12,
                                  fontWeight: FontWeight.bold,
                                ),
                              ),
                            ),
                          ],
                        ),
                        const SizedBox(height: 4),
                        Text(
                          'Member since ${user?.getString('memberSince')}',
                          style: TextStyle(
                            fontSize: 12,
                            color: Colors.grey[600],
                          ),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Stats Grid
          Row(
            children: [
              Expanded(
                child: _buildStatCard(
                  'Total Orders',
                  stats?.getInt('totalOrders').toString() ?? '0',
                  Icons.shopping_bag,
                  Colors.blue,
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: _buildStatCard(
                  'Total Spent',
                  '\$${stats?.getDouble('totalSpent').toStringAsFixed(2) ?? '0'}',
                  Icons.attach_money,
                  Colors.green,
                ),
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            children: [
              Expanded(
                child: _buildStatCard(
                  'Points',
                  stats?.getInt('pointsEarned').toString() ?? '0',
                  Icons.star,
                  Colors.amber,
                ),
              ),
              const SizedBox(width: 8),
              Expanded(
                child: _buildStatCard(
                  'Savings',
                  '\$${stats?.getDouble('activeSavings').toStringAsFixed(2) ?? '0'}',
                  Icons.savings,
                  Colors.purple,
                ),
              ),
            ],
          ),
          const SizedBox(height: 24),

          // Recent Orders
          const Text(
            'Recent Orders',
            style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
          ),
          const SizedBox(height: 12),
          ...orders.map((order) {
            final status = order.getString('status');
            Color statusColor;
            switch (status) {
              case 'Delivered':
                statusColor = Colors.green;
                break;
              case 'In Transit':
                statusColor = Colors.orange;
                break;
              default:
                statusColor = Colors.grey;
            }

            return Card(
              margin: const EdgeInsets.only(bottom: 8),
              child: ListTile(
                leading: Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: statusColor.withValues(alpha: 0.1),
                    borderRadius: BorderRadius.circular(8),
                  ),
                  child: Icon(Icons.shopping_bag, color: statusColor),
                ),
                title: Text(
                  order.getString('orderId'),
                  style: const TextStyle(fontWeight: FontWeight.bold),
                ),
                subtitle: Text(
                  '${order.getString('date')} • ${order.getInt('items')} items',
                ),
                trailing: Column(
                  mainAxisAlignment: MainAxisAlignment.center,
                  crossAxisAlignment: CrossAxisAlignment.end,
                  children: [
                    Text(
                      '\$${order.getDouble('total').toStringAsFixed(2)}',
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    Container(
                      padding: const EdgeInsets.symmetric(
                        horizontal: 6,
                        vertical: 2,
                      ),
                      decoration: BoxDecoration(
                        color: statusColor.withValues(alpha: 0.2),
                        borderRadius: BorderRadius.circular(4),
                      ),
                      child: Text(
                        status,
                        style: TextStyle(
                          fontSize: 10,
                          color: statusColor,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ),
                  ],
                ),
              ),
            );
          }),
        ],
      ),
    );
  }

  Widget _buildStatCard(
    String label,
    String value,
    IconData icon,
    Color color,
  ) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            Icon(icon, color: color, size: 32),
            const SizedBox(height: 8),
            Text(
              value,
              style: TextStyle(
                fontSize: 20,
                fontWeight: FontWeight.bold,
                color: color,
              ),
            ),
            const SizedBox(height: 4),
            Text(
              label,
              style: TextStyle(fontSize: 12, color: Colors.grey[600]),
              textAlign: TextAlign.center,
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
150
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

Automatically map dynamic backend JSON to type-safe Dart structures at runtime without model classes. Perfect for evolving APIs.

Repository (GitHub)
View/report issues

Topics

#json #parsing #serialization #dynamic #backend

Documentation

API reference

License

MIT (license)

Dependencies

flutter, shared_preferences

More

Packages that depend on dynamic_schema_mapper