Search code examples
flutterdartanimationbloc

Animating LinearProgressIndicator within bloc pattern


I have a bloc which emits states that contain some progress value (0-100%). This value is to be displayed with LinearProgressIndicator. Updates may occur in chunks, meaning progress can jump from, say, 0% to 30% in a single step.

Below is a simple snippet to reproduce this behavior:

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

class StartProgressEvent {}

class ProgressState {
  final double progress;
  ProgressState(this.progress);
}

class ProgressBloc extends Bloc<StartProgressEvent, ProgressState> {
  ProgressBloc() : super(ProgressState(0)) {
    on<StartProgressEvent>(_startProgress);
  }

  void _startProgress(
    StartProgressEvent event,
    Emitter<ProgressState> emit,
  ) async {
    emit(ProgressState(0.1));
    await Future.delayed(const Duration(seconds: 3));

    emit(ProgressState(0.4));
    await Future.delayed(const Duration(seconds: 3));

    emit(ProgressState(0.7));
    await Future.delayed(const Duration(seconds: 3));

    emit(ProgressState(1.0));
  }
}

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: Center(
          child: BlocProvider<ProgressBloc>(
            create: (context) => ProgressBloc()..add(StartProgressEvent()),
            child: BlocBuilder<ProgressBloc, ProgressState>(
              builder: (context, state) => LinearProgressIndicator(value: state.progress),
            ),
          ),
        ),
      ),
    );
  }
}

This snippet shows the following indicator:

linear progress indicator

Instead of updating instantly, I want my indicator to animate ongoing changes, i.e. to update smoothly from its previous state to the current one.

I found this answer suggesting that we use TweenAnimationBuilder to animate LinearProgressIndicator but it implies that we know its current value which we don't.

In a broader sense this question is not limited to progress indicator. I believe it can be framed this way: how can we animate between two consecutive "states" of a widget (either stateless or stateful) within bloc architecture?


Solution

  • You can try AnimatedFractionallySizedBox with your duration instead of LinearProgressIndicator

    class DemoApp extends StatelessWidget {
      const DemoApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            body: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                BlocProvider<ProgressBloc>(
                  create: (context) => ProgressBloc()..add(StartProgressEvent()),
                  child: BlocBuilder<ProgressBloc, ProgressState>(
                      builder: (context, state) => AnimatedFractionallySizedBox(
                            duration: Duration(seconds: 3),
                            alignment: Alignment.centerLeft,
                            widthFactor: state.progress,
                            child: Container(
                              alignment: Alignment.centerLeft,
                              width: double.infinity,
                              height: 10,
                              color: Colors.blue,
                            ),
                          )
    
                      // LinearProgressIndicator(value: state.progress),
                      ),
                ),
              ],
            ),
          ),
        );
      }
    }