Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ on:

jobs:

test:
name: Run tests
verify:
name: Analyze and test
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v3

Expand All @@ -19,12 +20,19 @@ jobs:
- name: Install dependencies
run: flutter pub get

- name: Analyze project source
run: dart analyze --fatal-infos

- name: Run tests
run: flutter test

# Guards web support: a `dart:io` import in lib/ fails to compile here.
- name: Run tests on web
run: flutter test --platform chrome

publish:
name: Publish to pub.dev
needs: [ test ]
needs: [ verify ]
permissions:
id-token: write # Required for authentication using OIDC
uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1
Expand Down
50 changes: 31 additions & 19 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,40 @@
## Unreleased
## 2.0.0

**Breaking:** `ProgressDialogResult` is sealed and now has a third variant, so
exhaustive `switch` statements over it need a `Cancelled` case added.
### Breaking changes

* Fixed "Null check operator used on a null value" crash when the dialog was
dismissed before the task completed — by the Android back button, or by a
custom `builder` calling `Navigator.pop`
* Added `Cancelled<T>` result variant, `isCancelled`, and
`ProgressDialogResult.cancelled()`
* `unwrap()` throws `ProgressDialogCancelledException` for a cancelled result;
`map()` and `flatMap()` pass cancellation through
* Note: the task is not interrupted on cancellation — Dart futures cannot be
cancelled, so it runs to completion and its result is discarded
* Fixed the returned future never completing when the hosting `Navigator` is
disposed while the task is in flight — for example a nested navigator removed
from the widget tree. The call now settles with `Cancelled` instead of leaving
the caller awaiting forever
* Added Flutter Web support — `flutter_future_progress_dialog` no longer imports
`dart:io`, so pub.dev now reports all six platforms
* **Behaviour change:** `showAdaptiveProgressDialog` now selects its style from
* `ProgressDialogResult` gained a third variant, `Cancelled<T>`. The type is
sealed, so exhaustive `switch` statements over a result need a `Cancelled`
case added. See "Migrating from 1.x" in the README
* `showAdaptiveProgressDialog` now selects its style from
`Theme.of(context).platform` instead of the host operating system, matching
Flutter's own `showAdaptiveDialog`. An app that sets `ThemeData(platform: ...)`
now gets the style it asked for rather than the style of the machine it runs on

### Added

* Flutter Web support — `flutter_future_progress_dialog` no longer imports
`dart:io`, so pub.dev now reports all six platforms
* `Cancelled<T>` result variant, the `isCancelled` getter, and
`ProgressDialogResult.cancelled()`
* `ProgressDialogCancelledException`, thrown by `unwrap()` on a cancelled result

### Fixed

* "Null check operator used on a null value" crash when the dialog was dismissed
before the task completed — by the Android back button, or by a custom
`builder` calling `Navigator.pop`
* The returned future never completing when the hosting `Navigator` was disposed
while the task was in flight, which left the caller awaiting forever. The call
now settles with `Cancelled`
* `map()` and `flatMap()` now pass cancellation through instead of failing to
compile against the widened result type

### Notes

* Cancellation dismisses the dialog, not the task. Dart futures cannot be
cancelled, so the work runs to completion in the background and its result is
discarded

## 1.5.0

