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

A button that displays an indicator during asynchronous processing and allows callbacks to be executed after completion or exception catch.

example/lib/main.dart

// ignore_for_file: public_member_api_docs

import 'dart:math';

import 'package:awaitable_button/awaitable_button.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';

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

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool useMaterial3 = true;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Awaitable Button',
      theme: ThemeData(
        useMaterial3: useMaterial3,
        colorSchemeSeed: useMaterial3 ? Colors.green : null,
        primarySwatch: useMaterial3 ? null : Colors.blue,
        elevatedButtonTheme: ElevatedButtonThemeData(
          style: ElevatedButton.styleFrom(
            minimumSize: const Size.fromHeight(48),
          ),
        ),
        progressIndicatorTheme: const ProgressIndicatorThemeData(
          color: Colors.yellow,
          circularTrackColor: Colors.orange,
        ),
      ),
      home: MyHomePage(
        useMaterial3: useMaterial3,
        onFabPressed: () => setState(() {
          useMaterial3 = !useMaterial3;
        }),
      ),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({
    super.key,
    required this.useMaterial3,
    required this.onFabPressed,
  });

  final bool useMaterial3;

  final VoidCallback onFabPressed;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Awaitable Button'),
      ),
      floatingActionButton: FloatingActionButton.extended(
        onPressed: widget.onFabPressed,
        label: Text(widget.useMaterial3 ? 'Use Material 2' : 'Use Material 3'),
      ),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: <Widget>[
          Wrap(
            children: [
              TextButton(
                onPressed: () async {
                  await launchUrl(
                    Uri.parse('https://pub.dev/packages/awaitable_button'),
                  );
                },
                child: const Text('pub.dev'),
              ),
              TextButton(
                onPressed: () async {
                  await launchUrl(
                    Uri.parse(
                      'https://github.com/altive/flutter_widgets/tree/main/packages/awaitable_button',
                    ),
                  );
                },
                child: const Text('Repository'),
              ),
            ],
          ),
          _Button(
            title: 'AwaitableElevatedButton',
            description: '''
Screen transition after processing is completed,
Success or Failure.''',
            useDivider: true,
            button: AwaitableElevatedButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 2));
              },
              whenComplete: (_) async {
                await Navigator.of(context).push<void>(
                  MaterialPageRoute(
                    builder: (context) {
                      return Scaffold(
                        appBar: AppBar(),
                        body: const Center(child: Text('Next page')),
                      );
                    },
                  ),
                );
              },
              child: const Text('Before pressing'),
            ),
          ),
          _Button(
            description: 'Can change the text being processed',
            button: AwaitableElevatedButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 2));
              },
              executingChild: const Text('Executing...'),
              child: const Text('Before pressing'),
            ),
          ),
          _Button(
            description: '''
Pass the result of processing to [whenComplete]. 
Success or Failure.''',
            button: AwaitableElevatedButton<String>(
              onPressed: () async {
                try {
                  await Future<void>.delayed(const Duration(seconds: 2));
                  if (Random().nextBool()) {
                    return 'Succeeded';
                  } else {
                    throw Exception();
                  }
                } on Exception {
                  return 'Failed';
                }
              },
              whenComplete: (value) async {
                await showDialog<void>(
                  context: context,
                  builder: (context) {
                    return AlertDialog(
                      title: Text(value),
                      actions: [
                        TextButton(
                          onPressed: Navigator.of(context).pop,
                          child: const Text('OK'),
                        ),
                      ],
                    );
                  },
                );
              },
              child: const Text('Before pressing'),
            ),
          ),
          _Button(
            description: '''
Handling Exceptions with [onError],
Success or Failure.''',
            button: AwaitableElevatedButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 2));
                if (Random().nextBool()) {
                  throw Exception();
                }
              },
              onError: (exception, stackTrace) {
                ScaffoldMessenger.of(context).showSnackBar(
                  const SnackBar(content: Text('Failed.')),
                );
              },
              whenComplete: (_) async {
                await showDialog<void>(
                  context: context,
                  builder: (context) {
                    return AlertDialog(
                      title: const Text('Succeeded'),
                      actions: [
                        TextButton(
                          onPressed: Navigator.of(context).pop,
                          child: const Text('OK'),
                        ),
                      ],
                    );
                  },
                );
              },
              child: const Text('Before pressing'),
            ),
          ),
          _Button(
            title: 'AwaitableOutlinedButton',
            description: 'It is the same as [AwaitableElevatedButton] '
                'except for the style.',
            useDivider: true,
            button: AwaitableOutlinedButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 2));
              },
              executingChild: const Text('Executing...'),
              child: const Text('Before pressing'),
            ),
          ),
          _Button(
            title: 'AwaitableTextButton',
            description: 'It is the same as [AwaitableElevatedButton] '
                'except for the style.',
            useDivider: true,
            button: AwaitableTextButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 2));
              },
              executingChild: const Text('Executing...'),
              child: const Text('Before pressing'),
            ),
          ),
          if (widget.useMaterial3) ...[
            _Button(
              title: 'AwaitableFilledButton',
              description: 'It is the same as [AwaitableElevatedButton] '
                  'except for the style.',
              useDivider: true,
              button: AwaitableFilledButton<void>(
                onPressed: () async {
                  await Future<void>.delayed(const Duration(seconds: 2));
                },
                executingChild: const Text('Executing...'),
                child: const Text('Before pressing'),
              ),
            ),
            _Button(
              title: 'AwaitableFilledTonalButton',
              description: 'It is the same as [AwaitableElevatedButton] '
                  'except for the style.',
              useDivider: true,
              button: AwaitableFilledTonalButton<void>(
                onPressed: () async {
                  await Future<void>.delayed(const Duration(seconds: 2));
                },
                executingChild: const Text('Executing...'),
                child: const Text('Before pressing'),
              ),
            ),
          ],
          _Button(
            title: 'AwaitableIconButton',
            description: 'Success or Failure handling',
            useDivider: true,
            button: AwaitableIconButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 2));
                if (Random().nextBool()) {
                  throw Exception();
                }
              },
              whenComplete: (_) async {
                await showDialog<void>(
                  context: context,
                  builder: (context) {
                    return AlertDialog(
                      title: const Text('Succeeded'),
                      actions: [
                        TextButton(
                          onPressed: Navigator.of(context).pop,
                          child: const Text('OK'),
                        ),
                      ],
                    );
                  },
                );
              },
              onError: (exception, stackTrace) {
                ScaffoldMessenger.of(context).showSnackBar(
                  SnackBar(content: Text('$exception, $stackTrace')),
                );
              },
              icon: const Icon(Icons.timer),
            ),
          ),
          _Button(
            description: 'Can change the icon being processed',
            button: AwaitableIconButton<void>(
              onPressed: () async {
                await Future<void>.delayed(const Duration(seconds: 1));
              },
              executingIcon: const Icon(Icons.timer_sharp),
              icon: const Icon(Icons.timer),
            ),
          ),
        ],
      ),
    );
  }
}

class _Button extends StatelessWidget {
  const _Button({
    this.title,
    required this.description,
    required this.button,
    this.useDivider = false,
  });

  final String? title;
  final String description;
  final Widget button;
  final bool useDivider;

  @override
  Widget build(BuildContext context) {
    final title = this.title;
    return Column(
      crossAxisAlignment: CrossAxisAlignment.stretch,
      children: [
        if (useDivider) ...[
          const Divider(),
          const SizedBox(height: 32),
        ],
        if (title != null) ...[
          Text(
            title,
            style: Theme.of(context).textTheme.headline6,
          ),
          const SizedBox(height: 16),
        ],
        Text(
          description,
          style: Theme.of(context).textTheme.caption,
        ),
        const SizedBox(height: 16),
        button,
        const SizedBox(height: 32),
      ],
    );
  }
}
20
likes
150
points
283
downloads

Publisher

verified publisheraltive.co.jp

Weekly Downloads

A button that displays an indicator during asynchronous processing and allows callbacks to be executed after completion or exception catch.

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on awaitable_button