felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

Flutter BLoC Functionallity of = adding item to list without fully rebuild list, changing a value of class instance property #1965

Closed Lix-ai closed 3 years ago

Lix-ai commented 3 years ago

Maybe my question is a nooby one, but i really try to understand how to implement the following operations:

How to add item for a list without the need of rebuilding all list. How to update a class instance property value. Does BLoCProvider used only once in a class? ===> I'm asking those question because i see that BLoC working with 'Equatable' and that in a the state class there is always a need to bring default value to the state we provide to the view class.

Another Question (what change the view and operate the state): Does i need to bring always a list in order for example to update an item from the list or only changing the state+emit(in cubit) will change the state of the application, maybe it realted to BLocListener?

Thanks!

Lix-ai commented 3 years ago

Like using for example audioplayers package and run several players.

felangel commented 3 years ago

Hi @Lix-ai 👋 Thanks for opening an issue!

How to add item for a list without the need of rebuilding all list.

Generally I wouldn't worry about this unless you are experiencing performance issues. Widgets like ListView.builder are already optimized to only render the visible portions of the list.

How to update a class instance property value. All state should be immutable so the way to change state is to create a new instance of the state. I recommend using copyWith as illustrated in https://github.com/felangel/bloc/blob/32dbf13baff0cbd10bf47e5052ccf25903166d19/examples/flutter_firebase_login/lib/login/cubit/login_state.dart#L17.

Does BLoCProvider used only once in a class?

You should generally use a BlocProvider once for each bloc instance when you want to create and provide the bloc instance to a part of the widget tree.

Does i need to bring always a list in order for example to update an item from the list or only changing the state+emit(in cubit) will change the state of the application, maybe it realted to BLocListener?

As I previously mentioned, the way to update a state or part of a state is to emit a new instance of the state. You can use a copyWith method to create a new instance with some modifications. At the end of the day, it's totally up to you how to model the state and it will vary based on the feature or use-case.

Hope that helps! 👍

Lix-ai commented 3 years ago

Thanks for the detailed answer Felix, great honor to hear from the creator of the most trended flutter architecture (i spend a month for looking of the correct architecture approach) =],

I would like to summarize/asked a questions regarding your answer:

  1. BlocListener is here only for updating/emitting new state but without the need of rebuilding a widgets right?
  2. BlocProvider is only for 1 BLoC instance in a widget tree ==> For the whole, 1 view class.
  3. Emitting/Creating new instance of a state(using copyWith) probably will made a difficulties when for example an item in a list is including widgets, +/ audioplayers and i want to preserve the state of the item's.
  4. Regarding number '3', I shall use Stateful widget class in order to preserver state, like when using an audioplayer for example and others stuff on a item in a list.

===> It seems that any changing the application is including rebuilding all widgets (from scratch w/o property value changing) without preserving state of the application (state-machine attitude).

Thanks in advance and thanks for all :)!!!

narcodico commented 3 years ago

Hi @Lix-ai 👋

  1. BlocListener is used to react to state changes in the bloc and it's useful for side effects like navigation, showing dialogs, etc. It is not directly used to update/emit new states but you can accomplish that by adding an event to the bloc from the listener. Read more about it here;
  2. BlocProvider can be provided pretty much anywhere in the widget tree and any widget that is a descendant will have access to the provided instance of the bloc. If by view you're referring to a route then BlocProvider can be used to provide a bloc to the whole route, but it can also be used to scope a bloc to multiple routes or even provide the bloc to all routes if placed above the MaterialApp. Read more about it here;
  3. That scenario should never occur simply because it is not recommended to keep UI objects as part of the state class of a bloc. Only keep the non-UI data/parameters on the state class and use them to build the UI widgets as needed;
  4. You pretty much never need a StatefulWidget when using bloc, you can build UI with StatelessWidgets only. But if you need to handle some additional UI logic like animations then there's no inconvenience in just converting your widget to a stateful one, bloc specific widgets will behave exactly the same. The bottom line here is that you don't need a StatefulWidget to preserve state because state is already preserved by bloc being cached by BlocProvider between rebuilds.

Hope that helps! ✌

Lix-ai commented 3 years ago

Hi and thanks @narcodico 👋 All clear except question number 3.

Still don't understand if a have audioplayer in an item which part of a list (ListView.Builder, for example) and I want to use Bloc? Can I achieve it? You said "Only keep the non-UI data/parameters on the state class", but again if i want to add new item which contains an audioplayer, what about other audioplayers at other item?

I.E, I need to add item to the list only via the 'Cubit'. In "https://github.com/felangel/bloc/tree/master/examples/flutter_complex_list/lib/list" example, you can see that the 'State' class send the list data to the view (UI class). Confused.

Thanks =]

narcodico commented 3 years ago

Starting from the example you linked let's assume the Item class holds the values needed to build an audio player.

