Vyuh CDX
Quick Start

Getting Started

Create your first entity with full CRUD operations, UI layouts, and permissions

This guide will walk you through creating your first entity using the Vyuh Entity System. We'll build a simple Product entity with full CRUD operations, UI layouts, and permissions in under 10 minutes.

Prerequisites

Before starting, ensure you have:

  1. A Flutter project with Vyuh Framework set up
  2. The vyuh_entity_system package added to your dependencies
  3. Basic understanding of Flutter and Dart

Step 1: Define Your Entity Model

First, create your entity model by extending EntityBase:

lib/models/product.dart
import 'package:json_annotation/json_annotation.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';

part 'product.g.dart';

@JsonSerializable()
class Product extends EntityBase {
  final String name;
  final String description;
  final double price;
  final String category;
  final int stockQuantity;
  final bool isActive;

  Product({
    required super.id,
    required super.schemaType,
    required this.name,
    required this.description,
    required this.price,
    required this.category,
    required this.stockQuantity,
    this.isActive = true,
    super.createdAt,
    super.layout,
    super.modifiers,
  });

  factory Product.fromJson(Map<String, dynamic> json) =>
      _$ProductFromJson(json);

  @override
  Map<String, dynamic> toJson() => _$ProductToJson(this);
}

Step 2: Generate JSON Serialization

Run the build runner to generate serialization code:

flutter pub run build_runner build --delete-conflicting-outputs

Step 3: Create the Entity API

Implement the API class for server communication:

lib/api/product_api.dart
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/product.dart';

class ProductApi extends EntityApi<Product> {
  ProductApi({required super.client});

  @override
  Product fromJson(Map<String, dynamic> json) => Product.fromJson(json);

  @override
  Future<List<Product>> performList({
    int? offset,
    int? limit,
    String? sortBy,
    String? sortOrder,
    String? search,
  }) async {
    // The base class handles the API call
    return super.list(
      offset: offset,
      limit: limit,
      sortBy: sortBy,
      sortOrder: sortOrder,
      search: search,
    );
  }

  @override
  Future<Product?> performGetById(String id) async {
    return super.byId(id);
  }

  @override
  Future<Product> performCreate(Product entity) async {
    return super.createNew(entity);
  }

  @override
  Future<Product> performUpdate(String id, Product entity) async {
    return super.update(id, entity);
  }

  @override
  Future<void> performDelete(String id) async {
    return super.delete(id);
  }
}

Step 4: Define Layouts

Create layouts for displaying your entity:

lib/layouts/product_layouts.dart
import 'package:flutter/material.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/product.dart';

class ProductLayouts {
  static EntityLayoutDescriptor<Product> create() {
    return EntityLayoutDescriptor<Product>(
      list: [
        // Table layout for desktop
        TableListLayout<Product>(
          title: 'Products',
          columns: [
            TableColumn(
              key: 'name',
              label: 'Product Name',
              getValue: (product) => product.name,
            ),
            TableColumn(
              key: 'category',
              label: 'Category',
              getValue: (product) => product.category,
            ),
            TableColumn(
              key: 'price',
              label: 'Price',
              getValue: (product) => '\$${product.price.toStringAsFixed(2)}',
            ),
            TableColumn(
              key: 'stockQuantity',
              label: 'Stock',
              getValue: (product) => product.stockQuantity.toString(),
            ),
            TableColumn(
              key: 'isActive',
              label: 'Status',
              getValue: (product) => product.isActive ? 'Active' : 'Inactive',
            ),
          ],
        ),
        // Grid layout for mobile
        GridListLayout<Product>(
          title: 'Products',
          buildCard: (context, product) => Card(
            child: ListTile(
              title: Text(product.name),
              subtitle: Text('${product.category}\$${product.price}'),
              trailing: Chip(
                label: Text('Stock: ${product.stockQuantity}'),
                backgroundColor: product.stockQuantity > 0
                    ? Colors.green.shade100
                    : Colors.red.shade100,
              ),
            ),
          ),
        ),
      ],
      details: [
        // Detail view layout
        EntityLayoutConfiguration<Product>(
          build: (context, product) => SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  product.name,
                  style: Theme.of(context).textTheme.headlineMedium,
                ),
                const SizedBox(height: 8),
                Text(
                  product.description,
                  style: Theme.of(context).textTheme.bodyLarge,
                ),
                const SizedBox(height: 24),
                _buildInfoRow('Category', product.category),
                _buildInfoRow('Price', '\$${product.price.toStringAsFixed(2)}'),
                _buildInfoRow('Stock Quantity', '${product.stockQuantity}'),
                _buildInfoRow('Status', product.isActive ? 'Active' : 'Inactive'),
              ],
            ),
          ),
        ),
      ],
    );
  }

  static Widget _buildInfoRow(String label, String value) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Row(
        children: [
          SizedBox(
            width: 150,
            child: Text(
              label,
              style: const TextStyle(fontWeight: FontWeight.bold),
            ),
          ),
          Text(value),
        ],
      ),
    );
  }
}

Step 5: Create the Form

Define a form for creating and editing products:

lib/forms/product_form.dart
import 'package:vyuh_feature_forms/vyuh_feature_forms.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/product.dart';

