Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flutter: Unable to find BLoC of type _BlocProvider<UserBloc>. #4

Closed
joshcase opened this issue Jul 5, 2019 · 3 comments
Closed

flutter: Unable to find BLoC of type _BlocProvider<UserBloc>. #4

joshcase opened this issue Jul 5, 2019 · 3 comments

Comments

@joshcase
Copy link

joshcase commented Jul 5, 2019

Hi Roberto,

Thanks for providing a wonderfully useful class to a Flutter novice like me - it's very much appreciated. I'm not sure whether this is an issue with the class itself or (far more likely) my understanding of how inherited widgets work.

I'm building a medical quiz app, and require the [UserBloc] object to be accessible anywhere in the app, to allow it to receive events regarding the user's progress through the various levels and so forth. To do this, I've decided to wrap my [MaterialApp] in your [BlocProvider] that (should) provide the [UserBloc] wherever I need it. However, I'm getting some "Unable to find BloC of type _BlocProvider" in some unexpected places.

See the following:
main.dart

import 'package:dailymedicaltrivia2/ui/pages/PrePlayPage.dart';
import 'package:dailymedicaltrivia2/ui/pages/CareerPage.dart';
import 'package:generic_bloc_provider/generic_bloc_provider.dart';
import 'package:dailymedicaltrivia2/data/UserRepository.dart';
import 'package:dailymedicaltrivia2/data/AppDatabaseProvider.dart';
import 'package:dailymedicaltrivia2/data/database/AppDatabase.dart';
import 'package:dailymedicaltrivia2/data/DMTAPIProvider.dart';
import 'package:dailymedicaltrivia2/data/LocalJsonProvider.dart';
import 'package:dailymedicaltrivia2/bloc/User/UserBloc.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  UserBloc userBloc;

  _MyAppState() {
    UserRepository _userRepository = UserRepository(
      apiProvider: DMTAPIProvider(),
      dbProvider: AppDatabaseProvider(database: AppDatabase('data.db')),
      jsonProvider: LocalJsonProvider(),
    );
    userBloc = UserBloc(repository: _userRepository);
  }

  @override
  Widget build(BuildContext context) {
    return BlocProvider<UserBloc>(
      bloc: userBloc,
      child: MaterialApp(
        debugShowCheckedModeBanner: false,
        routes: {
          '/': (context) => PrePlayPage(),
          '/career': (context) => CareerPage(),
        },
        title: 'Daily Medical Trivia',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          highlightColor: Colors.white,
          fontFamily: 'Montserrat',
        ),
      ),
    );
  }
}

PrePlayPage.dart

import 'package:dailymedicaltrivia2/ui/pages/CareerPage.dart';

class PrePlayPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        child: Column(
          mainAxisSize: MainAxisSize.max,
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Builder(builder: (BuildContext context) {
              return Container(
                width: MediaQuery.of(context).size.width * 0.65,
                height: MediaQuery.of(context).size.width * 0.65,
                child: Image.asset('assets/images/icon.png'),
                decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(
                    Radius.circular(15.0),
                  ),
                ),
              );
            }),
            FlatButton(
              child: Text('Play'),
              onPressed: () {
                Navigator.pushNamed(context, '/career');
              },
            ),
          ],
        ),
      ),
    );
  }
}

CareerPage.dart

import 'package:dailymedicaltrivia2/ui/CareerGUI.dart';
import 'package:dailymedicaltrivia2/ui/CareerPath.dart';
import 'package:dailymedicaltrivia2/bloc/User/UserBloc.dart';
import 'package:generic_bloc_provider/generic_bloc_provider.dart';

class CareerPage extends StatefulWidget {
  @override
  _CareerPageState createState() => _CareerPageState();
}

class _CareerPageState extends State<CareerPage> {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: StreamBuilder<UserState>(
          stream: BlocProvider.of<UserBloc>(context).stateStream, //This is <Line18>, does NOT throw
          builder: (context, snapshot) {
            if (snapshot.hasError) {
              return Center(child: Text('An error occurred'));
            } else if (!snapshot.hasData) {
              return Center(child: Text(''));
            } else {
              if (snapshot.data.levels.length == 0) {
                return Container(
                  height: double.infinity,
                  width: double.infinity,
                  color: const Color.fromRGBO(94, 148, 184, 1),
                  child: Center(
                    child: CircularProgressIndicator(
                      valueColor:
                          new AlwaysStoppedAnimation<Color>(Colors.white),
                    ),
                  ),
                );
              } else {
                UserBloc bloc = BlocProvider.of<UserBloc>(context); //<Line 38>, does NOT throw
                return Stack(
                  children: <Widget>[
                    Positioned.fill(
                      child: Container(
                          color: const Color.fromRGBO(171, 202, 223, 1.0)),
                    ),
                    CareerPath(levels: snapshot.data.levels), //This is <Line45>, DOES throw when building
                    CareerGUI(),
                  ],
                );
              }
            }
          }),
    );
  }
}

