vexo 1.0.0 copy "vexo: ^1.0.0" to clipboard
vexo: ^1.0.0 copied to clipboard

Vexo is the ultimate Flutter state management — zero dependencies, as simple as GetX, yet as powerful as Riverpod and BLoC combined. Fast, reactive, clean.

example/lib/main.dart

// ============================================================
//  example/lib/main.dart
//  Vexo Full Demo — Counter, Auth, Todo, Theme, Async, BLoC
// ============================================================

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

// ═══════════════════════════════════════════════════════════
//  1. SIMPLE CONTROLLER — Counter (GetX-style simplicity)
// ═══════════════════════════════════════════════════════════

class CounterController extends VexoController {
  final count = VexoState(0);
  final step  = VexoState(1);

  // Derived state — auto-updates when count or step changes
  late final doubled  = count.select((v) => v * 2);
  late final isEven   = count.select((v) => v.isEven);

  @override
  void onInit() {
    // Log every change
    ever(count, (v) => debugPrint('Counter: $v'));
    // Debounce rapid increments
    debounce(count, (v) => debugPrint('Settled at $v'),
        duration: const Duration(milliseconds: 500));
    super.onInit();
  }

  void increment() => count.update((v) => v + step.value);
  void decrement() => count.update((v) => v - step.value);
  void reset()     => count.emit(0);
}

// ═══════════════════════════════════════════════════════════
//  2. ASYNC CONTROLLER — Posts loader (Riverpod-style async)
// ═══════════════════════════════════════════════════════════

class Post {
  final int id;
  final String title;
  const Post(this.id, this.title);
}

class PostsController extends VexoController {
  final posts  = VexoAsyncState<List<Post>>();
  final search = VexoState('');

  late final filtered = VexoComputed<List<Post>>(
    () => (posts.data ?? [])
        .where((p) => p.title
            .toLowerCase()
            .contains(search.value.toLowerCase()))
        .toList(),
    [posts, search],
  );

  @override
  void onInit() {
    loadPosts();
    // Re-filter on search input
    debounce(search, (_) {}, duration: const Duration(milliseconds: 200));
    super.onInit();
  }

  Future<void> loadPosts() => posts.execute(() async {
        await Future.delayed(const Duration(seconds: 1));
        return List.generate(
          20,
          (i) => Post(i + 1, 'Post number ${i + 1}'),
        );
      });
}

// ═══════════════════════════════════════════════════════════
//  3. BLOC-STYLE — Auth (BLoC-style events + typed state)
// ═══════════════════════════════════════════════════════════

abstract class AuthEvent extends VexoEvent {}
class LoginEvent  extends AuthEvent { final String user; LoginEvent(this.user); }
class LogoutEvent extends AuthEvent {}

abstract class AuthState {}
class AuthInitial    extends AuthState {}
class AuthLoading    extends AuthState {}
class AuthLoggedIn   extends AuthState { final String user; AuthLoggedIn(this.user); }
class AuthLoggedOut  extends AuthState {}
class AuthFailed     extends AuthState { final String msg; AuthFailed(this.msg); }

class AuthBloc extends VexoCubitBloc<AuthEvent, AuthState> {
  AuthBloc() : super(AuthInitial());

  @override
  void onEvent(AuthEvent event) async {
    switch (event) {
      case LoginEvent(:var user):
        emit(AuthLoading());
        await Future.delayed(const Duration(seconds: 1));
        if (user.isEmpty) {
          emit(AuthFailed('Username cannot be empty'));
        } else {
          emit(AuthLoggedIn(user));
        }

      case LogoutEvent():
        emit(AuthLoading());
        await Future.delayed(const Duration(milliseconds: 500));
        emit(AuthLoggedOut());
    }
  }
}

// ═══════════════════════════════════════════════════════════
//  4. THEME CONTROLLER — Global singleton
// ═══════════════════════════════════════════════════════════

class ThemeController extends VexoController {
  final isDark = VexoState(false);
  void toggle() => isDark.toggle();
}

