Как управлять состоянием формы с помощью шаблона BLoC? - PullRequest
0 голосов
/ 24 апреля 2019

В настоящее время я работаю над сторонним проектом, чтобы узнать о шаблонах Rx и BLoC.

Я бы хотел управлять состоянием формы без использования setState().

У меня уже естьBLoC, который управляет моими «событиями», которые хранятся в базе данных SQLite и добавляются после проверки этой формы.

Нужно ли создавать BLoC специально для этой части пользовательского интерфейса и как?Можно ли сохранить такой код?Должен ли я изменить свой фактический BLoC?

Вы можете найти мой текущий код здесь:

class _EventsAddEditScreenState extends State<EventsAddEditScreen> {
  bool hasDescription = false;
  bool hasLocation = false;  
  bool hasChecklist = false;

  DateTime eventDate;
  TextEditingController eventNameController =  new TextEditingController();
  TextEditingController descriptionController =  new TextEditingController();

  @override
  Widget build(BuildContext context) {
    final eventBloc = BlocProvider.of<EventsBloc>(context);
    return BlocBuilder(
      bloc: eventBloc,
      builder: (BuildContext context, EventsState state) {
        return Scaffold(
          body: Stack(
            children: <Widget>[
              Column(children: <Widget>[
                Expanded(
                    child: ListView(
                  shrinkWrap: true,
                  children: <Widget>[
                    _buildEventImage(context),
                    hasDescription ? _buildDescriptionSection(context) : _buildAddSection('description'),
                    _buildAddSection('location'),
                    _buildAddSection('checklist'),
                    //_buildDescriptionSection(context),
                  ],
                ))
              ]),
              new Positioned(
                //Place it at the top, and not use the entire screen
                top: 0.0,
                left: 0.0,
                right: 0.0,
                child: AppBar(
                  actions: <Widget>[
                    IconButton(icon: Icon(Icons.check), onPressed: () async{
                      if(this._checkAllField()){
                        String description = hasDescription ? this.descriptionController.text : null;
                        await eventBloc.dispatch(AddEvent(Event(this.eventNameController.text, this.eventDate,"balbla", description: description)));
                        print('Saving ${this.eventDate} ${eventNameController.text}');
                      }
                    },)
                  ],
                  backgroundColor: Colors.transparent, //No more green
                  elevation: 0.0, //Shadow gone
                ),
              ),
            ],
          ),
        );
      },
    );
  }

  Widget _buildAddSection(String sectionName) {
    TextStyle textStyle = TextStyle(
        color: Colors.black87, fontSize: 18.0, fontWeight: FontWeight.w700);
    return Container(
      alignment: Alignment.topLeft,
      padding:
          EdgeInsets.only(top: 20.0, left: 40.0, right: 40.0, bottom: 20.0),
      child: FlatButton(
        onPressed: () {
          switch(sectionName){
            case('description'):{
              this.setState((){hasDescription = true;});
            }
            break;
            case('checklist'):{
              this.setState((){hasChecklist = true;});
            }
            break;
            case('location'):{
              this.setState((){hasLocation=true;});
            }
            break;
            default:{

            }
            break;
          }
        },
        padding: EdgeInsets.only(top: 0.0, left: 0.0),
        child: Text(
          '+ Add $sectionName',
          style: textStyle,
        ),
      ),
    );
  }

1 Ответ

2 голосов
/ 25 апреля 2019

Давайте решим это шаг за шагом.

Ваш первый вопрос: Нужно ли создавать необходимый BLoC специально для этой части пользовательского интерфейса?

Ну, это родственник ваших потребностей и вашего приложения. При необходимости вы можете иметь BLoC для каждого экрана, но вы также можете иметь один BLoC для 2 или 3 виджетов, в этом нет никаких правил. Если вы считаете, что в этом случае хорошим подходом является реализация другого BLoC для вашего экрана, поскольку код будет более читабельным, упорядоченным и масштабируемым, вы можете сделать это или, если вы считаете, что лучше сделать только один блок со всеми внутри, вы свободны к этому тоже.

Ваш второй вопрос: и как?

В вашем коде я вижу только setState вызовы в _buildAddSection, поэтому давайте изменим это написание нового класса BLoc и будем обрабатывать изменения состояния с помощью потоков RxDart .

class LittleBloc {
  // Note that all stream already start with an initial value. In this case, false.