CareerPath.dart

import 'package:generic_bloc_provider/generic_bloc_provider.dart';
import 'package:dailymedicaltrivia2/bloc/user/UserBloc.dart';
import 'package:dailymedicaltrivia2/model/Level.dart';
import 'package:dailymedicaltrivia2/ui/dialogs/StartLevelDialog.dart';

class CareerPath extends StatefulWidget {
  final List<Level> levels;

  CareerPath({
    @required this.levels,
  });

  @override
  _CareerPathState createState() => _CareerPathState();
}

class _CareerPathState extends State<CareerPath> {
  List<PathChunk> _chunks;

  @override
  Widget build(BuildContext context) {
    UserBloc bloc = BlocProvider.of<UserBloc>(context); //DOES throw
    _chunks = _createChunks(widget.levels);
    return ListView.builder(
        physics: ClampingScrollPhysics(),
        reverse: true,
        itemCount: _chunks.length,
        itemBuilder: (context, i) {
          return _chunks[i];
        });
  }

  List<PathChunk> _createChunks(List<Level> _levels) {
    int _levelsPerChunk = 32;
    int _indexOffset = 0;
    int _deepestCompletedIndex = _indexOfDeepestCompletedLevel();
    _chunks = [];
    while (_levels.length > 0) {
      if (_levels.length > _levelsPerChunk) {
        _chunks.add(PathChunk(
          deepestCompletedIndex: _deepestCompletedIndex,
          indexOffset: _indexOffset,
          levels: _levels.sublist(0, _levelsPerChunk),
        ));
        _levels = _levels.sublist(_levelsPerChunk + 1);
        _indexOffset = _indexOffset + _levelsPerChunk;
      } else {
        _chunks.add(PathChunk(
          deepestCompletedIndex: _deepestCompletedIndex,
          indexOffset: _indexOffset,
          levels: _levels.sublist(0),
        ));
        _levels = [];
      }
    }
    return _chunks;
  }

  int _indexOfDeepestCompletedLevel() {
    int _index = 0;
    widget.levels.forEach((level) {
      if (level.isComplete | level.isMastered) {
        _index++;
      }
    });
    return _index;
  }
}

class PathChunk extends StatelessWidget {
  final double _chunkHeight = 600;
  final int deepestCompletedIndex;
  final int indexOffset;
  final int lineOfSight = 3;
  final List<Level> levels;

  final List<LevelHubLocation> _hubLocations = [
    LevelHubLocation(45, 4),
    LevelHubLocation(70, 3.5),
    LevelHubLocation(90, 6),
    LevelHubLocation(87, 19),
    LevelHubLocation(67, 20),
    LevelHubLocation(47, 18),
    LevelHubLocation(24, 18),
    LevelHubLocation(4, 26),
    LevelHubLocation(17, 37),
    LevelHubLocation(39, 35),
    LevelHubLocation(60, 33),
    LevelHubLocation(84, 34),
    LevelHubLocation(100, 44),
    LevelHubLocation(88, 52),
    LevelHubLocation(67, 49),
    LevelHubLocation(46, 47),
    LevelHubLocation(27, 53),
    LevelHubLocation(47, 60),
    LevelHubLocation(72, 58),
    LevelHubLocation(95, 62),
    LevelHubLocation(88, 75),
    LevelHubLocation(63, 73),
    LevelHubLocation(32, 68),
    LevelHubLocation(8, 71),
    LevelHubLocation(4, 85),
    LevelHubLocation(26, 88),
    LevelHubLocation(48, 86),
    LevelHubLocation(70, 83),
    LevelHubLocation(70, 83),
    LevelHubLocation(95, 83),
    LevelHubLocation(92, 96),
    LevelHubLocation(72, 95),
    LevelHubLocation(50, 95),
    LevelHubLocation(35, 100),
  ];