// ═══════════════════════════════════════════════════════════
//  5. TODO CONTROLLER — List state extensions
// ═══════════════════════════════════════════════════════════

class Todo {
  final String title;
  final bool done;
  const Todo(this.title, {this.done = false});
  Todo copyWith({bool? done}) => Todo(title, done: done ?? this.done);
}

class TodoController extends VexoController {
  final todos       = VexoState<List<Todo>>([]);
  final showDone    = VexoState(true);
  final inputField  = VexoField<String>(
    initial: '',
    validators: [VexoValidators.required('Todo cannot be empty')],
  );

  late final visible = VexoComputed<List<Todo>>(
    () => showDone.value
        ? todos.value
        : todos.value.where((t) => !t.done).toList(),
    [todos, showDone],
  );

  late final doneCount =
      todos.select((list) => list.where((t) => t.done).length);

  void addTodo() {
    if (!inputField.validate()) return;
    todos.add(Todo(inputField.value));
    inputField.reset();
  }

  void toggle(int index) {
    final list = [...todos.value];
    list[index] = list[index].copyWith(done: !list[index].done);
    todos.emit(list);
  }

  void remove(int index) => todos.removeAt(index);
}

// ═══════════════════════════════════════════════════════════
//  MAIN — Register controllers + run app
// ═══════════════════════════════════════════════════════════

void main() {
  // Global middleware — logs all state transitions
  VexoObserver.middleware = _AppMiddleware();

  // Permanent singletons
  Vexo.put(() => ThemeController(), permanent: true);
  Vexo.put(() => CounterController(), permanent: true);
  Vexo.put(() => AuthBloc(), permanent: true);
  Vexo.put(() => PostsController(), permanent: true);
  Vexo.put(() => TodoController(), permanent: true);

  runApp(const VexoApp());
}

class _AppMiddleware extends VexoMiddleware {
  @override
  void onCreate(VexoController ctrl) =>
      debugPrint('🟢 Vexo: ${ctrl.runtimeType} created');
  @override
  void onClose(VexoController ctrl) =>
      debugPrint('🔴 Vexo: ${ctrl.runtimeType} closed');
}

// ═══════════════════════════════════════════════════════════
//  APP ROOT
// ═══════════════════════════════════════════════════════════

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

  @override
  Widget build(BuildContext context) {
    final theme = Vexo.find<ThemeController>();
    return VexoBuilder(
      states: [theme.isDark],
      builder: (ctx) => MaterialApp(
        title: 'Vexo Demo',
        debugShowCheckedModeBanner: false,
        theme: theme.isDark.value ? ThemeData.dark() : ThemeData.light(),
        home: const HomePage(),
      ),
    );
  }
}

// ═══════════════════════════════════════════════════════════
//  HOME PAGE — Tab navigator
// ═══════════════════════════════════════════════════════════

class HomePage extends StatefulWidget {
  const HomePage({super.key});
  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _tab = 0;

  final _pages = const [
    CounterPage(),
    TodoPage(),
    PostsPage(),
    AuthPage(),
  ];

  @override
  Widget build(BuildContext context) {
    final theme = Vexo.find<ThemeController>();
    return Scaffold(
      appBar: AppBar(
        title: const Text('⚡ Vexo Demo'),
        actions: [
          VexoBuilder(
            states: [theme.isDark],
            builder: (_) => IconButton(
              icon: Icon(theme.isDark.value
                  ? Icons.light_mode
                  : Icons.dark_mode),
              onPressed: theme.toggle,
            ),
          ),
        ],
      ),
      body: _pages[_tab],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _tab,
        type: BottomNavigationBarType.fixed,
        onTap: (i) => setState(() => _tab = i),
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.add), label: 'Counter'),
          BottomNavigationBarItem(icon: Icon(Icons.check_box), label: 'Todo'),
          BottomNavigationBarItem(icon: Icon(Icons.list), label: 'Posts'),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: 'Auth'),
        ],
      ),
    );
  }
}

