flutter 如何管理BLoC类型的事件?

xiozqbni  于 2023-11-21  发布在  Flutter
关注(0)|答案(2)|浏览(134)

我使用这些软件包来处理以下代码:flutter_blocoxidizedfreezedget_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;
}

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

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


在运行我的应用程序后,它会继续显示CircularProgressIndicator小部件,并在控制台中显示以下文本:

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.


我不知道发生了什么,我认为我的TmdbBloc类是正确的。
关于这个代码:

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


我认为它没有正确定义,因为它没有表示特定的关联事件。如果该片段与TmdbEvent.getPopularMovies()PopularMoviesEvent关联,如何修复它?

ttcibm8c

ttcibm8c1#

你将_runEvent声明为async,并返回void而不是Future<>。这通常是一个不好的模式,因为这个函数的调用者不知道它的异步特性,不能将结果await

void _runEvent(Emitter<TmdbState> emit, Future<TMovieListResult> Function() caller) async {}

字符串
如果将void更改为适当的Future<>,BloC应该能够等待调用并避免任何竞争条件

flseospp

flseospp2#

我做了这些改变,它像魅力一样工作:

tmdb_cubit.dart(* 将Bloc替换为Cubit*)

typedef TmdbState = PageState<List<MovieEntity>>;

sealed class TmdbCubit<T extends TmdbEvent> extends Cubit<TmdbState> {
  final TmdbPublicUseCases useCases;

  TmdbCubit(this.useCases) : super(const PageState.initial());

  Future<void> _runEvent(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)),
    );
  }
}

final class PopularMoviesCubit extends TmdbCubit<PopularMoviesEvent> {
  PopularMoviesCubit(super.useCases);
  void call() async => await _runEvent(useCases.proxy.getPopularMovies);
}

final class TrendingMoviesCubit extends TmdbCubit<TrendingMoviesEvent> {
  TrendingMoviesCubit(super.useCases);
  void call() async => await _runEvent(useCases.proxy.getTrendingMovies);
}

final class SearchMoviesCubit extends TmdbCubit<SearchMoviesEvent> {
  SearchMoviesCubit(super.useCases);
  void call(String query) async => await _runEvent(() => useCases.proxy.searchMovies(query));
}

字符串

home_screen.dart

...
              BlocBuilder<TrendingMoviesCubit, TmdbState>(
                builder: (context, state) {
                  return state.when(
                    initial: () => const Text('No data to show'), 
                    loading: () => const CircularProgressIndicator(), 
                    loaded: (list) => Text((list as List<MovieEntity>).runtimeType.toString()),
                    error: (e) => Text('Something went wrong: $e'),
                  );
                },
              ),
...

主.dart

....
      home: BlocProvider(
        create: (_) => TrendingMoviesCubit(sl<TmdbPublicUseCases>())..call(),
....

相关问题