  PathChunk({
    @required this.deepestCompletedIndex,
    @required this.levels,
    @required this.indexOffset,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      height: _chunkHeight,
      child: Stack(
        children: [
          Container(
            height: double.infinity,
            width: double.infinity,
            color: Color.fromRGBO(94, 148, 184, 1),
          ),
          _buildLineLayer(), //Line layer
          _buildHubLayer(),
        ],
      ),
    );
  }

  Widget _buildHubLayer() {
    bool _shouldOpenThisLevel;
    bool _shouldShowThisLevel;
    List<Align> _hubAligns = [];
    for (var i = 0; i < levels.length; i++) {
      if ((i + this.indexOffset) > this.deepestCompletedIndex) {
        _shouldOpenThisLevel = false;
      } else {
        _shouldOpenThisLevel = true;
      }
      if ((i + this.indexOffset) >
          this.deepestCompletedIndex + this.lineOfSight) {
        _shouldShowThisLevel = false;
      } else {
        _shouldShowThisLevel = true;
      }
      _hubAligns.add(Align(
        alignment: _hubLocations[i].toAlignment(),
        child: LevelHub(
          index: i,
          level: levels[i],
          isOpen: _shouldOpenThisLevel,
          isVisible: _shouldShowThisLevel,
        ),
      ));
    }
    return Stack(
      children: _hubAligns,
    );
  }

  Widget _buildLineLayer() {
    return Container(
      decoration: BoxDecoration(
        image: DecorationImage(
          image: AssetImage("assets/images/line.png"),
          fit: BoxFit.fill,
        ),
      ),
      child: null /* add child content here */,
    );
  }
}

class LevelHubLocation {
  final double x;
  final double y;

  LevelHubLocation(this.x, this.y);

  Alignment toAlignment() {
    var alignX = (this.x / 50) - 1;
    var alignY = (this.y / 50) - 1;
    return Alignment(alignX, -alignY);
  }
}

class LevelHub extends StatelessWidget {
  final String iconPath;
  final bool isOpen;
  final bool isVisible;
  final Level level;
  final int lineOfSight = 3;
  final int index;

  final Color baseColor;
  final Color topColor;

  LevelHub({
    this.isOpen = false,
    this.isVisible = false,
    this.level,
    this.index,
  })  : topColor = _chooseTopColor(level, isOpen),
        baseColor = _chooseBaseColor(level, isOpen),
        iconPath = _chooseIconPath(level, isVisible);

  static Color _chooseBaseColor(Level _level, bool _isOpen) {
    Color _color = const Color.fromRGBO(138, 138, 138, 1);
    if (_isOpen) {
      _color = const Color.fromRGBO(127, 184, 223, 1);
    }
    if (_level.isComplete) {
      _color = const Color.fromRGBO(0, 173, 72, 1);
    }
    if (_level.isMastered) {
      _color = const Color.fromRGBO(251, 179, 0, 1);
    }
    return _color;
  }

  static Color _chooseTopColor(Level _level, bool _isOpen) {
    Color _color = const Color.fromRGBO(158, 158, 158, 1);
    if (_isOpen) {
      _color = const Color.fromRGBO(171, 202, 223, 1);
    }
    if (_level.isComplete) {
      _color = const Color.fromRGBO(0, 200, 83, 1);
    }
    if (_level.isMastered) {
      _color = const Color.fromRGBO(251, 199, 70, 1);
    }
    return _color;
  }

  static String _chooseIconPath(Level _level, bool _isVisible) {
    if (_isVisible) {
      return _level.iconPath;
    } else {
      return 'assets/levelicons/outofsight.png';
    }
  }

  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        if (this.isOpen) {
          UserBloc bloc = BlocProvider.of<UserBloc>(context);
          Navigator.of(context).push(PageRouteBuilder(
              transitionDuration: Duration(seconds: 0),
              opaque: false,
              pageBuilder: (BuildContext context, _, __) {
                return StartLevelDialog(
                  index: this.index,
                  level: this.level,
                );
              }));
        }
      },
      child: Container(
        height: 40,
        width: 40,
        foregroundDecoration: _foregroundDecoration(),
        decoration: BoxDecoration(
          color: topColor,
          boxShadow: [
            new BoxShadow(
              color: baseColor,
              offset: Offset(0, 5),
              spreadRadius: 0.5,
            ),
          ],
          borderRadius: BorderRadius.all(Radius.circular(20)),
          image: DecorationImage(
            image: AssetImage(iconPath),
            fit: BoxFit.fill,
          ),
        ),
      ),
    );
  }

  BoxDecoration _foregroundDecoration() {
    ///Likely not relevant
  }
}