// ═══════════════════════════════════════════════════════════
//  COUNTER PAGE
// ═══════════════════════════════════════════════════════════

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

  @override
  Widget build(BuildContext context) {
    final ctrl = Vexo.find<CounterController>();
    return VexoBuilder(
      states: [ctrl.count, ctrl.step],
      builder: (_) => Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Count', style: Theme.of(context).textTheme.titleLarge),
            Text(
              '${ctrl.count.value}',
              style: Theme.of(context).textTheme.displayLarge,
            ),
            Text('× 2 = ${ctrl.doubled.value}',
                style: Theme.of(context).textTheme.titleMedium),
            Text(ctrl.isEven.value ? '(even)' : '(odd)',
                style: const TextStyle(color: Colors.grey)),
            const SizedBox(height: 32),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                FloatingActionButton(
                    heroTag: 'dec',
                    onPressed: ctrl.decrement,
                    child: const Icon(Icons.remove)),
                const SizedBox(width: 16),
                FloatingActionButton(
                    heroTag: 'inc',
                    onPressed: ctrl.increment,
                    child: const Icon(Icons.add)),
                const SizedBox(width: 16),
                FloatingActionButton(
                    heroTag: 'rst',
                    onPressed: ctrl.reset,
                    child: const Icon(Icons.refresh)),
              ],
            ),
            const SizedBox(height: 24),
            Text('Step: ${ctrl.step.value}'),
            Slider(
              value: ctrl.step.value.toDouble(),
              min: 1,
              max: 10,
              divisions: 9,
              label: '${ctrl.step.value}',
              onChanged: (v) => ctrl.step.emit(v.toInt()),
            ),
          ],
        ),
      ),
    );
  }
}

// ═══════════════════════════════════════════════════════════
//  TODO PAGE
// ═══════════════════════════════════════════════════════════

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

  @override
  Widget build(BuildContext context) {
    final ctrl = Vexo.find<TodoController>();
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(16),
          child: VexoFieldBuilder<String>(
            field: ctrl.inputField,
            builder: (_, field) => TextField(
              onChanged: (v) => field.value = v,
              onSubmitted: (_) => ctrl.addTodo(),
              decoration: InputDecoration(
                hintText: 'New todo…',
                errorText: field.isTouched ? field.error : null,
                suffixIcon: IconButton(
                  icon: const Icon(Icons.add),
                  onPressed: ctrl.addTodo,
                ),
              ),
            ),
          ),
        ),
        VexoBuilder(
          states: [ctrl.todos, ctrl.showDone],
          builder: (_) => Padding(
            padding: const EdgeInsets.symmetric(horizontal: 16),
            child: Row(
              children: [
                VexoBuilder(
                  states: [ctrl.todos],
                  builder: (_) => Text(
                    '${ctrl.doneCount.value}/${ctrl.todos.value.length} done',
                    style: const TextStyle(color: Colors.grey),
                  ),
                ),
                const Spacer(),
                const Text('Show done'),
                Switch(
                  value: ctrl.showDone.value,
                  onChanged: (_) => ctrl.showDone.toggle(),
                ),
              ],
            ),
          ),
        ),
        Expanded(
          child: VexoBuilder(
            states: [ctrl.todos, ctrl.showDone],
            builder: (_) => ListView.builder(
              itemCount: ctrl.visible.value.length,
              itemBuilder: (_, i) {
                final todo = ctrl.visible.value[i];
                return ListTile(
                  leading: Checkbox(
                    value: todo.done,
                    onChanged: (_) => ctrl.toggle(i),
                  ),
                  title: Text(
                    todo.title,
                    style: todo.done
                        ? const TextStyle(
                            decoration: TextDecoration.lineThrough)
                        : null,
                  ),
                  trailing: IconButton(
                    icon: const Icon(Icons.delete),
                    onPressed: () => ctrl.remove(i),
                  ),
                );
              },
            ),
          ),
        ),
      ],
    );
  }
}

