I am using these packages for the following codes: flutter_bloc, oxidized, freezed, and get_it
tmdb_bloc.dart
typedef TmdbState = PageState<List<MovieEntity>>;
class TmdbBloc extends Bloc<TmdbEvent, TmdbState> {
final TmdbPublicUseCases useCases;
TmdbBloc(this.useCases) : super(const PageState.initial()) {
on<TmdbEvent>((event, emit) async {
event.map(
getPopularMovies: (_) => _runEvent(emit, useCases.proxy.getPopularMovies),
getTrendingMovies: (_) => _runEvent(emit, useCases.proxy.getTrendingMovies),
searchMovies: (e) => _runEvent(emit, () => useCases.proxy.searchMovies(e.query)),
);
});
}
void _runEvent(Emitter<TmdbState> emit, Future<TMovieListResult> Function() caller) async {
emit(const PageState.loading());
final response = await caller();
response.when(
ok: (list) => emit(PageState.loaded(list)),
err: (error) => emit(PageState.error(error.message)),
);
}
}
tmdb_events.dart
part 'tmdb_events.freezed.dart';
@freezed
sealed class TmdbEvent with _$TmdbEvent {
const factory TmdbEvent.getPopularMovies() = PopularMoviesEvent;
const factory TmdbEvent.getTrendingMovies() = TrendingMoviesEvent;
const factory TmdbEvent.searchMovies(String query) = SearchMoviesEvent;
}
page_states.dart
part 'page_states.freezed.dart';
@freezed
sealed class PageState<T extends Object> with _$PageState {
const factory PageState.initial() = InitialState;
const factory PageState.loading() = LoadingState;
const factory PageState.loaded(T data) = LoadedState;
const factory PageState.error([String? message]) = ErrorState;
}
di.dart
final sl = GetIt.instance;
void getItInit() {
// external connector
sl.registerLazySingleton<TApiDataSourceClient>(() => DioClient());
// data sources
sl.registerLazySingleton<TBaseApiClient>(() => RestApiClient(sl()));
sl.registerLazySingleton<TBaseDataProxy>(() => DataProxy(sl()));
// use cases
sl.registerLazySingleton(() => TmdbPublicUseCases(sl()));
}
main.dart
void main() async {
await TMDB.init();
getItInit();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Welcome to Flutter',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: BlocProvider(
create: (_) => TmdbBloc(sl<TmdbPublicUseCases>())..add(const TmdbEvent.getPopularMovies()),
child: const HomeScreen(),
),
);
}
}
home_screen.dart
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: _buildAppBar(),
body: Container(
color: Colors.black,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildDefaultImage(),
const SizedBox(height: 30),
const Text(
'Popular Movies',
style: TextStyle(color: Colors.white, fontSize: 20, fontWeight: FontWeight.bold),
),
BlocBuilder<TmdbBloc, TmdbState>(
builder: (context, state) {
return state.when(
initial: () => const Text('No data to show'),
loading: () => const CircularProgressIndicator(),
loaded: (list) => Text(list.runtimeType.toString()),
error: (e) => Text('Something went wrong: $e'),
);
},
),
const SizedBox(height: 20),
],
),
),
),
);
}
...
}
After running my app it keeps showing the CircularProgressIndicator widget with the following text in Debug Console:
Launching lib/main.dart on sdk gphone64 x86 64 in debug mode...
✓ Built build/app/outputs/flutter-apk/app-debug.apk.
E/flutter ( 4527): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:bloc/src/emitter.dart': Failed assertion: line 114 pos 7: '!_isCompleted':
E/flutter ( 4527):
E/flutter ( 4527): emit was called after an event handler completed normally.
E/flutter ( 4527): This is usually due to an unawaited future in an event handler.
E/flutter ( 4527): Please make sure to await all asynchronous operations with event handlers
E/flutter ( 4527): and use emit.isDone after asynchronous operations before calling emit() to
E/flutter ( 4527): ensure the event handler has not completed.
E/flutter ( 4527):
E/flutter ( 4527): **BAD**
E/flutter ( 4527): on<Event>((event, emit) {
E/flutter ( 4527): future.whenComplete(() => emit(...));
E/flutter ( 4527): });
E/flutter ( 4527):
E/flutter ( 4527): **GOOD**
E/flutter ( 4527): on<Event>((event, emit) async {
E/flutter ( 4527): await future.whenComplete(() => emit(...));
E/flutter ( 4527): });
E/flutter ( 4527):
E/flutter ( 4527): #0 _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
E/flutter ( 4527): #1 _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
E/flutter ( 4527): #2 _Emitter.call (package:bloc/src/emitter.dart:114:7)
E/flutter ( 4527): #3 TmdbBloc._runEvent.<anonymous closure> (package:movie_app_tdd_di/features/tmdb_public/3_presentation/1_bloc/tmdb_bloc.dart:27:25)
E/flutter ( 4527): #4 Ok.when (package:oxidized/src/result.dart:264:75)
E/flutter ( 4527): #5 TmdbBloc._runEvent (package:movie_app_tdd_di/features/tmdb_public/3_presentation/1_bloc/tmdb_bloc.dart:26:14)
E/flutter ( 4527): <asynchronous suspension>
E/flutter ( 4527):
D/EGL_emulation( 4527): app_time_stats: avg=18.48ms min=5.36ms max=91.61ms count=44
D/EGL_emulation( 4527): app_time_stats: avg=13.80ms min=5.36ms max=31.71ms count=61
D/EGL_emulation( 4527): app_time_stats: avg=7.21ms min=3.08ms max=12.55ms count=60
D/EGL_emulation( 4527): app_time_stats: avg=14.61ms min=9.96ms max=26.73ms count=60
D/EGL_emulation( 4527): app_time_stats: avg=16.67ms min=10.15ms max=22.99ms count=61
D/EGL_emulation( 4527): app_time_stats: avg=16.73ms min=14.36ms max=18.81ms count=60
D/EGL_emulation( 4527): app_time_stats: avg=18.50ms min=5.40ms max=100.80ms count=54
D/EGL_emulation( 4527): app_time_stats: avg=8.58ms min=3.11ms max=27.14ms count=61
Application finished.
Exited.
I have no idea what is happening, I think my TmdbBloc class is right.
Regarding this code:
BlocBuilder<TmdbBloc, TmdbState>(
builder: (context, state) {
return state.when(
initial: () => const Text('No data to show'),
loading: () => const CircularProgressIndicator(),
loaded: (list) => Text(list.runtimeType.toString()),
error: (e) => Text('Something went wrong: $e'),
);
},
),
I think it is not correctly defined because it does not indicate a specific associated event. How to fix it if that snippet is associated with TmdbEvent.getPopularMovies()
or PopularMoviesEvent
?
2
Answers
I made these changes and it is working like charm:
tmdb_cubit.dart (Replaced Bloc with Cubit)
home_screen.dart
main.dart
You declared
_runEvent
to beasync
and returnvoid
instead of aFuture<>
.That’s a bad pattern in general, because a caller of this function does not know about its asynchronous nature and cannot
await
the result.If you change
void
to an appropriateFuture<>
, BloC should be able to await the call and avoid any race conditions