fkernal 1.3.0 copy "fkernal: ^1.3.0" to clipboard
fkernal: ^1.3.0 copied to clipboard

A configuration-driven Flutter framework that handles networking, state management, storage, error handling, and theming automatically.

example/main.dart

// FKernal Comprehensive Example
// This single file demonstrates ALL features of the FKernal framework.
//
// Features demonstrated:
// - FKernal initialization with full configuration
// - Networking with endpoints, caching, and invalidation
// - State management with FKernalBuilder
// - Local state with slices (Value, Toggle, Counter, List)
// - Theme switching
// - Error handling
// - Context extensions

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

// ============================================================================
// MODELS - Implement FKernalModel for type-safe API handling
// ============================================================================

class User implements FKernalModel {
  final int? id;
  final String name;
  final String username;
  final String email;
  final String? phone;
  final String? website;

  User(
      {this.id,
      required this.name,
      required this.username,
      required this.email,
      this.phone,
      this.website});

  factory User.fromJson(Map<String, dynamic> json) => User(
        id: json['id'],
        name: json['name'] ?? '',
        username: json['username'] ?? '',
        email: json['email'] ?? '',
        phone: json['phone'],
        website: json['website'],
      );

  @override
  Map<String, dynamic> toJson() => {
        if (id != null) 'id': id,
        'name': name,
        'username': username,
        'email': email,
        if (phone != null) 'phone': phone,
        if (website != null) 'website': website,
      };

  @override
  void validate() {
    if (name.isEmpty) {
      throw const FKernalError(
          type: FKernalErrorType.validation, message: 'Name required');
    }
    if (!email.contains('@')) {
      throw const FKernalError(
          type: FKernalErrorType.validation, message: 'Valid email required');
    }
  }
}

class Post implements FKernalModel {
  final int? id;
  final int userId;
  final String title;
  final String body;

  Post(
      {this.id, required this.userId, required this.title, required this.body});

  factory Post.fromJson(Map<String, dynamic> json) => Post(
        id: json['id'],
        userId: json['userId'] ?? 0,
        title: json['title'] ?? '',
        body: json['body'] ?? '',
      );

  @override
  Map<String, dynamic> toJson() => {
        if (id != null) 'id': id,
        'userId': userId,
        'title': title,
        'body': body
      };

  @override
  void validate() {
    if (title.isEmpty) {
      throw const FKernalError(
          type: FKernalErrorType.validation, message: 'Title required');
    }
  }
}

class Todo implements FKernalModel {
  final int? id;
  final int userId;
  final String title;
  final bool completed;

  Todo(
      {this.id,
      required this.userId,
      required this.title,
      this.completed = false});

  factory Todo.fromJson(Map<String, dynamic> json) => Todo(
        id: json['id'],
        userId: json['userId'] ?? 0,
        title: json['title'] ?? '',
        completed: json['completed'] ?? false,
      );

  @override
  Map<String, dynamic> toJson() => {
        if (id != null) 'id': id,
        'userId': userId,
        'title': title,
        'completed': completed
      };

  @override
  void validate() {
    if (title.isEmpty) {
      throw const FKernalError(
          type: FKernalErrorType.validation, message: 'Title required');
    }
  }
}

// ============================================================================
// LOCAL STATE - Complex state for calculator demo
// ============================================================================

class CalculatorState {
  final String display;
  final String expression;
  final double? firstOperand;
  final String? operator;
  final bool shouldResetDisplay;
  final List<String> history;

  const CalculatorState({
    this.display = '0',
    this.expression = '',
    this.firstOperand,
    this.operator,
    this.shouldResetDisplay = false,
    this.history = const [],
  });

  CalculatorState copyWith({
    String? display,
    String? expression,
    double? firstOperand,
    String? operator,
    bool? shouldResetDisplay,
    bool clearFirstOperand = false,
    bool clearOperator = false,
    List<String>? history,
  }) {
    return CalculatorState(
      display: display ?? this.display,
      expression: expression ?? this.expression,
      firstOperand:
          clearFirstOperand ? null : (firstOperand ?? this.firstOperand),
      operator: clearOperator ? null : (operator ?? this.operator),
      shouldResetDisplay: shouldResetDisplay ?? this.shouldResetDisplay,
      history: history ?? this.history,
    );
  }
}

