0

I am writing a flutter application that is build around a computation intensive problem that takes several seconds to minutes to solve. The user triggers the computation via a button and I want to show a CircuarProgressIndicator while the computation goes on.

I also want to test this behaviour. I. e. write a widget test that presses the button, finds a CircularProgressIndicator, waits until that is gone and then inspects the result. However, I was unable to find a way to wait for the ProgressIndicator to disappear.

For reproducibility I built a minimal application that has the same issue. The application wants to compute the 5000th prime number, triggered by a button (full application code at the bottom).

The test looks like this:

  testWidgets('Wait for Progress Indicator to finish', (WidgetTester tester) async {
    await tester.pumpWidget(const MyApp());

    await tester.tap(find.text("Trigger Async"));
    await tester.pump();

    expect(find.byType(CircularProgressIndicator), findsOneWidget);

    await tester.pumpAndSettle();

    expect(find.byType(CircularProgressIndicator), findsNothing);
  });

This code fails at await tester.pumpAndSettle(); with pumpAndSettle timed out. I am aware of this post and therefore I also tried a lot of manual pumps by replacing the await tester.pumpAndSettle(); with

    for (int i = 0; i < 10000; i++){
      await tester.pump();
    }

This causes the last expect to fail. The tester can still find a CircularProgressIndicator. So that is not solving my problem either.

What am I doing wrong? Is there a way to achieve what I want to do.

Full application code:

main.dart

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const LoadingOverlay(
        child: MyHomePage(),
      ),
    );
  }
}

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

  void _doSomethingAsync(BuildContext context) async {
    LoadingOverlay.of(context).show();
    final p = await compute(getnthPrime, 5000);
    LoadingOverlay.of(context).hide();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: TextButton(
            child: const Text('Trigger Async'),
            onPressed: () => _doSomethingAsync(context),
          ),
      )
    );
  }
}

Future<int> getnthPrime(int n) async{
  int currentPrimeCount = 0;
  int candidate = 1;
  while(currentPrimeCount < n) {
    ++candidate;
    if (isPrime(candidate)) {
      ++currentPrimeCount;
    }
  }
  return candidate;
}

bool isPrime(int n) {
  int count = 0;
  for(int i = 1 ; i <= n; ++i) {
    if (n % i == 0) {
      ++count;
    }
  }
  return count == 2;
}

loading_overlay.dart

class LoadingOverlay extends StatefulWidget {
  const LoadingOverlay({Key? key, required this.child}) : super(key: key);

  final Widget child;

  static _LoadingOverlayState of(BuildContext context) {
    return context.findAncestorStateOfType<_LoadingOverlayState>()!;
  }

  @override
  State<LoadingOverlay> createState() => _LoadingOverlayState();
}

class _LoadingOverlayState extends State<LoadingOverlay> {
  bool _isLoading = false;

  void show() {
    setState(() {
      _isLoading = true;
    });
  }

  void hide() {
    setState(() {
      _isLoading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        widget.child,
        if (_isLoading)
          const Opacity(
            opacity: 0.8,
            child: ModalBarrier(dismissible: false, color: Colors.black),
          ),
        if (_isLoading)
          const Center(
            child: CircularProgressIndicator(),
          ),
      ],
    );
  }
}
drgroove
  • 1
  • 1

0 Answers0