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);
}
}
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),
),
);
}
}