// ═══════════════════════════════════════════════════════════
//  POSTS PAGE  (Async)
// ═══════════════════════════════════════════════════════════

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

  @override
  Widget build(BuildContext context) {
    final ctrl = Vexo.find<PostsController>();
    return Column(
      children: [
        Padding(
          padding: const EdgeInsets.all(12),
          child: VexoBuilder(
            states: [ctrl.search],
            builder: (_) => TextField(
              decoration: const InputDecoration(
                hintText: 'Search posts…',
                prefixIcon: Icon(Icons.search),
              ),
              onChanged: (v) => ctrl.search.value = v,
            ),
          ),
        ),
        Expanded(
          child: VexoAsyncBuilder<List<Post>>(
            state: ctrl.posts,
            loading: () => const Center(
                child: CircularProgressIndicator()),
            error: (e, _) => Center(
                child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                Text('Error: $e'),
                ElevatedButton(
                  onPressed: ctrl.loadPosts,
                  child: const Text('Retry'),
                ),
              ],
            )),
            data: (_) => VexoBuilder(
              states: [ctrl.search],
              builder: (__) => ListView.builder(
                itemCount: ctrl.filtered.value.length,
                itemBuilder: (_, i) {
                  final post = ctrl.filtered.value[i];
                  return ListTile(
                    leading: CircleAvatar(child: Text('${post.id}')),
                    title: Text(post.title),
                  );
                },
              ),
            ),
          ),
        ),
      ],
    );
  }
}

// ═══════════════════════════════════════════════════════════
//  AUTH PAGE  (BLoC events)
// ═══════════════════════════════════════════════════════════

class AuthPage extends StatefulWidget {
  const AuthPage({super.key});
  @override
  State<AuthPage> createState() => _AuthPageState();
}

class _AuthPageState extends State<AuthPage> {
  final _controller = TextEditingController();

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final bloc = Vexo.find<AuthBloc>();
    return Padding(
      padding: const EdgeInsets.all(24),
      child: VexoBuilder(
        states: [bloc.stateAtom],
        builder: (_) {
          final state = bloc.state;
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Icon(
                switch (state) {
                  AuthLoggedIn() => Icons.check_circle,
                  AuthFailed()   => Icons.error,
                  AuthLoading()  => Icons.hourglass_empty,
                  _              => Icons.person_outline,
                },
                size: 80,
                color: switch (state) {
                  AuthLoggedIn() => Colors.green,
                  AuthFailed()   => Colors.red,
                  _              => Colors.grey,
                },
              ),
              const SizedBox(height: 16),
              Text(
                switch (state) {
                  AuthLoggedIn(:var user) => 'Welcome, $user!',
                  AuthFailed(:var msg)    => msg,
                  AuthLoading()           => 'Loading…',
                  AuthLoggedOut()         => 'You are logged out.',
                  _                       => 'Please log in.',
                },
                style: Theme.of(context).textTheme.titleLarge,
                textAlign: TextAlign.center,
              ),
              const SizedBox(height: 32),
              if (state is! AuthLoggedIn) ...[
                TextField(
                  controller: _controller,
                  decoration:
                      const InputDecoration(hintText: 'Enter username'),
                ),
                const SizedBox(height: 16),
                ElevatedButton(
                  onPressed: state is AuthLoading
                      ? null
                      : () => bloc.add(LoginEvent(_controller.text)),
                  child: const Text('Login'),
                ),
              ] else ...[
                ElevatedButton(
                  onPressed: () {
                    _controller.clear();
                    bloc.add(LogoutEvent());
                  },
                  child: const Text('Logout'),
                ),
              ],
            ],
          );
        },
      ),
    );
  }
}
2
likes
150
points
19
downloads

Documentation

API reference

Publisher

verified publishermysteriouscoder.com

Weekly Downloads

Vexo is the ultimate Flutter state management — zero dependencies, as simple as GetX, yet as powerful as Riverpod and BLoC combined. Fast, reactive, clean.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on vexo