flutter_solo 0.2.0 copy "flutter_solo: ^0.2.0" to clipboard
flutter_solo: ^0.2.0 copied to clipboard

State management for Flutter: sequential jobs, exclusive state ownership, cooperative cancellation, rebuilds through ValueListenable.

example/lib/main.dart

import 'dart:async';

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

/// The Usage fragment of README.md, with a fake API and a screen around it.
void main() => runApp(const MaterialApp(home: ProfileScreen()));

/// A fake profile service: slow, and failing every third call.
class ProfileApi {
  int _calls = 0;

  Future<String> fetchName() async {
    await Future<void>.delayed(const Duration(seconds: 2));
    if (++_calls % 3 == 0) {
      throw StateError('the network is having a day');
    }

    return 'Ada Lovelace';
  }
}

sealed class Profile {}

final class Empty extends Profile {}

final class Loading extends Profile {}

final class Loaded extends Profile {
  final String name;

  Loaded(this.name);
}

/// One state, one job over it, and a policy for the second tap.
final class ProfileController extends SoloListenable<Profile> {
  final ProfileApi api;

  ProfileController(this.api) : super(Empty());

  Job<String> load() => run<Profile, String>(
        key: 'load',
        policy: Policy.droppable, // a second tap returns the first job
        (ctx) async {
          ctx.emit(Loading());
          final name = await ctx.wait(api.fetchName);
          ctx.emit(Loaded(name));

          return name;
        },
      );
}

class ProfileScreen extends StatefulWidget {
  const ProfileScreen({super.key});

  @override
  State<ProfileScreen> createState() => _ProfileScreenState();
}

class _ProfileScreenState extends State<ProfileScreen> {
  final controller = ProfileController(ProfileApi());

  @override
  void dispose() {
    // Nothing closes the controller for you, and closing it cancels the
    // job that is still in flight: leaving the screen is that cheap.
    unawaited(controller.close());
    super.dispose();
  }

  Future<void> _load() async {
    switch (await controller.load().done) {
      case Done(:final value):
        _say('hello $value');
      case Failed(:final error):
        _say('$error');
      case Cancelled():
        break; // left the screen, or a second tap while the first ran
    }
  }

  void _say(String message) {
    if (!mounted) {
      return;
    }
    ScaffoldMessenger.of(context)
        .showSnackBar(SnackBar(content: Text(message)));
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(title: const Text('flutter_solo')),
        body: Center(
          child: ValueListenableBuilder<Profile>(
            valueListenable: controller,
            builder: (context, state, _) => switch (state) {
              Empty() => TextButton(
                  onPressed: _load,
                  child: const Text('Load'),
                ),
              Loading() => const CircularProgressIndicator(),
              Loaded(:final name) => Text(name),
            },
          ),
        ),
      );
}
0
likes
160
points
--
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

State management for Flutter: sequential jobs, exclusive state ownership, cooperative cancellation, rebuilds through ValueListenable.

Repository (GitHub)
View/report issues

Topics

#state-management #async #cancellation #concurrency

License

BSD-3-Clause (license)

Dependencies

flutter, solo

More

Packages that depend on flutter_solo