* Fixed crash when dialog context unmounts before task completes
Expand Down
53 changes: 52 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Developed and maintained by [Nerdy Pro](https://nerdy.pro).
- [Usage](#usage)
- [API reference](#api-reference)
- [FAQ](#faq)
- [Migrating from 1.x](#migrating-from-1x)
- [Known limitations](#known-limitations)
- [License](#license)

Expand Down Expand Up @@ -132,7 +133,7 @@ Or add it to `pubspec.yaml` manually:

```yaml
dependencies:
flutter_future_progress_dialog: ^1.5.0
flutter_future_progress_dialog: ^2.0.0
```

Then import it:
Expand Down Expand Up @@ -318,6 +319,56 @@ Yes. `flutter_future_progress_dialog` supports all six Flutter platforms — And

Yes, if you use the `BuildContext` afterwards. `showProgressDialog` closes its own dialog, but your `context` may still have been unmounted while the task was running, so guard any subsequent use of it as you would after any `await`.

## Migrating from 1.x

Version 2.0.0 has two breaking changes.

### `ProgressDialogResult` has a third variant

`Cancelled<T>` was added so that dismissing the dialog reports an outcome instead
of crashing. `ProgressDialogResult` is sealed, so any exhaustive `switch` over a
result now fails to compile with a message like:

```
The type 'ProgressDialogResult<String>' isn't exhaustively matched by the switch
cases since it doesn't match the pattern 'Cancelled<String>()'.
```

Add the missing arm:

```dart
switch (result) {
case Success(:final value):
// ...
case Failure(:final error):
// ...
case Cancelled(): // <-- add this
// The dialog was dismissed before the task delivered a result
}
```

If you would rather not handle cancellation distinctly, treat it like a failure:

```dart
if (result.isCancelled) return;
```

Code using `isSuccess`, `isError`, `unwrap()`, `map()`, or `flatMap()` keeps
compiling unchanged. Be aware that `unwrap()` now throws
`ProgressDialogCancelledException` for a cancelled result, and that a `Success`
holding `null` is still a `Success` — it is never reported as `Cancelled`.

### `showAdaptiveProgressDialog` follows the theme, not the host OS

The style is now chosen from `Theme.of(context).platform`, matching Flutter's own
`showAdaptiveDialog`. Previously it read the host operating system through
`dart:io`.

This only changes behaviour for apps that override `ThemeData.platform` — those
now get the dialog style they asked for. It is also what makes the package work
on Flutter Web, where the old check threw. To pin one style regardless of
platform, call `showProgressDialog` or `showCupertinoProgressDialog` directly.

## Known limitations

- **The task cannot be aborted.** Cancellation dismisses the dialog, not the work. See [Can the user cancel the progress dialog?](#can-the-user-cancel-the-progress-dialog)
Expand Down
6 changes: 4 additions & 2 deletions lib/src/cupertino_progress_bar_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ class _CupertinoProgressBarDialog extends State<CupertinoProgressBarDialog> {
child: LayoutBuilder(
builder: (context, constraints) {
return AnimatedPadding(
padding: MediaQuery.viewInsetsOf(context) + const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
padding: MediaQuery.viewInsetsOf(context) +
const EdgeInsets.symmetric(horizontal: 40.0, vertical: 24.0),
duration: widget.insetAnimationDuration,
curve: widget.insetAnimationCurve,
child: MediaQuery.removeViewInsets(
Expand All @@ -60,7 +61,8 @@ class _CupertinoProgressBarDialog extends State<CupertinoProgressBarDialog> {
context: context,
child: Center(
child: Container(
margin: const EdgeInsets.symmetric(vertical: _kDialogEdgePadding),
margin: const EdgeInsets.symmetric(
vertical: _kDialogEdgePadding),
width: _kCupertinoProgressSize,
height: _kCupertinoProgressSize,
child: const CupertinoPopupSurface(
Expand Down
24 changes: 18 additions & 6 deletions lib/src/result.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ sealed class ProgressDialogResult<T> {
static ProgressDialogResult<T> success<T>(T value) => Success(value);

/// Creates a failure result with the given error and optional stack trace
static ProgressDialogResult<T> failure<T>(Object error, [StackTrace? stackTrace]) => Failure(error, stackTrace);
static ProgressDialogResult<T> failure<T>(Object error,
[StackTrace? stackTrace]) =>
Failure(error, stackTrace);

/// Creates a cancelled result
static ProgressDialogResult<T> cancelled<T>() => Cancelled<T>();
Expand All @@ -59,7 +61,8 @@ sealed class ProgressDialogResult<T> {
}

/// Chains results by applying the provided function to success values
ProgressDialogResult<R> flatMap<R>(ProgressDialogResult<R> Function(T value) fn) {
ProgressDialogResult<R> flatMap<R>(
ProgressDialogResult<R> Function(T value) fn) {
return switch (this) {
Success(:final value) => fn(value),
Failure(:final error, :final stackTrace) => Failure(error, stackTrace),
Expand All @@ -79,7 +82,10 @@ class Success<T> extends ProgressDialogResult<T> {

@override
bool operator ==(Object other) =>
identical(this, other) || other is Success && runtimeType == other.runtimeType && value == other.value;
identical(this, other) ||
other is Success &&
runtimeType == other.runtimeType &&
value == other.value;

@override
int get hashCode => value.hashCode;
Expand All @@ -98,7 +104,10 @@ class Failure<T> extends ProgressDialogResult<T> {
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Failure && runtimeType == other.runtimeType && error == other.error && stackTrace == other.stackTrace;
other is Failure &&
runtimeType == other.runtimeType &&
error == other.error &&
stackTrace == other.stackTrace;

@override
int get hashCode => error.hashCode ^ stackTrace.hashCode;
Expand All @@ -121,7 +130,9 @@ class Cancelled<T> extends ProgressDialogResult<T> {
bool get isCancelled => true;

@override
bool operator ==(Object other) => identical(this, other) || other is Cancelled && runtimeType == other.runtimeType;
bool operator ==(Object other) =>
identical(this, other) ||
other is Cancelled && runtimeType == other.runtimeType;

@override
int get hashCode => runtimeType.hashCode;
Expand All @@ -135,5 +146,6 @@ class ProgressDialogCancelledException implements Exception {
const ProgressDialogCancelledException();

@override
String toString() => 'ProgressDialogCancelledException: the progress dialog was dismissed before the task completed';
String toString() =>
'ProgressDialogCancelledException: the progress dialog was dismissed before the task completed';
}
5 changes: 4 additions & 1 deletion lib/src/show_progress_dialog.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ class _Outcome<T> {
w.NavigatorState navigator,
w.Route<ProgressDialogResult<T>> route,
) {
navigator.push<ProgressDialogResult<T>>(route).then((result) => settle(result ?? Cancelled<T>())).ignore();
navigator
.push<ProgressDialogResult<T>>(route)
.then((result) => settle(result ?? Cancelled<T>()))
.ignore();

// If the navigator is disposed before the route ever builds, the dialog's
// onDispose never fires because its state was never created.
Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: flutter_future_progress_dialog
description: Show a progress dialog while a Future runs and return the result. Supports Material, Cupertino, and adaptive styles with type-safe error handling.
version: 1.5.0
version: 2.0.0
repository: https://github.com/nerdy-pro/flutter-progress-dialog
issue_tracker: https://github.com/nerdy-pro/flutter-progress-dialog/issues

Expand Down
Loading
Loading