Search code examples
flutterbloc

flutter bloc library percentage progress bar


How can a bloc show percentage progress bar

//For example, there is a regular bloc 
@override
  Stream<JobState> mapEventToState(JobEvent event) async* {
    if (event is HardJobEvent) {
      yield* _mapHardJobToState();
    }
  }
  
 
  Stream<UpdateState> _mapHardJobToState() async* {
    try {
      //It is necessary to display a progress bar for this method.
      await doSomeHardJob();
    } catch (e) {
      print(e);
    }
  }
  
  
  doSomeHardJob() async* {
     for( var i = 1 ; i < 1000; i++ ) { 
       //This yield does not work. Doesn't display any errors
       //State not transfer
       yield HardJob(nowCounter: i);
    }   
  }

Solution

  • I use cubit instead of bloc. but the technique should be similar. I have a broadcast stream controller in the payload generating function. In the event dispatcher (this should be your bloc) I listen to it and emit loading states with a double value. the bloc builder in the widgets can react to it. check put my little implementation:

    import 'dart:async';
    import 'package:flutter/material.dart';
    import 'package:flutter_bloc/flutter_bloc.dart';
    
    /// Model
    class Data {
      final DateTime date;
      Data(this.date);
    }
    
    /// Repo
    class DataRepository {
        /// here comes the trick:
        final StreamController<double> progress = StreamController.broadcast(); // make it broadcast to allow multiple subcribtions
    
    Future<List<Data>> generateData() async {
        /// here comes your time taking work
        progress.sink.add(0.0); // set progress to 0
    
        List<Data> payload =
        []; // this will be the data you want to transport in the loadED event
    
    await Future.forEach(List.generate(10, (i) => i), (int i) async {
      /// here comes the progess; dont send it too late otherwise
      /// the loadED state will be followed by a loadING state and
      ///  you will see a never ending spinner
      progress.sink.add(i / 10);
    
      /// this would be like eg a http call
      payload.add(Data(DateTime.now()));
      await Future.delayed(
          const Duration(seconds: 1)); // simulate the time consuming action
    });
    
        return payload;
      }
    }
    
    /// State
    @immutable
    abstract class DataState {}
    
    class DataInitial extends DataState {
      DataInitial();
    }
    
    class DataLoading extends DataState {
      /// this state will emit the actual progress value to the spinner
      final double progress;
      DataLoading(this.progress);
    
      /// boilerplate code to tell state events apart from each other even     though they are of the same type
      @override
      bool operator ==(Object other) {
        if (identical(this, other)) return true;
        return other is DataLoading && other.progress == progress;
      }
    
      @override
      int get hashCode => progress.hashCode;
    }
    
    class DataLoaded extends DataState {
      /// this state will transport the payload data
      final List<Data> data;
      DataLoaded(this.data);
    
      /// same as aboth
      @override
      bool operator ==(Object other) {
        if (identical(this, other)) return true;
        return other is DataLoaded && other.data == data;
      }
    
      @override
      int get hashCode => data.hashCode;
    }
    
    /// Cubit
    class DataCubit extends Cubit<DataState> {
      /// Cubit works like a simple Bloc
      final DataRepository dataRepository;
    
      /// the repo will do the actual work
      DataCubit({required this.dataRepository}) : super(DataInitial());
    
      Future<void> generateData() async {
        /// this will bring the progress value to the loading spinner widget.
        /// each time a new progress is made a new DataLoadING state will be emitted
        dataRepository.progress.stream
        .listen((progress) => emit(DataLoading(progress)));
    
        /// this await is sincere; it will take aaages; really
        final payload = await dataRepository.generateData();
    
        /// finally the payload will be sent to the widgets
        emit(DataLoaded(payload));
      }
    }
    
    late DataRepository dataRepository;
    
    void main() {
      /// init the repo that will do the heavy lifting like eg a db or http     request
      dataRepository = DataRepository();
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        /// makes the cubit (like a baby bloc) available to all child widgets of the app
        return BlocProvider<DataCubit>(
          create: (context) => DataCubit(dataRepository: dataRepository),
          child: const MaterialApp(
            title: 'Flutter Progress Demo',
            home: MyHomePage(),
          ),
        );
      }
    }
    
    class MyHomePage extends StatelessWidget {
      const MyHomePage({Key? key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Progress Example'),
          ),
          body: Center(
            child: BlocBuilder<DataCubit, DataState>(
              builder: (context, state) {
                if (state is DataLoading) {
                  return CircularProgressIndicator.adaptive(
                    value: state.progress,
                      );
                    } else if (state is DataLoaded) {
                  List<Data> longAnticipatedData = state.data;
                  return ListView.builder(
                      itemCount: longAnticipatedData.length,
                      itemBuilder: (context, i) => ListTile(
                        title:
                              Text(longAnticipatedData[i].date.toIso8601String()),
                          ));
                } else {
                  /// initial state
                  return const Center(
                    child: Text('press the FAB'),
                  );
                }
              },
            ),
          ),
          floatingActionButton: FloatingActionButton(
            /// generate data; will take a good while
            onPressed: () => context.read<DataCubit>().generateData(),
            child: const Icon(Icons.start),
          ),
        );
      }
    }