class ProductFormDescriptor extends EntityFormDescriptor<Product> {
  @override
  StepForm prepare(Product? entity) {
    final isEdit = entity != null;

    return singleFormAsStep(
      title: isEdit ? 'Edit Product' : 'New Product',
      form: FormBuilder(
        title: 'Product Details',
        fields: [
          TextField(
            name: 'name',
            label: 'Product Name',
            value: entity?.name,
            validators: [required()],
          ),
          TextField(
            name: 'description',
            label: 'Description',
            value: entity?.description,
            validators: [required()],
            multiline: true,
          ),
          NumberField(
            name: 'price',
            label: 'Price',
            value: entity?.price,
            validators: [required(), min(0)],
            decimal: true,
          ),
          SelectField(
            name: 'category',
            label: 'Category',
            value: entity?.category,
            options: [
              Option(value: 'electronics', label: 'Electronics'),
              Option(value: 'clothing', label: 'Clothing'),
              Option(value: 'food', label: 'Food & Beverages'),
              Option(value: 'other', label: 'Other'),
            ],
            validators: [required()],
          ),
          NumberField(
            name: 'stockQuantity',
            label: 'Stock Quantity',
            value: entity?.stockQuantity,
            validators: [required(), min(0)],
          ),
          ToggleField(
            name: 'isActive',
            label: 'Active',
            value: entity?.isActive ?? true,
          ),
        ],
      ),
    );
  }

  @override
  Map<String, dynamic> toFormData(Product entity) {
    return {
      'name': entity.name,
      'description': entity.description,
      'price': entity.price,
      'category': entity.category,
      'stockQuantity': entity.stockQuantity,
      'isActive': entity.isActive,
    };
  }

  @override
  Product fromFormData(Map<String, dynamic> data, {Product? entity}) {
    return Product(
      id: entity?.id ?? '',
      schemaType: 'products',
      name: data['name'],
      description: data['description'],
      price: data['price'],
      category: data['category'],
      stockQuantity: data['stockQuantity'],
      isActive: data['isActive'],
      createdAt: entity?.createdAt ?? DateTime.now(),
    );
  }
}

Step 6: Configure the Entity

Bring everything together in the entity configuration:

lib/config/product_config.dart
import 'package:flutter/material.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import '../models/product.dart';
import '../api/product_api.dart';
import '../layouts/product_layouts.dart';
import '../forms/product_form.dart';

class ProductConfig {
  static final instance = EntityConfiguration<Product>(
    metadata: EntityMetadata(
      identifier: 'products',
      name: 'Product',
      pluralName: 'Products',
      description: 'Manage your product catalog',
      icon: Icons.inventory,
      themeColor: Colors.blue,
      category: 'Inventory',
      route: EntityRouteBuilder.fromIdentifier('products'),
    ),
    api: (client) => ProductApi(client: client),
    layouts: ProductLayouts.create(),
    form: ProductFormDescriptor(),
    actions: EntityActions<Product>(
      list: [
        EntityAction(
          label: 'Export',
          icon: Icons.download,
          onTap: (context, entities) async {
            // Export logic here
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Exporting ${entities.length} products')),
            );
          },
        ),
      ],
      single: [
        EntityAction(
          label: 'Duplicate',
          icon: Icons.copy,
          onTap: (context, entities) async {
            final product = entities.first;
            // Duplicate logic here
          },
        ),
      ],
    ),
  );
}

Step 7: Register the Entity

Register your entity in your feature descriptor:

lib/feature_descriptor.dart
import 'package:vyuh_core/vyuh_core.dart';
import 'package:vyuh_entity_system/vyuh_entity_system.dart';
import 'config/product_config.dart';

class InventoryFeatureDescriptor extends FeatureDescriptor {
  InventoryFeatureDescriptor()
      : super(
          name: 'inventory',
          title: 'Inventory Management',
          description: 'Product inventory features',
          icon: Icons.inventory,
        );

  @override
  void init(Vyuh vyuh) {
    // Register the product entity
    vyuh.entity?.register<Product>(ProductConfig.instance);
  }
}

Step 8: Use Your Entity

Your entity is now ready to use! The entity system automatically provides:

Routes

  • /products - List all products
  • /products/new - Create new product
  • /products/:id - View product details
  • /products/:id/edit - Edit product

Access your entity through the dashboard or use the command palette (Cmd/Ctrl+K) to search for "Products".

Programmatic Access

// In your widgets
final provider = EntityProvider.of<Product>(context);

// List products
final products = await provider.list();

// Get single product
final product = await provider.byId('product-id');

// Create product
final newProduct = await provider.create(product);

// Update product
await provider.update('product-id', updatedProduct);

// Delete product
await provider.delete('product-id');

What's Next?

Congratulations! You've created your first entity with:

  • ✅ Full CRUD operations
  • ✅ Multiple UI layouts
  • ✅ Form validation
  • ✅ Custom actions
  • ✅ Automatic routing

Explore Advanced Features

  1. Add Permissions - See Permissions

    if (await hasPermission(Permission.create('products'))) {
      // Show create button
    }
  2. Custom Layouts - See Layouts and Views

    class ProductMapLayout extends EntityLayout<Product> {
      // Custom map visualization
    }
  3. Entity Relationships - See Entity Lifecycle

    class Product extends EntityBase {
      final String categoryId; // Reference to Category entity
      // ... other fields
    }
  4. Activity Tracking - See Activity Tracking

    // Automatic audit trail for all operations

Troubleshooting

Common Issues

  1. Build runner fails

    • Ensure json_annotation and build_runner are in dev_dependencies
    • Delete .dart_tool/build and try again
  2. Routes not working

    • Make sure entity is registered in feature descriptor
    • Check that feature is loaded in main app
  3. API calls failing

    • Verify server endpoints match entity identifier
    • Check authentication configuration

Getting Help

  • Check the Examples for more complex scenarios
  • Review Best Practices for optimization tips
  • File issues in the repository for bugs or feature requests

Ready for more? Continue to Entity Configuration for a deep dive into all configuration options!