简体   繁体   中英

How To Get My Deeply Nested Data To Update Properly When Using Flutter_bloc 8.0.0+ And Equatable

Introduction: I have been working on this problem for a while. The problem involves updating the state of deeply nested data with the Bloc 8.0.0+ paradigm. I am using a class that holds deeply nested data in it (in this minimum viable code: a 'Collection' that contains a name and children are meant to be a Show Series, and the Series has children that are meant to be Seasons, and so on...).

The nested structure is something like this:

List<CollectionState>
|-- List<CollectionState>
|   |-- List<CollectionState>

An important functionality of the code is that a child is added to the children list of the correct parent so it will display in the correct order of the hierarchy of parents to their children in the ListView; ie Collection has one Series (8768), and that Series has two Seasons (1817 and 7623), and when pressing on a Season, an Episode is added to its correct parent Season instead of being added to the bottom of the ListView. In this case, pressing on Season 1817 four times adds Episodes 2175, 2773, 5420 and 8826 under itself instead of adding to Season 7623. 在此处输入图像描述

Problem: As I understand it, a good practice while working with BLoC 8.0.0+ would be extending the CollectionState class with Equatable. The following code that I provide works; however, it does use this best practice. I want it to do so, but I am having problems which I will explain shortly. I have commented in the code of collection_state.dart with:

// 1) where Equatable should be extended  

I have located in the code where the issue occurs in collection_bloc.dart when the CollectionState class is extended with Equatable (please note that the problem happens only with changing the code by extending CollectionState class with Equatable, which the code does not do). I have commented in the code with this comment:

//TODO: Here is the problem. This code does not work properly when I extend the CollectionState class to Equatable.

Surprisingly, there is little information online that I could find for using deeply nested data with BLoC 8.0.0+.

I am new to BLoC 8.0.0+ and even newer to Equatable (I've always used Provider up to this point) and I don't understand why my code is not updating correctly when extending Equatable. I guess I am having an immutability issue because the update to the class with the AddInfo bloc event is not considered different to Equatable. I am at a loss to understand how to change my code to use the best practices with deeply nested data with BLoC.

Question:

  1. How do I change my code to extend the CollectionState class with Equatable and still have it update my UI correctly?

  2. Bearing in mind that I have a cursory understanding of Equatable, I would like to know more about the underlying root of the problem. Is it the case that the bloc event method is not producing a class different enough to Equatable, so it is not updating or is something entirely different happening here?

pub spec.yaml

dependencies:
  flutter:
    sdk: flutter

  flutter_bloc: ^8.1.1
  equatable: ^2.0.5

main.dart

import 'package:deeply_nested_objects/add_collection_logic.dart';
import 'package:deeply_nested_objects/bloc/collection_bloc.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocProvider(
      create: (context) => CollectionBloc(),
      child: const MaterialApp(
        home: MyHomePage(),
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<CollectionBloc, CollectionState>(
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(
            title: const Text('Deeply nested data and Bloc 8.0.0+'),
          ),
          body: ListView.builder(
            itemCount: state.getAllNodes(state).length,
            itemBuilder: (context, index) {
              var nodes = state.getAllNodes(state)[index];
              return ListTile(
                onTap: () => addToCollection(nodes.showType, index, context),
                leading: Card(
                  child: Text(nodes.name),
                ),
              );
            },
          ),
        );
      },
    );
  }
}

add_collection_logic.dart

import 'dart:math';

import 'package:deeply_nested_objects/bloc/collection_bloc.dart';
import 'package:deeply_nested_objects/bloc/collection_event.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

void addToCollection(ShowType showType, int index, BuildContext context) {
    
    if (showType == ShowType.collection) {
      BlocProvider.of<CollectionBloc>(context).add(AddInfo(
        child: CollectionState(
          name: "Series ${(1000 + Random().nextInt(9000))}",
          showType: ShowType.series,
          children: [],
        ),
        index: index,
      ));
    }
    if (showType == ShowType.series) {
      BlocProvider.of<CollectionBloc>(context).add(AddInfo(
        child: CollectionState(
          name: 'Season ${(1000 + Random().nextInt(9000))}',
          showType: ShowType.season,
          children: [],
        ),
        index: index,
      ));
    }
    if (showType == ShowType.season) {
      BlocProvider.of<CollectionBloc>(context).add(AddInfo(
        child: CollectionState(
          name: "Episode ${(1000 + Random().nextInt(9000))}",
          showType: ShowType.episode,
          children: [],
        ),
        index: index,
      ));
    }
  }

collection_event.dart

import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:equatable/equatable.dart';

abstract class CollectionEvents extends Equatable {
  @override
  List<Object> get props => [];
}

class AddInfo extends CollectionEvents {
  AddInfo({required this.index, required this.child});

  final int index;
  final CollectionState child;
}

collection_bloc.dart

import 'package:deeply_nested_objects/bloc/collection_event.dart';
import 'package:deeply_nested_objects/bloc/collection_state.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

class CollectionBloc extends Bloc<CollectionEvents, CollectionState> {
  CollectionBloc() : super(CollectionState.initial()) {
    on<AddInfo>((event, emit) {
      if (event.child.showType == ShowType.series) {
        emit(state.copyWith(children: [...state.children, event.child]));
      }
      if (event.child.showType == ShowType.season ||
          event.child.showType == ShowType.episode) {
      //TODO: Here is the problem. This code does not work properly when I extend the CollectionState class to Equatable.
        // get the list of all nodes
        List<CollectionState> list = state.getAllNodes(state);
        // find the parent node while still in the list
        CollectionState parent = list[event.index];
        // add the child to the parent
        parent.children.add(event.child);
        // update the state
        emit(state.copyWith(children: [...state.children]));
      }
    });
  }
}

collection_state.dart

enum ShowType { collection, series, season, episode }

// 1) where should be Equatable
class CollectionState {
  const CollectionState({
    required this.name,
    required this.children,
    required this.showType,
  });
  final String name;
  final List<CollectionState> children;
  final ShowType showType;

  factory CollectionState.initial() {
    return const CollectionState(
      name: "Collection",
      showType: ShowType.collection,
      children: [],
    );
  }

  List<CollectionState> getAllNodes(CollectionState node) {
    // empty list to store the result
    List<CollectionState> result = [];
    // add the current node
    result.add(node);
    // add the children too
    for (CollectionState child in node.children) {
      // composite design pattern seek and find
      result.addAll(getAllNodes(child));
    }
    return result;
  }

  CollectionState copyWith({
    String? name,
    List<CollectionState>? children,
    ShowType? showType,
  }) {
    return CollectionState(
      name: name ?? this.name,
      children: children ?? this.children,
      showType: showType ?? this.showType,
    );
  }
}

You have to assign properties in equatable that you want to check equality on.

Example:

class SomeClass extends Equatable {
  SomeClass({required this.index});

  final int index;

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

If I would check some SomeClass(index: 10) == SomeClass(index: 9) it would be true because I didn't say equatable what properties it should look for on == operator

If I update my code to

class SomeClass extends Equatable {
  SomeClass({required this.index});

  final int index;

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

Now same check would be false, cause it is looking on index property

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM