Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly set value of DropdownButton using Bloc in Flutter?

Tags:

flutter

dart

bloc

I'm new in Bloc programming pattern and I'm having an issue when using it in with Dropdown That's in my bloc class:

final _dropDown = BehaviorSubject<String>();
Stream<String> get dropDownStream => _dropDown.stream;
Sink<String> get dropDownSink => _dropDown.sink;
final _dropdownValues = BehaviorSubject<List<String>>(seedValue: [
    'One',
    'Two',
    'Three',
    'Four',
  ].toList());
  Stream<List<String>> get dropdownValuesStream => _dropdownValues.stream;

In my widget page I added the following dropdown widget so that everything is handled by the Bloc class:

StreamBuilder<List<String>>(
                      stream: _exampleBloc.dropdownValuesStream,
                      builder: (BuildContext contextValues, AsyncSnapshot snapshotValues) {
                        return StreamBuilder<String>(
                            stream: _exampleBloc.dropDownStream,
                            builder: (BuildContext context, AsyncSnapshot snapshot) {
                              return InputDecorator(
                                decoration: InputDecoration(
                                  icon: const Icon(Icons.color_lens),
                                  labelText: 'DropDown',
                                ),
                                child: DropdownButtonHideUnderline(
                                  child: DropdownButton<String>(
                                    value: snapshot.data,
                                    onChanged: (String newValue) => _exampleBloc.dropDownSink.add(newValue),
                                    items: snapshotValues.data != null ? snapshotValues.data.map<DropdownMenuItem<String>>((String value) {
                                      return DropdownMenuItem<String>(
                                        value: value,
                                        child: Text(value),
                                      );
                                    }).toList() : <String>[''].map<DropdownMenuItem<String>>((String value) {
                                      return DropdownMenuItem<String>(
                                        value: value,
                                        child: Text(value),
                                      );
                                    }).toList(),
                                  ),
                                ),
                              );
                            },
                          );
                      },
                    ),

But doing like that, I get this error when setting the value (value: snapshot.data) of the DropdownButton:

I/flutter ( 5565): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter ( 5565): The following assertion was thrown building StreamBuilder<String>(dirty, state:
I/flutter ( 5565): _StreamBuilderBaseState<String, AsyncSnapshot<String>>#70482):
I/flutter ( 5565): 'package:flutter/src/material/dropdown.dart': Failed assertion: line 514 pos 15: 'items == null ||
I/flutter ( 5565): value == null || items.where((DropdownMenuItem<T> item) => item.value == value).length == 1': is not
I/flutter ( 5565): true.
I/flutter ( 5565):
I/flutter ( 5565): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter ( 5565): more information in this error message to help you determine and fix the underlying cause.
I/flutter ( 5565): In either case, please report this assertion by filing a bug on GitHub:
I/flutter ( 5565):   https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter ( 5565):
I/flutter ( 5565): When the exception was thrown, this was the stack:
I/flutter ( 5565): #2      new DropdownButton (package:flutter/src/material/dropdown.dart:514:15)
I/flutter ( 5565): #3      _ExamplePageState.build.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:financeiro_mobile/src/ui/exemple/example_page.dart:129:42)
I/flutter ( 5565): #4      StreamBuilder.build (package:flutter/src/widgets/async.dart:423:74)
I/flutter ( 5565): #5      _StreamBuilderBaseState.build (package:flutter/src/widgets/async.dart:125:48)
I/flutter ( 5565): #6      StatefulElement.build (package:flutter/src/widgets/framework.dart:3809:27)
I/flutter ( 5565): #7      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3721:15)
I/flutter ( 5565): #8      Element.rebuild (package:flutter/src/widgets/framework.dart:3547:5)
I/flutter ( 5565): #9      StatefulElement.update (package:flutter/src/widgets/framework.dart:3878:5)
I/flutter ( 5565): #10     Element.updateChild (package:flutter/src/widgets/framework.dart:2742:15)
I/flutter ( 5565): #11     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3732:16)
I/flutter ( 5565): #12     Element.rebuild (package:flutter/src/widgets/framework.dart:3547:5)
I/flutter ( 5565): #13     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2286:33)
I/flutter ( 5565): #14     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:676:20)
I/flutter ( 5565): #15     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:219:5)
I/flutter ( 5565): #16     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
I/flutter ( 5565): #17     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
I/flutter ( 5565): #18     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842:5)
I/flutter ( 5565): #19     _invoke (dart:ui/hooks.dart:154:13)
I/flutter ( 5565): #20     _drawFrame (dart:ui/hooks.dart:143:3)
I/flutter ( 5565): (elided 2 frames from class _AssertionError)

I tried a lot of ideas like checking if snapshotValues.data is not null when setting. I know that the value has to be something from the list or null. But no logic that I put there makes this error go away. If I set the value to null, it works, but then the selected value doesn't show. Am I doing this wrong? Is there a better way that works? How can I solve this issue? Thanks!

like image 893
GustavoAndrade Avatar asked Dec 19 '18 14:12

GustavoAndrade


2 Answers

I solved it using two stream in the bloc, one for the list of elemtns and the other for the value. So in ther build, you need two chained StreamBuilders and when u got both snapshots with data, you load the build. Like that:

Widget _holdingDropDown() {
return StreamBuilder(
    stream: bloc.holding,
    builder: (BuildContext context, AsyncSnapshot<Holding> snapshot) {
      return Container(
        child: Center(
          child: snapshot.hasData
              ? StreamBuilder(
                  stream: bloc.obsHoldingList,
                  builder: (BuildContext context,
                      AsyncSnapshot<List<Holding>> holdingListSnapshot) {
                    return holdingListSnapshot.hasData ?
                    DropdownButton<Holding>(
                      value: snapshot.data,
                      items: _listDropDownHoldings,
                      onChanged: (Holding h) {
                        _changeDropDownItemHolding(h);
                      },
                    ): CircularProgressIndicator();
                  },
                )
              : CircularProgressIndicator(),
        ),
      );
    });
}

I use the circular progress indicator to return if I don't have the snapshot with data. I hope I have been helpful.

like image 77
Rostan Avatar answered Oct 21 '22 18:10

Rostan


That's because you are using a StreamBuilder, so at the first time your snapshot is empty, you have to do a validation :

        return snapshot.hasData ? 
             InputDecorator(
                                        decoration: InputDecoration(
                                          icon: const Icon(Icons.color_lens),
                                          labelText: 'DropDown',
                                        ),
                                        child: DropdownButtonHideUnderline(
                                          child: DropdownButton<String>(
                                            value: snapshot.data,
                                            onChanged: (String newValue) => _exampleBloc.dropDownSink.add(newValue),
                                            items: snapshotValues.data != null ? snapshotValues.data.map<DropdownMenuItem<String>>((String value) {
                                              return DropdownMenuItem<String>(
                                                value: value,
                                                child: Text(value),
                                              );
                                            }).toList() : SizedBox(height: 0.0)
                                        ),
                                      ) : SizedBox(height: 0.0);

Display an empty widget SizedBox(height: 0.0) or a CircleProgressIndicator

like image 25
diegoveloper Avatar answered Oct 21 '22 17:10

diegoveloper