Do I need a StatefulWidget if my app is using bloc?

6,181

Solution 1

When using StreamBuilder you are in fact using a StatefulWidget which listen to that Stream. The only difference is that you don't write setState yourself.

Another common use-case is for animations. If you want transitions such as fade/translate/whatever; you'll have to use AnimationController. Which you will store inside a custom StatefulWidget

Solution 2

does it behoove me to use a StatefulWidget, where I'm using the bloc method anyway? I guess more specifically, why would I want to complicate my app using streams and states, when I could just use streams, wrap what I need to in a provider, wrap some widgets in a streamBuilder, and call it a day?

The answer to your questions depends on what your goals are.

A StatefulWidget does not scale to larger applications. The BLOC pattern does.

Why is StatefulWidget not good for larger applications?

Communicating information from one screen to a sibling screen tends to be challenging, meaning you have to write a lot of code to transfer data from one screen to another. It is possible, but it tends to be a pain and that's what the BLOC pattern solves.

It makes it easy to share information between multiple widgets inside our application.

BLOC stands for Business LOgic Component and its idea is to house all the data or state inside the application inside one area. It's outside the rest of the application and makes it easy to access.

That's different from a StatefulWidget, because with a BLOC all the data can live within one class outside the component hierarchy. So the state is being centralized to some outside object.

So with the BLOC pattern you do need a solid understanding of streams and you mentioned the StreamBuilder Widget which is were flutter and streams really come together.

The StreamBuilder takes a stream and a builder function and anytime that StreamBuilder sees a new piece of data it will call the builder function and re render itself on our mobile device, so it would look something like this:

Widget emailField() {
    return StreamBuilder(
        stream: bloc.email,
        builder: (context, snapshot) {
          return TextField(
            keyboardType: TextInputType.emailAddress,
            decoration: InputDecoration(
              hintText: '[email protected]',
              labelText: 'Email Address',
            ),
          );
        });
  }

The above approach being a single global instance which works fine for small application.

The provider you mentioned is a class that extends the inherited widget base class.

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

class Provider extends InheritedWidget {
  final bloc = Bloc();

  bool updateShouldNotify(_) => true;

  static Bloc of(BuildContext, context) {
    return (context.inheritFromWidgetOfExactType(Provider) as Provider).bloc;
  }
}

Solution 3

Let me share my insights after working with 2 years with Flutter, Bloc and StatefulWidgets.

Whenever you find yourself in a situation that you want to make two StatefulWidget communicate, then think twice if Bloc is the answer! I have gone too far with bloc-everything approach as was suggested in their docs and expected that bloc would help me achieve better modularity in code. I used BlocListeners to utilize listener: argument and call setState() in it... Then I tried with BlocBuilders. I have always reached the dead end and got many bugs while integrating StatefulWidget closely with Bloc. Why, you ask? Just to achieve this cleany, shiny modularity to have widgets in separate files and make them reusable and all that clean-code jazz.

However, that's not the case for StatefulWidget when it comes to practice.

Let me give you my mental model while approaching designing the widget. As for the example I will work on the following UI component - the vertical Column that contains two ListItems that every one composes of two Switches, where the top switch toggles the bottom one. When a parent preference switch is toggled off, then it also disables the child one.

Sample UI:

The UI

Video with such requirement to be fulfilled:

desired case

How to code such UI? The first idea is to create:

  • ParentPreferenceWidget.dart
  • ChildPreferenceWidget.dart
  • TitleSectionWidget.dart

Column would look like:

Column(
  children: [
    ParentPreferenceWidget(...),
    ChildPreferenceWidget(...),
    TitleSectionWidget(...),
    ParentPreferenceWidget(...),
    ChildPreferenceWidget(...),
    TitleSectionWidget(...),
  ]
)

Ok, so the UI was coded. And how to make those Swtiches communicate? (ParentPreferenceWidget with ChildPreferenceWidget one).

Well, the problem arises, as ParentPreferenceWidget and ChildPreferenceWidget will have a have Switch widget.

A Switch widget needs a value: parameter and is capable of running animation.

In every tutorial it is required to call setState for such Widget which aggregates Switch. So such widgets need to extend StatefulWidget.