// ============================================================================
// ENDPOINTS - Declarative API configuration
// ============================================================================

final endpoints = <Endpoint>[
  // GET endpoints with caching
  Endpoint(
    id: 'getUsers',
    path: '/users',
    method: HttpMethod.get,
    cacheConfig: const CacheConfig(duration: Duration(minutes: 5)),
    parser: (json) => (json as List)
        .map((u) => User.fromJson(Map<String, dynamic>.from(u)))
        .toList(),
    description: 'Fetches all users',
  ),
  Endpoint(
    id: 'getUser',
    path: '/users/{id}',
    method: HttpMethod.get,
    cacheConfig: const CacheConfig(duration: Duration(minutes: 10)),
    parser: (json) => User.fromJson(Map<String, dynamic>.from(json)),
    description: 'Fetches user by ID',
  ),
  // POST with cache invalidation
  Endpoint(
    id: 'createUser',
    path: '/users',
    method: HttpMethod.post,
    invalidates: ['getUsers'],
    parser: (json) => User.fromJson(Map<String, dynamic>.from(json)),
    description: 'Creates a new user',
  ),
  // Posts
  Endpoint(
    id: 'getPosts',
    path: '/posts',
    method: HttpMethod.get,
    cacheConfig: CacheConfig.medium,
    parser: (json) => (json as List)
        .map((p) => Post.fromJson(Map<String, dynamic>.from(p)))
        .toList(),
  ),
  Endpoint(
    id: 'getUserPosts',
    path: '/users/{userId}/posts',
    method: HttpMethod.get,
    cacheConfig: CacheConfig.short,
    parser: (json) => (json as List)
        .map((p) => Post.fromJson(Map<String, dynamic>.from(p)))
        .toList(),
  ),
  // Todos
  Endpoint(
    id: 'getTodos',
    path: '/todos',
    method: HttpMethod.get,
    cacheConfig: CacheConfig.short,
    parser: (json) => (json as List)
        .map((t) => Todo.fromJson(Map<String, dynamic>.from(t)))
        .toList(),
  ),
];

// ============================================================================
// MAIN - App entry point with FKernal initialization
// ============================================================================

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize FKernal with full configuration
  await FKernal.init(
    config: const FKernalConfig(
      baseUrl: 'https://jsonplaceholder.typicode.com',
      environment: Environment.development,
      features: FeatureFlags(
        enableCache: true,
        enableOffline: false,
        enableAutoRetry: true,
        maxRetryAttempts: 3,
        enableLogging: true,
      ),
      defaultCacheConfig: CacheConfig(duration: Duration(minutes: 5)),
      connectTimeout: 30000,
      receiveTimeout: 30000,
      theme: ThemeConfig(
        primaryColor: Color(0xFF6366F1),
        secondaryColor: Color(0xFF8B5CF6),
        useMaterial3: true,
        defaultThemeMode: ThemeMode.system,
        borderRadius: 12.0,
        defaultPadding: 16.0,
      ),
      // UNIVERSAL STATE MANAGEMENT CONFIGURATION
      // FKernal uses Riverpod by default, but you can switch engines:
      // stateManagement: StateManagementType.riverpod, // default
      // stateManagement: StateManagementType.bloc,
      // stateManagement: StateManagementType.getx,
    ),
    endpoints: endpoints,
  );

  runApp(const FKernalDemoApp());
}

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

  @override
  Widget build(BuildContext context) {
    return FKernalApp(
      child: Builder(
        builder: (context) {
          final themeManager = context.themeManager;
          return ListenableBuilder(
            listenable: themeManager,
            builder: (context, _) => MaterialApp(
              title: 'FKernal Complete Demo',
              debugShowCheckedModeBanner: false,
              theme: themeManager.lightTheme,
              darkTheme: themeManager.darkTheme,
              themeMode: themeManager.themeMode,
              home: const HomeScreen(),
            ),
          );
        },
      ),
    );
  }
}

// ============================================================================
// HOME SCREEN - Navigation hub with theme toggle
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final colorScheme = theme.colorScheme;

    return Scaffold(
      appBar: AppBar(
        title: const Text('FKernal Demo'),
        actions: [
          ListenableBuilder(
              listenable: context.themeManager,
              builder: (context, _) {
                return IconButton(
                  icon: Icon(context.themeManager.themeMode == ThemeMode.dark
                      ? Icons.light_mode
                      : Icons.dark_mode),
                  onPressed: () => context.themeManager.toggleTheme(),
                  tooltip: 'Toggle Theme',
                );
              }),
        ],
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Hero section
          Container(
            padding: const EdgeInsets.all(24),
            decoration: BoxDecoration(
              gradient: LinearGradient(
                colors: [
                  colorScheme.primaryContainer,
                  colorScheme.secondaryContainer
                ],
                begin: Alignment.topLeft,
                end: Alignment.bottomRight,
              ),
              borderRadius: BorderRadius.circular(16),
            ),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Icon(Icons.bolt, size: 48, color: colorScheme.primary),
                const SizedBox(height: 16),
                Text('FKernal Complete Demo',
                    style: theme.textTheme.headlineSmall
                        ?.copyWith(fontWeight: FontWeight.bold)),
                const SizedBox(height: 8),
                Text(
                  'This example demonstrates ALL features: networking, state management, local slices, theming, and error handling.',
                  style: theme.textTheme.bodyMedium
                      ?.copyWith(color: colorScheme.onSurfaceVariant),
                ),
              ],
            ),
          ),
          const SizedBox(height: 24),

          // Feature demos
          Text('API Features',
              style: theme.textTheme.titleMedium
                  ?.copyWith(fontWeight: FontWeight.bold)),
          const SizedBox(height: 12),
          _DemoCard(
            icon: Icons.people,
            title: 'Users',
            subtitle: 'FKernalBuilder, mutations, cache invalidation',
            color: colorScheme.primary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const UsersScreen())),
          ),
          const SizedBox(height: 8),
          _DemoCard(
            icon: Icons.article,
            title: 'Posts',
            subtitle: 'Nested builders, path parameters',
            color: colorScheme.secondary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const PostsScreen())),
          ),
          const SizedBox(height: 8),
          _DemoCard(
            icon: Icons.check_circle,
            title: 'Todos',
            subtitle: 'Resource filtering and computed state',
            color: colorScheme.tertiary,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const TodosScreen())),
          ),
          const SizedBox(height: 24),

          // Local State demos
          Text('Local State',
              style: theme.textTheme.titleMedium
                  ?.copyWith(fontWeight: FontWeight.bold)),
          const SizedBox(height: 12),
          _DemoCard(
            icon: Icons.calculate,
            title: 'Calculator',
            subtitle: 'Complex local state with FKernalLocalBuilder',
            color: Colors.orange,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const CalculatorScreen())),
          ),
          const SizedBox(height: 8),
          _DemoCard(
            icon: Icons.toggle_on,
            title: 'Slices Demo',
            subtitle: 'Value, Toggle, Counter, List slices',
            color: Colors.teal,
            onTap: () => Navigator.push(context,
                MaterialPageRoute(builder: (_) => const SlicesDemoScreen())),
          ),
        ],
      ),
    );
  }
}

class _DemoCard extends StatelessWidget {
  final IconData icon;
  final String title;
  final String subtitle;
  final Color color;
  final VoidCallback onTap;

  const _DemoCard(
      {required this.icon,
      required this.title,
      required this.subtitle,
      required this.color,
      required this.onTap});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        leading: CircleAvatar(
            backgroundColor: color.withValues(alpha: 0.1),
            child: Icon(icon, color: color)),
        title: Text(title),
        subtitle: Text(subtitle),
        trailing: const Icon(Icons.arrow_forward_ios, size: 16),
        onTap: onTap,
      ),
    );
  }
}