  final BehaviorSubject<bool> _descriptionSubject = BehaviorSubject.seeded(false);
  Observable<bool> get hasDescription => _descriptionSubject.stream;

  final BehaviorSubject<bool> _checklistSubject = BehaviorSubject.seeded(false);
  Observable<bool> get hasChecklist => _checklistSubject.stream;

  final BehaviorSubject<bool> _locationSubject = BehaviorSubject.seeded(false);
  Observable<bool> get hasLocation => _locationSubject.stream;

  void changeDescription(final bool status) => _descriptionSubject.sink.add(status);
  void changeChecklist(final bool status) => _checklistSubject.sink.add(status);
  void changeLocation(final bool status) => _locationSubject.sink.add(status);

  dispose(){
    _descriptionSubject?.close();
    _locationSubject?.close();
    _checklistSubject?.close();
  }
}

Теперь я буду использовать этот BLoc в вашем виджете. Ниже приведен полный код метода build с изменениями. В основном мы будем использовать StreamBuilder для создания виджетов в дереве виджетов.

 final LittleBloc bloc = LittleBloc(); // Our instance of bloc 
 @override
  Widget build(BuildContext context) {
    final eventBloc = BlocProvider.of<EventsBloc>(context);
    return BlocBuilder(
      bloc: eventBloc,
      builder: (BuildContext context, EventsState state) {
        return Scaffold(
          body: Stack(
            children: <Widget>[
              Column(children: <Widget>[
                Expanded(
                    child: ListView(
                      shrinkWrap: true,
                      children: <Widget>[
                        _buildEventImage(context),
                        StreamBuilder<bool>(
                          stream: bloc.hasDescription,
                          builder: (context, snapshot){
                            hasDescription = snapshot.data; // if you want hold the value
                            if (snapshot.data)
                              return _buildDescriptionSection(context);//we got description true

                            return buildAddSection('description'); // we have description false
                          }
                        ),
                        _buildAddSection('location'),
                        _buildAddSection('checklist'),
                        //_buildDescriptionSection(context),
                      ],
                    ),
                ),
              ]
              ),
              new Positioned(
                //Place it at the top, and not use the entire screen
                top: 0.0,
                left: 0.0,
                right: 0.0,
                child: AppBar(
                  actions: <Widget>[
                    IconButton(icon: Icon(Icons.check), 
                      onPressed: () async{
                        if(this._checkAllField()){
                          String description = hasDescription ? this.descriptionController.text : null;
                          await eventBloc.dispatch(AddEvent(Event(this.eventNameController.text, this.eventDate,"balbla", description: description)));
                          print('Saving ${this.eventDate} ${eventNameController.text}');
                        }
                      },
                    ),
                  ],
                  backgroundColor: Colors.transparent, //No more green
                  elevation: 0.0, //Shadow gone
                ),
              ),
            ],
          ),
        );
      },
    );
  }

И не более setState звонков в вашем _buildAddSection. Просто нужно изменить switch заявление. Вызовы changes... обновят потоки в классе BLoc, и это сделает перестройку виджета, который прослушивает поток.

switch(sectionName){
  case('description'):
    bloc.changeDescription(true);
    break;

  case('checklist'):
    bloc.changeChecklist(true);
    break;

  case('location'):
    bloc.changeLocation(true);
    break;

  default:
    // you better do something here!
    break;
}

И не забывайте вызывать bloc.dispose() внутри WidgetState dispose метод.

...