In particular, note that on line 18 of CareerPage.dart I can access the BlocProvider.of(context).stateStream without throwing any errors. However, in line 23 of the build method of CareerPath.dart, when that runs I get the exception:

flutter: ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
flutter: The following assertion was thrown building CareerPath(dirty, state: _CareerPathState#66b7b):
flutter: Unable to find BLoC of type _BlocProvider.
flutter: Context provided: CareerPath(dirty, state: _CareerPathState#66b7b)
flutter:
flutter: When the exception was thrown, this was the stack:
flutter: #0 _BlocProvider.of (package:generic_bloc_provider/src/bloc_provider.dart:62:7)
flutter: #1 BlocProvider.of (package:generic_bloc_provider/src/bloc_provider.dart:20:21)
flutter: #2 _CareerPathState.build (package:dailymedicaltrivia2/ui/CareerPath.dart:23:34)
flutter: #3 StatefulElement.build (package:flutter/src/widgets/framework.dart:4012:27)
flutter: #4 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3924:15)
flutter: #5 Element.rebuild (package:flutter/src/widgets/framework.dart:3721:5)
flutter: #6 ComponentElement._firstBuild (package:flutter/src/widgets/framework.dart:3907:5)
flutter: #7 StatefulElement._firstBuild (package:flutter/src/widgets/framework.dart:4053:11)
flutter: #8 ComponentElement.mount (package:flutter/src/widgets/framework.dart:3902:5)
flutter: #9 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3084:14)
flutter: #10 MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5198:32)
flutter: #11 Element.inflateWidget (package:flutter/src/widgets/framework.dart:3084:14)
flutter: #12 Element.updateChild (package:flutter/src/widgets/framework.dart:2887:12)
flutter: #13 ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3935:16)
flutter: #14 Element.rebuild (package:flutter/src/widgets/framework.dart:3721:5)
flutter: #15 BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2340:33)
flutter: #16 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:700:20)
flutter: #17 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:285:5)
flutter: #18 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1016:15)
flutter: #19 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:958:9)
flutter: #20 _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:874:5)
flutter: #24 _invoke (dart:ui/hooks.dart:236:10)
flutter: #25 _drawFrame (dart:ui/hooks.dart:194:3)
flutter: (elided 3 frames from package dart:async)
flutter: ════════════════════════════════════════════════════════════════════════════════════════════════════

So how can BlocProvider.of<UserBloc>(context) be the source of the [StreamBuilder] on line 18 of CareerPage.dart, be valid on line 38, yet be gone by the time I try to build the CareerPath on line 45?

EDIT: I've realised it looks like GitHub does not supply line numbers so I've tagged the relevant lines with the form <Line18>, <Line38>, <Line45> if you want to find the relevant parts quickly.

Please let me know if you require any more information or a simpler example to help you understand the issue I'm having.

Many thanks

@joshcase
Copy link
Author

joshcase commented Jul 5, 2019

I've created a public repository of my whole project, with the issue, here: https://github.com/JoshCase/BlocProviderIssue

@joshcase
Copy link
Author

joshcase commented Jul 7, 2019

Update: I've managed to resolve this issue myself.

The problem was that in main.dart, I was importing: import 'package:dailymedicaltrivia2/bloc/User/UserBloc.dart';
and in CareerPath.dart I was importing: import 'package:dailymedicaltrivia2/bloc/user/UserBloc.dart';.
Note that they have different capitalisation. This led to a toxic and difficult to debug error in which main.dart was creating a "User/UserBloc.dart", and the [BLocProvider] in CareerPath.dart was looking for a "user/UserBloc.dart". Problem was resolved by changing the capitalisation to be consistent across all files. Leaving this here so that hopefully someone else is saved 10+ hours from this problem.

@joshcase joshcase closed this as completed Jul 7, 2019
@robertohuertasm
Copy link
Owner

Hi @joshcase, happy to see that you've been able to solve it and sorry to not have been able to help. I was waiting to have more time to take a look at your issue but you got ahead 😉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants