0

When I try to use BlocBuilder inside ReorderableListView i get an Could not find the correct Provider<ThemeCubit> above this BlocBuilder<ThemeCubit, ThemeState> Widget error. The error occurs when I drag an item inside list.

Example:

List<int> _items = List<int>.generate(50, (int index) => index);

ReorderableListView(
      children: <Widget>[
        for (int index = 0; index < _items.length; index += 1)
          Container(
            key: Key(index.toString()),
            child: BlocBuilder<ThemeCubit, ThemeState>(
              builder: (___, ____) {
                return Text('Item ${_items[index]}');
              }
            )
          )
      ],
      onReorder: (int oldIndex, int newIndex) {},
    )

Weirdly enough, if I try to access the bloc data with context.read<ThemeCubit>() I don't get an error.


Reproducible example (main.dart)

Problem is reproduced when you drag an item in list.

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SimpleCubit extends Cubit<int>
{
  SimpleCubit() : super(0);
}

void main() async {
  runApp(MyTrip());
}

class AppName extends StatelessWidget {
  AppName({super.key});

  final List<int> _items = List<int>.generate(50, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MultiBlocProvider(
          providers: [
            BlocProvider<SimpleCubit>(
              create: (_) => SimpleCubit()
            ),
          ],
          child: ReorderableListView(
            children: <Widget>[
              for (int index = 0; index < _items.length; index += 1)
                Container(
                  key: Key(index.toString()),
                  child: BlocBuilder<SimpleCubit, int>(
                    builder: (_, state) {
                      return Text(state.toString());
                    }
                  )
                )
            ],
            onReorder: (int oldIndex, int newIndex) {},
          ),
        ),
      )
    );
  }
}
RandomSlav
  • 507
  • 3
  • 13

2 Answers2

1

Just wrap your MaterialApp with the BlocProvider, your error indicates that it does not find the context, since you only provide it to the ReorderableListView widget, so you better create your bloc globally so that any widget with context can call it that way: (BUT IT HAS TO BE IN YOUR MAIN MATERIAL APP in MyTrip())

void main() async {
  runApp(MyTrip());
}

class MyTrip extends StatelessWidget {

 MyTrip({super.key});
    
 @override
  Widget build(BuildContext context) {
    return MultiBlocProvider( //  like this
      providers: [
        BlocProvider<SimpleCubit>(create: (_) => SimpleCubit()),
      ],
      child: MaterialApp(
        [...] // here it has to be your routes, theme, translation, etc...
      ),
    );
  }
Daniel Roldán
  • 1,328
  • 3
  • 20
  • Now it works, but does that mean that it's wrong in the [documentation](https://pub.dev/packages/flutter_bloc#maindart) as well? – RandomSlav Jun 03 '22 at 14:03
  • No, the documentation is not wrong but i answer some question, because you have to use BlocProvider.value, take a look : https://stackoverflow.com/questions/72314677/how-to-update-todo-item/72315019#72315019 or this : https://stackoverflow.com/questions/72256983/could-not-find-the-correct-providertokenbloc-above-this-app-widget/72257517#72257517 – Daniel Roldán Jun 03 '22 at 14:24
0

I could not reproduce the issue with the example provided above. But I can assume what your problem is.

I modified your Reproducible example to see the same error you get:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SimpleCubit extends Cubit<int> {
  SimpleCubit() : super(0);

  void onPress() {
    print('press');
  }
}

void main() async {
  runApp(MyTrip());
}

class MyTrip extends StatelessWidget {
  MyTrip({super.key});

  final List<int> _items = List<int>.generate(50, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MultiBlocProvider(
          providers: [
            BlocProvider<SimpleCubit>(create: (_) => SimpleCubit()),
          ],
          child: ReorderableListView(
            children: <Widget>[
              for (int index = 0; index < _items.length; index += 1)
                Container(
                  key: Key(index.toString()),
                  child: BlocBuilder<SimpleCubit, int>(
                    builder: (_, state) {
                      return GestureDetector(
                        onTap: context.read<SimpleCubit>().onPress,
                        child: Text(state.toString()),
                      );
                    },
                  ),
                ),
            ],
            onReorder: (int oldIndex, int newIndex) {},
          ),
        ),
      ),
    );
  }
}

The explanation of this error is that you cannot call bloc methods if you provided this bloc inside the same build method, because you're referring to the context that does not contain such bloc:

enter image description here

Please see the modified version of your example that should work:

import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class SimpleCubit extends Cubit<int> {
  SimpleCubit() : super(0);

  void onPress() {
    print('press');
  }
}

void main() async {
  runApp(MyTrip());
}

class MyTrip extends StatelessWidget {
  const MyTrip({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MultiBlocProvider(
          providers: [
            BlocProvider<SimpleCubit>(create: (_) => SimpleCubit()),
          ],
          child: _MyTrip(),
        ),
      ),
    );
  }
}

class _MyTrip extends StatelessWidget {
  _MyTrip({super.key});

  final List<int> _items = List<int>.generate(50, (int index) => index);

  @override
  Widget build(BuildContext context) {
    return ReorderableListView(
      children: <Widget>[
        for (int index = 0; index < _items.length; index += 1)
          Container(
            key: Key(index.toString()),
            child: BlocBuilder<SimpleCubit, int>(
              builder: (_, state) {
                return GestureDetector(
                  onTap: context.read<SimpleCubit>().onPress,
                  child: Text(state.toString()),
                );
              },
            ),
          ),
      ],
      onReorder: (int oldIndex, int newIndex) {},
    );
  }
}

Here we separated bloc provisioning and usage. That's how it's designed to use.

Alex Verbitski
  • 499
  • 3
  • 12
  • The example I provided was simplified, in reality I don't use `SimpleCubit` in the main page, but rather in next page (exactly like you did in answer). You couldn't repordouced because **the problem is reproduced by dragging the item in `ReorderableListView`**. – RandomSlav Jun 02 '22 at 20:05
  • Got it, I'll check – Alex Verbitski Jun 02 '22 at 20:14
  • @RandomSlav could you please clarify how do you use ThemeCubit inside your app? I'm asking because I'm not sure I understand the case when it's needed to be inside of the ReordableListWidget – Alex Verbitski Jun 02 '22 at 20:23
  • I have a button that uses the `ThemeCubit`. The button is inside the `ReordableListWidget`. – RandomSlav Jun 02 '22 at 21:47
  • Could you use ThemeCubit builder outside the ReorderableListView? – Alex Verbitski Jun 02 '22 at 21:48
  • Maybe but I would have to change some things because button is a sepereate widget used in many places across the app. – RandomSlav Jun 02 '22 at 23:05