A note for StatefulWidgets - their associated State objects have longer lifecylce than StatelessWidgets. And it made because of performance reasons. Resource here.

So State objects are not destroyed during a widget tree rebuild phase, while StatelessWidgets are destroyed and recreated. This enables running the animation smoothly for the first case.

So how to use bloc then?

This could come to your mind (pseudo-code):

Column(
  children: [
    BlocProvider<...>(
      create: ...
      child: Column(
        children: [
           ParentPreferenceWidget(...), // will fire bloc events
           BlocBuilder<...>(
             builder: (context, state) => ChildPreferenceWidget(state, ...) // this will rebuild
           )  
        ]
      )
    ),
    TitleSectionWidget(...),
    BlocProvider<...>(
      create: ...
      child: Column(
        children: [
           ParentPreferenceWidget(...), // will fire bloc events
           BlocBuilder<...>(
             builder: (context, state) => ChildPreferenceWidget(state, ...) // this will rebuild
           )  
        ]
      )
    ),
    TitleSectionWidget(...),
  ]
)

Beware! Whenever event is fired, a new instance of PreferenceWidget is created. We've just said before that this is a StatefulWidget, hence no performance gain at all.

And also there will be bloc events multiplication (either you create separate bloc or reuse one). Either the new states are added and code complexity will grow.

And what if you want to move bloc higher? Like it would become a master one, so two ParentPreferenceWidget can communicate.

Then what you need to do, is to add more data to bloc events like bool wasFiredFromBlocA. This becomes a bloc hell.

Unfortunately, the only reasonable solution I came up with was to return to the basics and write the whole ParentPreferenceWidget and ChildPreferenceWidget functionality inside a single Stateful widget.

This is motivated by the problems mentioned about while using single or multiple Blocs and making them communicate the state to child widgets.

So for my newly created Parent-Child widget, a State object contains two fields, like this:

bool _switchParentState;
bool _switchChildState;

And voila - this way state objects live as long as expected and handle the state correctly.

As for the bloc - you can manage such internal state with the help of BlocListener if you really want to listen to some parent bloc from the 'outside'.

This could look like this:

return BlocListener(
  listener: (context, state) {
    setState(() {
        // use state to set state
    });
  },
  child: ... // build the widget tree normally.
)

A note - just remember to use ValueKey or some other Key for any StatefulWidget that is duplicated inside the view hierarchy. As they need to be differentiated by the Flutter framework in case of State objects.

And that's how I perceive this after struggling a lot. The bloc is a great solution for simple cases. But when it comes to framework principles and some complex state mutations, it is better to stick to the basics.

Solution 4

You can use a StatelessWidget or a StatefulWidget and everything will work fine but, in those instances where I have needed to initialise state in my bloc, I have used a StatefulWidget and put my initialisation logic inside the initState() override.

(please correct me kindly if this isn't best practice, I'm new to bloc, flutter and streams!)

Share:
6,181
Academiphile
Author by

Academiphile

I am an Electrical Engineering student, with a love for learning. I often browse the stack exchange network in my free time. I love Linux, and I'm half-decent at back-end programming. In my free time, I write.

Updated on December 06, 2022

Comments

  • Academiphile
    Academiphile over 1 year

    I'm missing something.

    I recently watched the talk here, where the flutter devs are going through using the bloc development method with reactivex in Dart. If I'm using these streams and streamBuilders to manage data flowing through my app, and rebuild appropriately, does it behoove me to use a StatefulWidget, where I'm using the bloc method anyway? I guess more specifically, why would I want to complicate my app using streams and states, when I could just use streams, wrap what I need to in a provider, wrap some widgets in a streamBuilder, and call it a day?

  • boformer
    boformer almost 6 years
    Also for text input fields (TextInputController)
  • TBG
    TBG over 2 years
    Now that you've got some experience (I assume), would you still do that ?
  • Adam
    Adam about 2 years
    A StatefulWidget does not scale to larger applications. The BLOC pattern does What utter opinionated nonsense. Not that I disagree with BLoC pattern, but the statement that "StatefulWidget" won't scale is silly and wrong.
  • Mário Meyrelles
    Mário Meyrelles about 2 years
    Thanks for such a detailed explanation. I will consider this idea :)