brianegan / flutter_architecture_samples

TodoMVC for Flutter
http://fluttersamples.com/
BSD 3-Clause "New" or "Revised" License
8.75k stars 1.71k forks source link

Todo Redux Performance #56

Closed aboo closed 6 years ago

aboo commented 6 years ago

In the todo app using Redux every time one of the todo items changes all the items get rebuilt.

How do I know that? I added a print in todo_item.dart:27.

That means if there are 100 items in the list and one of them gets completed all the others will rebuild their widget. This is not the behaviour using StatefullWidgets.

To me (and I'm new to Flutter world) this feels like a major performance issue.

Questions:

  1. Am I correct?
  2. Is there a way to fix this?

For example is there a way to build a widget in answer to a certain action. Or ignore rebuild if certain action is dispatched?

brianegan commented 6 years ago

Hey, thanks for writing in Aboo...

Hrm, that's interesting. I'm actually seeing the same behavior from both Redux and the Vanilla Examples: When the list changes, Flutter will rebuild the Visible Todo Items.

From my vantage point, this is expected behavior. While I'd have to measure it on low-end devices, my guess is this wouldn't be massive performance issue. Why? As long as you're using the ListView.builder constructor, Flutter will only rebuild the visible Widgets that are on screen, not the entire list of Widgets. Remember: Rebuilding the Visible Widgets in the List is generally very fast (you're essentially instantiating 10-15 new objects), and Flutter will only repaint what's changed. You can verify Flutter is only rebuilding specific Todos by enabling repaint rainbows in Dev Mode.

Please let me know if that helps :)

aboo commented 6 years ago

Thanks for the reply @brianegan

I understand your argument and it kind of make sense however in a real world scenario there might be many widgets (not even visible) with some calculations and logic to figure out something complex. Not being able to exclude that part from certain actions or subscribe to just certain actions regardless of it being rendered or not feels a bit loose on the performance and could actually have effects on the app/battery/ux.

In another sample that I made even the widgets on pages two levels app (after drilling in two levels) are still getting called on actions and although there is nothing to render but you can literally step through all the build logic.

I believe some like this would help a lot but I don't know if it's possible to be done at all or not:

WARNING!!! pseudo code only!

...
 return StoreConnector<AppState, _ViewModel>(
    converter: (store) => _ViewModel(store),
    onlyeReceive: [Action1, Action2]
    builder: (store, vm, action) {
      return Container( ...

or

...
 return StoreConnector<AppState, _ViewModel>(
    converter: (store) => _ViewModel(store),
    ignore: [Action1, Action2]
    builder: (store, vm, action) {
      return Container(...
brianegan commented 6 years ago

Hey Aboo, you're definitely correct -- using Redux can cause rebuilds in the background if the State changes. This also happens if you're using StatefulWidgets to share state across screens.

Overall, I think this is actually one drawback to using the Redux / StatefulWidget Approach. Since everything is contained in a Single Store / State class, all StoreConnectors will be informed when any part of the State changes.

To mitigate that performance issue with flutter_redux, you can use the distinct property on StoreConnectors. It's similar to your idea, and it will only trigger a Widget rebuild if the ViewModel is different than the previous ViewModel.

I think your idea of only listening to certain actions is also interesting.

Another approach: Use a technique like the Bloc pattern. It generally splits up responsibilities into individual Blocs, and you listen to individual Streams of data. That way, only the StreamBuilder widgets that listen to a particular Stream are rebuilt.

I've actually been favoring that approach more myself recently because of this exact problem, and because I think it requires less code overall.

That said, I've run this example (which uses the distinct trick) on older phones and haven't noticed a massive performance bottleneck from Redux. The battery life question is always an interesting one, however.

If you've seen this particular choice cause specific performance issues, I'd be happy to take a look! It's a bit hard to optimize without seeing a concrete performance issue and being able measure the impact this part of code is having on that.

aboo commented 6 years ago

Thanks Brian. The fact that you confirmed the issue or drawback is enough for me to stop searching more. I've used distinct as well but in my case the result is the same. Perhaps I need to distinct parents as well.

I will close this issue now.

brianegan commented 6 years ago

For sure, thanks for writing in! Good catch & Discussion of the pros / cons of these architecture choices :)