// ============================================================================
// USERS SCREEN - FKernalBuilder with mutations
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Users'),
        actions: [
          IconButton(
            icon: const Icon(Icons.refresh),
            onPressed: () => context.refreshResource<List<User>>('getUsers'),
          ),
        ],
      ),
      body: FKernalBuilder<List<User>>(
        resource: 'getUsers',
        builder: (context, users) {
          if (users.isEmpty) {
            return const AutoEmptyWidget(
                title: 'No Users', icon: Icons.people_outline);
          }
          return RefreshIndicator(
            onRefresh: () => context.refreshResource<List<User>>('getUsers'),
            child: ListView.builder(
              itemCount: users.length,
              itemBuilder: (context, index) {
                final user = users[index];
                return Card(
                  margin:
                      const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
                  child: ListTile(
                    leading: CircleAvatar(child: Text(user.name[0])),
                    title: Text(user.name),
                    subtitle: Text(user.email),
                    trailing: const Icon(Icons.chevron_right),
                    onTap: () => _showUserDetail(context, user),
                  ),
                );
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: () => _createUser(context),
        icon: const Icon(Icons.add),
        label: const Text('Add User'),
      ),
    );
  }

  void _showUserDetail(BuildContext context, User user) {
    showModalBottomSheet(
      context: context,
      builder: (context) => Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(user.name, style: Theme.of(context).textTheme.headlineSmall),
            Text('@${user.username}',
                style: Theme.of(context).textTheme.bodyMedium),
            const SizedBox(height: 16),
            Text('Email: ${user.email}'),
            if (user.phone != null) Text('Phone: ${user.phone}'),
            if (user.website != null) Text('Website: ${user.website}'),
            const SizedBox(height: 16),
            // Nested FKernalBuilder for user posts
            Text('Posts:', style: Theme.of(context).textTheme.titleMedium),
            SizedBox(
              height: 120,
              child: FKernalBuilder<List<Post>>(
                resource: 'getUserPosts',
                pathParams: {'userId': user.id.toString()},
                builder: (context, posts) => ListView(
                  scrollDirection: Axis.horizontal,
                  children: posts
                      .take(5)
                      .map((p) => Card(
                          child: Padding(
                              padding: const EdgeInsets.all(8),
                              child: Text(p.title, maxLines: 2))))
                      .toList(),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  void _createUser(BuildContext context) async {
    try {
      await context.performAction<User>('createUser',
          payload: User(
              name: 'New User', email: 'new@example.com', username: 'newuser'));
      if (context.mounted) {
        ScaffoldMessenger.of(context)
            .showSnackBar(const SnackBar(content: Text('User created!')));
      }
    } catch (e) {
      if (context.mounted) {
        ScaffoldMessenger.of(context)
            .showSnackBar(SnackBar(content: Text('Error: $e')));
      }
    }
  }
}

// ============================================================================
// POSTS SCREEN - Nested builders
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Posts')),
      body: FKernalBuilder<List<Post>>(
        resource: 'getPosts',
        builder: (context, posts) => RefreshIndicator(
          onRefresh: () => context.refreshResource<List<Post>>('getPosts'),
          child: ListView.builder(
            itemCount: posts.length,
            itemBuilder: (context, index) {
              final post = posts[index];
              return Card(
                margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
                child: Padding(
                  padding: const EdgeInsets.all(16),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(post.title,
                          style: Theme.of(context).textTheme.titleMedium),
                      const SizedBox(height: 8),
                      Text(post.body,
                          maxLines: 3, overflow: TextOverflow.ellipsis),
                    ],
                  ),
                ),
              );
            },
          ),
        ),
      ),
    );
  }
}

// ============================================================================
// TODOS SCREEN - Resource filtering
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Todos')),
      body: FKernalBuilder<List<Todo>>(
        resource: 'getTodos',
        builder: (context, todos) {
          final completed = todos.where((t) => t.completed).toList();
          final pending = todos.where((t) => !t.completed).toList();

          return RefreshIndicator(
            onRefresh: () => context.refreshResource<List<Todo>>('getTodos'),
            child: ListView(
              children: [
                _buildSection(context, 'Pending', pending, false),
                _buildSection(context, 'Completed', completed, true),
              ],
            ),
          );
        },
      ),
    );
  }

  Widget _buildSection(
      BuildContext context, String title, List<Todo> todos, bool isCompleted) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: Row(children: [
            Text(title, style: Theme.of(context).textTheme.titleMedium),
            const SizedBox(width: 8),
            Chip(label: Text('${todos.length}')),
          ]),
        ),
        ...todos.take(10).map((todo) => Card(
              margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
              child: ListTile(
                leading: Icon(
                    isCompleted ? Icons.check_circle : Icons.circle_outlined,
                    color: isCompleted ? Colors.green : null),
                title: Text(todo.title,
                    style: TextStyle(
                        decoration:
                            isCompleted ? TextDecoration.lineThrough : null)),
              ),
            )),
      ],
    );
  }
}

// ============================================================================
// CALCULATOR SCREEN - FKernalLocalBuilder with complex state
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return FKernalLocalBuilder<CalculatorState>(
      slice: 'calculator',
      create: () => LocalSlice<CalculatorState>(
          initialState: const CalculatorState(), enableHistory: true),
      builder: (context, state, update) {
        void onDigit(String d) => update((s) => s.copyWith(
            display:
                s.shouldResetDisplay || s.display == '0' ? d : s.display + d,
            shouldResetDisplay: false));
        void onOperator(String op) => update((s) => s.copyWith(
            firstOperand: double.tryParse(s.display),
            operator: op,
            expression: '${s.display} $op',
            shouldResetDisplay: true));
        void onClear() => update((s) => s.copyWith(
            display: '0',
            expression: '',
            clearFirstOperand: true,
            clearOperator: true));
        void onEquals() {
          if (state.firstOperand == null || state.operator == null) return;
          final second = double.tryParse(state.display) ?? 0;
          double result = 0;
          switch (state.operator) {
            case '+':
              result = state.firstOperand! + second;
              break;
            case '-':
              result = state.firstOperand! - second;
              break;
            case '×':
              result = state.firstOperand! * second;
              break;
            case '÷':
              result = second != 0 ? state.firstOperand! / second : 0;
              break;
          }
          final r = result == result.toInt()
              ? result.toInt().toString()
              : result.toStringAsFixed(4);
          update((s) => s.copyWith(
              display: r,
              expression: '',
              clearFirstOperand: true,
              clearOperator: true,
              shouldResetDisplay: true));
        }

        return Scaffold(
          appBar: AppBar(title: const Text('Calculator (Local State)')),
          body: Column(
            children: [
              Expanded(
                flex: 2,
                child: Container(
                  alignment: Alignment.bottomRight,
                  padding: const EdgeInsets.all(24),
                  child: Column(
                    mainAxisAlignment: MainAxisAlignment.end,
                    crossAxisAlignment: CrossAxisAlignment.end,
                    children: [
                      if (state.expression.isNotEmpty)
                        Text(state.expression,
                            style: const TextStyle(
                                fontSize: 20, color: Colors.grey)),
                      Text(state.display,
                          style: const TextStyle(
                              fontSize: 48, fontWeight: FontWeight.w300)),
                    ],
                  ),
                ),
              ),
              Expanded(
                flex: 4,
                child: GridView.count(
                  crossAxisCount: 4,
                  padding: const EdgeInsets.all(8),
                  children: [
                    _CalcBtn('C', onTap: onClear, isFunc: true),
                    _CalcBtn('±', onTap: () {}, isFunc: true),
                    _CalcBtn('%', onTap: () {}, isFunc: true),
                    _CalcBtn('÷', onTap: () => onOperator('÷'), isOp: true),
                    _CalcBtn('7', onTap: () => onDigit('7')),
                    _CalcBtn('8', onTap: () => onDigit('8')),
                    _CalcBtn('9', onTap: () => onDigit('9')),
                    _CalcBtn('×', onTap: () => onOperator('×'), isOp: true),
                    _CalcBtn('4', onTap: () => onDigit('4')),
                    _CalcBtn('5', onTap: () => onDigit('5')),
                    _CalcBtn('6', onTap: () => onDigit('6')),
                    _CalcBtn('-', onTap: () => onOperator('-'), isOp: true),
                    _CalcBtn('1', onTap: () => onDigit('1')),
                    _CalcBtn('2', onTap: () => onDigit('2')),
                    _CalcBtn('3', onTap: () => onDigit('3')),
                    _CalcBtn('+', onTap: () => onOperator('+'), isOp: true),
                    _CalcBtn('0', onTap: () => onDigit('0')),
                    _CalcBtn('.', onTap: () {}),
                    _CalcBtn('⌫', onTap: () {}),
                    _CalcBtn('=', onTap: onEquals, isEquals: true),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

class _CalcBtn extends StatelessWidget {
  final String label;
  final VoidCallback onTap;
  final bool isOp;
  final bool isFunc;
  final bool isEquals;

  const _CalcBtn(this.label,
      {required this.onTap,
      this.isOp = false,
      this.isFunc = false,
      this.isEquals = false});

  @override
  Widget build(BuildContext context) {
    final colorScheme = Theme.of(context).colorScheme;
    Color bg = colorScheme.surface;
    Color fg = colorScheme.onSurface;
    if (isEquals) {
      bg = colorScheme.primary;
      fg = colorScheme.onPrimary;
    } else if (isOp) {
      bg = colorScheme.primaryContainer;
      fg = colorScheme.onPrimaryContainer;
    } else if (isFunc) {
      bg = colorScheme.surfaceContainerHighest;
    }

    return Padding(
      padding: const EdgeInsets.all(4),
      child: Material(
        color: bg,
        borderRadius: BorderRadius.circular(16),
        child: InkWell(
          onTap: onTap,
          borderRadius: BorderRadius.circular(16),
          child: Center(
              child: Text(label, style: TextStyle(fontSize: 24, color: fg))),
        ),
      ),
    );
  }
}

// ============================================================================
// SLICES DEMO SCREEN - All slice types
// ============================================================================

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Local State Slices')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          // Toggle Slice
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('ToggleSlice',
                      style: Theme.of(context).textTheme.titleMedium),
                  FKernalToggleBuilder(
                    slice: 'darkMode',
                    create: () => ToggleSlice(false),
                    builder: (context, value, toggle) => SwitchListTile(
                      title: const Text('Dark Mode'),
                      value: value,
                      onChanged: (_) => toggle.toggle(),
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Counter Slice
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('CounterSlice',
                      style: Theme.of(context).textTheme.titleMedium),
                  FKernalCounterBuilder(
                    slice: 'counter',
                    create: () => CounterSlice(initial: 0, min: 0, max: 100),
                    builder: (context, value, counter) => Row(
                      mainAxisAlignment: MainAxisAlignment.center,
                      children: [
                        IconButton(
                            onPressed: counter.decrement,
                            icon: const Icon(Icons.remove)),
                        Text('$value',
                            style: Theme.of(context).textTheme.headlineMedium),
                        IconButton(
                            onPressed: counter.increment,
                            icon: const Icon(Icons.add)),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Value Slice
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('ValueSlice<String>',
                      style: Theme.of(context).textTheme.titleMedium),
                  FKernalValueBuilder<String>(
                    slice: 'message',
                    create: () => ValueSlice<String>('Hello FKernal!'),
                    builder: (context, value, setValue) => Column(
                      children: [
                        Text(value,
                            style: Theme.of(context).textTheme.bodyLarge),
                        const SizedBox(height: 8),
                        Wrap(
                          spacing: 8,
                          children: [
                            ElevatedButton(
                                onPressed: () => setValue('Hello!'),
                                child: const Text('Hello')),
                            ElevatedButton(
                                onPressed: () => setValue('FKernal rocks!'),
                                child: const Text('Rocks')),
                          ],
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // List Slice
          Card(
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('ListSlice<String>',
                      style: Theme.of(context).textTheme.titleMedium),
                  FKernalListBuilder<String>(
                    slice: 'items',
                    create: () => ListSlice<String>(['Item 1', 'Item 2']),
                    builder: (context, items, slice) => Column(
                      children: [
                        ...items.map((item) => ListTile(
                              title: Text(item),
                              trailing: IconButton(
                                  icon: const Icon(Icons.delete),
                                  onPressed: () => slice.remove(item)),
                            )),
                        ElevatedButton(
                          onPressed: () =>
                              slice.add('Item ${items.length + 1}'),
                          child: const Text('Add Item'),
                        ),
                      ],
                    ),
                  ),
                ],
              ),
            ),
          ),
        ],
      ),
    );
  }
}
0
likes
150
points
317
downloads

Publisher

unverified uploader

Weekly Downloads

A configuration-driven Flutter framework that handles networking, state management, storage, error handling, and theming automatically.

Repository (GitHub)
View/report issues
Contributing

Topics

#fkernal #state-management #storage #error-handling #theming

Documentation

API reference

License

MIT (license)

Dependencies

cloud_firestore, connectivity_plus, dio, firebase_auth, firebase_storage, flutter, flutter_bloc, flutter_riverpod, flutter_secure_storage, get, hive_flutter, json_annotation, mobx, path_provider, signals_flutter

More

Packages that depend on fkernal