class Item extends Equatable {
  const Item({
    @required this.url,
  });

  final String url;

  Item copyWith({
    String url,
  }) {
    return Item(
      url: url ?? this.url,
    );
  }

  @override
  List<Object> get props => [url];
}

Then you can use the state of the cubit to build a list of audio players.

ListView.builder(
            itemBuilder: (BuildContext context, int index) {
              return AudioPlayer(
                item: items[index],
              );
            },
            itemCount: items.length,
          );

And your AudioPlayer is a custom widget something like:

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

  final Item item;

  @override
  Widget build(BuildContext context) {
    return SomeAudioPlayerWidgetFromSomePackage(
      url: item.url,
    );
  }
}

So the difference is that you're only storing in the state the data needed to build the UI but not the UI itself, e.g.: url string not AudioPlayer widget.

Lix-ai commented 3 years ago

Thanks and sorry for late replay, So with that I can achieve what i desired to and keep the state of the items as they are right (after adding/deleting items)?

Thanks =]

felangel commented 3 years ago

No worries and yup that's correct 👍

gbaccetta commented 3 years ago

Hello, I'm not sure it is correct to write into a closed issue, but since it is related I'll give a shot here first. I recently stumbled on the need for this and I wanted to know your opinion on this. I have a list with some items fetching an url preview when the text contains an url. However the list being rebuild each time, the animations that expand the item list once the data is ready trigger every time an item is added and therefore the list rebuilt. Can I avoid this behavior using BloC ?

Gene-Dana commented 3 years ago

Hello, I'm not sure it is correct to write into a closed issue, but since it is related I'll give a shot here first. I recently stumbled on the need for this and I wanted to know your opinion on this. I have a list with some items fetching an url preview when the text contains an url. However the list being rebuild each time, the animations that expand the item list once the data is ready trigger every time an item is added and therefore the list rebuilt. Can I avoid this behavior using BloC ?

There are many paths to sorting out this problem, although following the suggested BLoC approach may greatly help you understand what the problem is and how to best solve

gbaccetta commented 3 years ago

Hey, thank you for your reply. Actually the problem is not related to bloc at all. In fact ListView.builder will always rebuild all of its children, independently to setting a key for them. The solution I found is to use a ListView.custom with a SliverChildBuilderDelegate. Then assigning a key to its children and retrieving the key from the index works as expected and child is not rebuilt. Not that childCount and findChildIndexCallback are parameters of the sliver and not the list itself. Here is an example:

ListView.custom(
            physics: const AlwaysScrollableScrollPhysics(),
            childrenDelegate: SliverChildBuilderDelegate(
              (context, index) {
                return MessageWidget(
                        key: ValueKey('m-${state.messages[index].messageId}'
                           ),
                        message: state.messages[index],
                      );
              },
              childCount: state.messages.length,
              findChildIndexCallback: (key) {
                final ValueKey<String> valueKey = key as ValueKey<String>;
                final index = state.messages
                    .indexWhere((m) => 'm-${m.messageId}' == valueKey.value);
                return index;
              },
            ),
          ),
kevin4dhd commented 2 years ago

Oye, gracias por tu respuesta. En realidad, el problema no está relacionado con el bloque en absoluto. De hecho ListView.builder, siempre reconstruirá a todos sus hijos, independientemente de establecer una clave para ellos. La solución que encontré es usar a ListView.customcon a SliverChildBuilderDelegate. Luego, asignar una clave a sus elementos secundarios y recuperar la clave del índice funciona como se esperaba y el elemento secundario no se reconstruye. No eso childCounty findChildIndexCallbackson parámetros de la astilla y no de la lista en sí. Aquí hay un ejemplo:

ListView.custom(
            physics: const AlwaysScrollableScrollPhysics(),
            childrenDelegate: SliverChildBuilderDelegate(
              (context, index) {
                return MessageWidget(
                        key: ValueKey('m-${state.messages[index].messageId}'
                           ),
                        message: state.messages[index],
                      );
              },
              childCount: state.messages.length,
              findChildIndexCallback: (key) {
                final ValueKey<String> valueKey = key as ValueKey<String>;
                final index = state.messages
                    .indexWhere((m) => 'm-${m.messageId}' == valueKey.value);
                return index;
              },
            ),
          ),

Could you show a hello world? I try to explain your code but always when I add a new element to the list it repaints all the elements of the list

Shortyyy commented 1 year ago

well it was a long day so i just describe what i want as good as i can. its actually simple. i have an infinite List, no Stream, i'm fetching data with offset and limit.

if i tap on the item in List and edit the title, it should update only this one item in List after saving it. (or add a new one visible to list not somewhere hidden in db) so should i use Bloc for List overview and wrap each List | Item in another Bloc? is it to much? will it even work

it bothers me for quite a while now, i always succesfully avoided it. What would be the better approach with flutter_bloc to solve it.