felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.77k stars 3.39k forks source link

fix: Bloc not calling while calling event with cascade in BlocProvider. But working fine if assigned to a variable and passed to BlocProvider. #4109

Closed aswanath closed 6 months ago

aswanath commented 6 months ago

I have given code like below. But the accountsGroup is of empty list as the FetchAccountGroups() event is not calling while debugged.

class AccountsScreen extends StatelessWidget {
  final bool fromAddEditScreen;

  const AccountsScreen({
    super.key,
    this.fromAddEditScreen = false,
  });

  @override
  Widget build(BuildContext context) {
    return BlocProvider<AccountsBloc>(
      create: (context) => getIt<AccountsBloc>()
        ..add(
          FetchAccountGroups(),
        ),
      child: Builder(
        builder: (context) {
          return Scaffold(
            appBar: CommonAppBar(
              showBackButton: fromAddEditScreen,
              title: context.t.accounts,
              leftPadding: fromAddEditScreen ? null : 18.0,
              action: Padding(
                padding: const EdgeInsets.only(right: 12.0),
                child: IconButton(
                  visualDensity: VisualDensity.comfortable,
                  style: IconButton.styleFrom(
                    padding: const EdgeInsets.symmetric(horizontal: 12.0),
                    backgroundColor: Colors.white,
                  ),
                  onPressed: () async {
                    await showDialog(
                      context: context,
                      builder: (_) => AddEditAccountsDialog(
                        accountGroups: BlocProvider.of<AccountsBloc>(context, listen: true).getAccountGroups,
                        isEdit: false,
                        onSaveTapped: () {},
                      ),
                    );
                  },
                  icon: Row(
                    children: [
                      SvgPicture.asset(
                        SvgAssetPaths.plusIcon,
                        colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
                      ),
                      8.hGap,
                      Text(
                        context.t.accountScreen.addAccount,
                        style: context.textStyleTheme.medium14.copyWith(
                          color: Colors.black,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

But if I change my code like this (assign bloc into a variable and pass to the bloc provider), it works perfectly fine. What will be the issue as I am not able to find it. Is it the issue with Flutter or with Bloc?

class AccountsScreen extends StatelessWidget {
  final bool fromAddEditScreen;

  const AccountsScreen({
    super.key,
    this.fromAddEditScreen = false,
  });

  @override
  Widget build(BuildContext context) {
    final accountsBloc = getIt<AccountsBloc>()
      ..add(
        FetchAccountGroups(),
      );
    return BlocProvider<AccountsBloc>(
      create: (context) => accountsBloc,
      child: Builder(
        builder: (context) {
          return Scaffold(
            appBar: CommonAppBar(
              showBackButton: fromAddEditScreen,
              title: context.t.accounts,
              leftPadding: fromAddEditScreen ? null : 18.0,
              action: Padding(
                padding: const EdgeInsets.only(right: 12.0),
                child: IconButton(
                  visualDensity: VisualDensity.comfortable,
                  style: IconButton.styleFrom(
                    padding: const EdgeInsets.symmetric(horizontal: 12.0),
                    backgroundColor: Colors.white,
                  ),
                  onPressed: () async {
                    await showDialog(
                      context: context,
                      builder: (_) => AddEditAccountsDialog(
                        accountGroups: BlocProvider.of<AccountsBloc>(context, listen: true).getAccountGroups,
                        isEdit: false,
                        onSaveTapped: () {},
                      ),
                    );
                  },
                  icon: Row(
                    children: [
                      SvgPicture.asset(
                        SvgAssetPaths.plusIcon,
                        colorFilter: const ColorFilter.mode(Colors.black, BlendMode.srcIn),
                      ),
                      8.hGap,
                      Text(
                        context.t.accountScreen.addAccount,
                        style: context.textStyleTheme.medium14.copyWith(
                          color: Colors.black,
                        ),
                      ),
                    ],
                  ),
                ),
              ),
            ),
          );
        },
      ),
    );
  }
}

I will provide the bloc event also:

  List<AccountGroupsModel> _accountGroups = [];

  List<AccountGroupsModel> get getAccountGroups => _accountGroups;

  FutureOr<void> _onFetchAccountGroups(FetchAccountGroups event, emit) async {
    final int initialItemCount = _accountGroups.length;
    _accountGroups = await _accountsRepository.getAccountGroups();
    if (_accountGroups.length > initialItemCount) {
      emit(AddedAccountGroupsSuccessfully());
    }
    emit(
      FetchedAccountGroups(
        accountGroups: _accountGroups,
      ),
    );
  }
aswanath commented 6 months ago

As I found out that the issue is as follows: While in the first case as I am using cascade to add an event and in the widget subtree I am not using any BlocBuilder or Listeners, so the bloc is managing to not even create the instance and pass to the widget tree. And while I am calling this

BlocProvider.of<AccountsBloc>(context, listen: true).getAccountGroups

then only the tree is activated with bloc but that event I added at first is void and never called.

So, I solved this issue by adding lazy=true inside the BlocProvider which will anyway pass down the bloc even if it is not using anywhere.