Browse Source

chore: cache row data

appflowy 3 years ago
parent
commit
3cc7c8e6de
17 changed files with 247 additions and 225 deletions
  1. 0 7
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 4 5
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart
  3. 7 12
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart
  4. 5 5
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart
  5. 7 8
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart
  6. 9 14
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart
  7. 2 6
      frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart
  8. 13 13
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  9. 5 5
      frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart
  10. 6 99
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  11. 3 3
      frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart
  12. 28 30
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  13. 130 6
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  14. 10 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  15. 8 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart
  16. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart
  17. 9 3
      frontend/app_flowy/packages/flowy_infra/lib/notifier.dart

+ 0 - 7
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -150,13 +150,6 @@ void _resolveGridDeps(GetIt getIt) {
     (view, _) => GridBloc(view: view),
   );
 
-  getIt.registerFactoryParam<RowBloc, RowData, GridFieldCache>(
-    (data, fieldCache) => RowBloc(
-      rowData: data,
-      fieldCache: fieldCache,
-    ),
-  );
-
   getIt.registerFactoryParam<GridHeaderBloc, String, GridFieldCache>(
     (gridId, fieldCache) => GridHeaderBloc(
       gridId: gridId,

+ 4 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart

@@ -58,12 +58,11 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
       fieldId: state.cellData.field.id,
       rowId: state.cellData.rowId,
     );
+    if (isClosed) {
+      return;
+    }
     result.fold(
-      (cell) {
-        if (!isClosed) {
-          add(CheckboxCellEvent.didReceiveCellUpdate(cell));
-        }
-      },
+      (cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)),
       (err) => Log.error(err),
     );
   }

+ 7 - 12
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart

@@ -57,19 +57,15 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
         (notificationData) => _loadCellData(),
         (err) => Log.error(err),
       );
-    });
+    }, listenWhen: () => !isClosed);
     _cellListener.start();
 
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
       result.fold(
-        (field) {
-          if (!isClosed) {
-            add(DateCellEvent.didReceiveFieldUpdate(field));
-          }
-        },
+        (field) => add(DateCellEvent.didReceiveFieldUpdate(field)),
         (err) => Log.error(err),
       );
-    });
+    }, listenWhen: () => !isClosed);
     _fieldListener.start();
   }
 
@@ -79,12 +75,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
       fieldId: state.cellData.field.id,
       rowId: state.cellData.rowId,
     );
+    if (isClosed) {
+      return;
+    }
     result.fold(
-      (cell) {
-        if (!isClosed) {
-          add(DateCellEvent.didReceiveCellUpdate(cell));
-        }
-      },
+      (cell) => add(DateCellEvent.didReceiveCellUpdate(cell)),
       (err) => Log.error(err),
     );
   }

+ 5 - 5
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart

@@ -84,12 +84,12 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
       fieldId: state.cellData.field.id,
       rowId: state.cellData.rowId,
     );
+
+    if (isClosed) {
+      return;
+    }
     result.fold(
-      (cell) {
-        if (!isClosed) {
-          add(NumberCellEvent.didReceiveCellUpdate(cell));
-        }
-      },
+      (cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)),
       (err) => Log.error(err),
     );
   }

+ 7 - 8
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart

@@ -49,16 +49,15 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
       fieldId: state.cellData.field.id,
       rowId: state.cellData.rowId,
     );
+    if (isClosed) {
+      return;
+    }
 
     result.fold(
-      (selectOptionContext) {
-        if (!isClosed) {
-          add(SelectionCellEvent.didReceiveOptions(
-            selectOptionContext.options,
-            selectOptionContext.selectOptions,
-          ));
-        }
-      },
+      (selectOptionContext) => add(SelectionCellEvent.didReceiveOptions(
+        selectOptionContext.options,
+        selectOptionContext.selectOptions,
+      )),
       (err) => Log.error(err),
     );
   }

+ 9 - 14
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_editor_bloc.dart

@@ -117,16 +117,15 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
           fieldId: state.field.id,
           rowId: state.rowId,
         );
+        if (isClosed) {
+          return;
+        }
 
         result.fold(
-          (selectOptionContext) {
-            if (!isClosed) {
-              add(SelectOptionEditorEvent.didReceiveOptions(
-                selectOptionContext.options,
-                selectOptionContext.selectOptions,
-              ));
-            }
-          },
+          (selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
+            selectOptionContext.options,
+            selectOptionContext.selectOptions,
+          )),
           (err) => Log.error(err),
         );
       },
@@ -144,14 +143,10 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
 
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
       result.fold(
-        (field) {
-          if (!isClosed) {
-            add(SelectOptionEditorEvent.didReceiveFieldUpdate(field));
-          }
-        },
+        (field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
         (err) => Log.error(err),
       );
-    });
+    }, listenWhen: () => !isClosed);
     _fieldListener.start();
   }
 }

+ 2 - 6
frontend/app_flowy/lib/workspace/application/grid/field/field_cell_bloc.dart

@@ -47,14 +47,10 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
   void _startListening() {
     _fieldListener.updateFieldNotifier?.addPublishListener((result) {
       result.fold(
-        (field) {
-          if (!isClosed) {
-            add(FieldCellEvent.didReceiveFieldUpdate(field));
-          }
-        },
+        (field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
         (err) => Log.error(err),
       );
-    });
+    }, listenWhen: () => !isClosed);
     _fieldListener.start();
   }
 }

+ 13 - 13
frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart

@@ -18,14 +18,14 @@ class GridBloc extends Bloc<GridEvent, GridState> {
   final GridListener _gridListener;
   final GridFieldsListener _fieldListener;
   final GridFieldCache fieldCache;
-  final GridRowCache _rowCache;
+  final GridRowCache rowCache;
 
   GridBloc({required View view})
       : _fieldListener = GridFieldsListener(gridId: view.id),
         _gridService = GridService(gridId: view.id),
         _gridListener = GridListener(gridId: view.id),
         fieldCache = GridFieldCache(),
-        _rowCache = GridRowCache(gridId: view.id),
+        rowCache = GridRowCache(gridId: view.id),
         super(GridState.initial(view.id)) {
     on<GridEvent>(
       (event, emit) async {
@@ -41,7 +41,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
             emit(state.copyWith(rows: value.rows, listState: value.listState));
           },
           didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
-            emit(state.copyWith(rows: _rowCache.rows, fields: value.fields));
+            emit(state.copyWith(rows: rowCache.rows, fields: value.fields));
           },
         );
       },
@@ -62,7 +62,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       result.fold(
         (changeset) {
           fieldCache.applyChangeset(changeset);
-          _rowCache.updateFields(fieldCache.unmodifiableFields);
+          rowCache.updateFields(fieldCache.unmodifiableFields);
           add(GridEvent.didReceiveFieldUpdate(fieldCache.clonedFields));
         },
         (err) => Log.error(err),
@@ -74,15 +74,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       result.fold(
         (changesets) {
           for (final changeset in changesets) {
-            _rowCache
+            rowCache
                 .deleteRows(changeset.deletedRows)
-                .foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(_rowCache.rows, listState)));
+                .foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(rowCache.rows, listState)));
 
-            _rowCache
+            rowCache
                 .insertRows(changeset.insertedRows)
-                .foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(_rowCache.rows, listState)));
+                .foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(rowCache.rows, listState)));
 
-            _rowCache.updateRows(changeset.updatedRows);
+            rowCache.updateRows(changeset.updatedRows);
           }
         },
         (err) => Log.error(err),
@@ -107,12 +107,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
       () => result.fold(
         (fields) {
           fieldCache.clonedFields = fields.items;
-          _rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
+          rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
 
           emit(state.copyWith(
             grid: Some(grid),
             fields: fieldCache.clonedFields,
-            rows: _rowCache.rows,
+            rows: rowCache.rows,
             loadingState: GridLoadingState.finish(left(unit)),
           ));
         },
@@ -126,7 +126,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
 class GridEvent with _$GridEvent {
   const factory GridEvent.initial() = InitialGrid;
   const factory GridEvent.createRow() = _CreateRow;
-  const factory GridEvent.didReceiveRowUpdate(List<RowData> rows, GridListState listState) = _DidReceiveRowUpdate;
+  const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridListState listState) = _DidReceiveRowUpdate;
   const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
 }
 
@@ -136,7 +136,7 @@ class GridState with _$GridState {
     required String gridId,
     required Option<Grid> grid,
     required List<Field> fields,
-    required List<RowData> rows,
+    required List<GridRow> rows,
     required GridLoadingState loadingState,
     required GridListState listState,
   }) = _GridState;

+ 5 - 5
frontend/app_flowy/lib/workspace/application/grid/grid_header_bloc.dart

@@ -36,11 +36,11 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
   }
 
   Future<void> _startListening() async {
-    fieldCache.addListener(() {}, onChanged: (fields) {
-      if (!isClosed) {
-        add(GridHeaderEvent.didReceiveFieldUpdate(fields));
-      }
-    });
+    fieldCache.addListener(
+      () {},
+      onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
+      listenWhen: () => !isClosed,
+    );
   }
 
   @override

+ 6 - 99
frontend/app_flowy/lib/workspace/application/grid/grid_service.dart

@@ -5,9 +5,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
-import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
-
-part 'grid_service.freezed.dart';
 
 class GridService {
   final String gridId;
@@ -74,11 +71,16 @@ class GridFieldCache {
     _fieldNotifier.addListener(() => onFieldChanged(clonedFields));
   }
 
-  void addListener(VoidCallback listener, {void Function(List<Field>)? onChanged}) {
+  void addListener(VoidCallback listener, {void Function(List<Field>)? onChanged, bool Function()? listenWhen}) {
     _fieldNotifier.addListener(() {
       if (onChanged != null) {
         onChanged(clonedFields);
       }
+
+      if (listenWhen != null && listenWhen() == false) {
+        return;
+      }
+
       listener();
     });
   }
@@ -130,98 +132,3 @@ class GridFieldCache {
     _fieldNotifier.dispose();
   }
 }
-
-class GridRowCache {
-  final String gridId;
-  UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
-  List<RowData> _rows = [];
-
-  GridRowCache({required this.gridId});
-
-  List<RowData> get rows => [..._rows];
-
-  void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
-    _fields = fields;
-    _rows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
-      return RowData.fromBlockRow(gridId, rowOrder, _fields);
-    }).toList();
-  }
-
-  void updateFields(UnmodifiableListView<Field> fields) {
-    if (fields.isEmpty) {
-      return;
-    }
-
-    _fields = fields;
-    _rows = _rows.map((row) => row.copyWith(fields: fields)).toList();
-  }
-
-  Option<GridListState> deleteRows(List<RowOrder> deletedRows) {
-    if (deletedRows.isEmpty) {
-      return none();
-    }
-
-    final List<RowData> newRows = [];
-    final DeletedIndex deletedIndex = [];
-    final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
-    _rows.asMap().forEach((index, value) {
-      if (deletedRowMap[value.rowId] == null) {
-        newRows.add(value);
-      } else {
-        deletedIndex.add(Tuple2(index, value));
-      }
-    });
-    _rows = newRows;
-
-    return Some(GridListState.delete(deletedIndex));
-  }
-
-  Option<GridListState> insertRows(List<IndexRowOrder> createdRows) {
-    if (createdRows.isEmpty) {
-      return none();
-    }
-
-    InsertedIndexs insertIndexs = [];
-    for (final newRow in createdRows) {
-      if (newRow.hasIndex()) {
-        insertIndexs.add(Tuple2(newRow.index, newRow.rowOrder.rowId));
-        _rows.insert(newRow.index, _toRowData(newRow.rowOrder));
-      } else {
-        insertIndexs.add(Tuple2(newRow.index, newRow.rowOrder.rowId));
-        _rows.add(_toRowData(newRow.rowOrder));
-      }
-    }
-
-    return Some(GridListState.insert(insertIndexs));
-  }
-
-  void updateRows(List<RowOrder> updatedRows) {
-    if (updatedRows.isEmpty) {
-      return;
-    }
-
-    final List<int> updatedIndexs = [];
-    for (final updatedRow in updatedRows) {
-      final index = _rows.indexWhere((row) => row.rowId == updatedRow.rowId);
-      if (index != -1) {
-        _rows.removeAt(index);
-        _rows.insert(index, _toRowData(updatedRow));
-        updatedIndexs.add(index);
-      }
-    }
-  }
-
-  RowData _toRowData(RowOrder rowOrder) {
-    return RowData.fromBlockRow(gridId, rowOrder, _fields);
-  }
-}
-
-typedef InsertedIndexs = List<Tuple2<int, String>>;
-typedef DeletedIndex = List<Tuple2<int, RowData>>;
-
-@freezed
-class GridListState with _$GridListState {
-  const factory GridListState.insert(InsertedIndexs items) = _Insert;
-  const factory GridListState.delete(DeletedIndex items) = _Delete;
-  const factory GridListState.initial() = InitialListState;
-}

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/row/row_action_sheet_bloc.dart

@@ -11,7 +11,7 @@ part 'row_action_sheet_bloc.freezed.dart';
 class RowActionSheetBloc extends Bloc<RowActionSheetEvent, RowActionSheetState> {
   final RowService _rowService;
 
-  RowActionSheetBloc({required RowData rowData})
+  RowActionSheetBloc({required GridRow rowData})
       : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
         super(RowActionSheetState.initial(rowData)) {
     on<RowActionSheetEvent>(
@@ -49,10 +49,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent {
 @freezed
 class RowActionSheetState with _$RowActionSheetState {
   const factory RowActionSheetState({
-    required RowData rowData,
+    required GridRow rowData,
   }) = _RowActionSheetState;
 
-  factory RowActionSheetState.initial(RowData rowData) => RowActionSheetState(
+  factory RowActionSheetState.initial(GridRow rowData) => RowActionSheetState(
         rowData: rowData,
       );
 }

+ 28 - 30
frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart

@@ -18,11 +18,16 @@ class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService _rowService;
   final RowListener _rowlistener;
   final GridFieldCache _fieldCache;
+  final GridRowCache _rowCache;
 
-  RowBloc({required RowData rowData, required GridFieldCache fieldCache})
-      : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
-        _fieldCache = fieldCache,
+  RowBloc({
+    required GridRow rowData,
+    required GridFieldCache fieldCache,
+    required GridRowCache rowCache,
+  })  : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
         _rowlistener = RowListener(rowId: rowData.rowId),
+        _fieldCache = fieldCache,
+        _rowCache = rowCache,
         super(RowState.initial(rowData)) {
     on<RowEvent>(
       (event, emit) async {
@@ -76,37 +81,30 @@ class RowBloc extends Bloc<RowEvent, RowState> {
   }
 
   Future<void> _startListening() async {
-    _rowlistener.updateRowNotifier?.addPublishListener((result) {
-      result.fold(
-        (row) {
-          if (!isClosed) {
-            add(RowEvent.didUpdateRow(row));
-          }
-        },
-        (err) => Log.error(err),
-      );
-    });
+    _rowlistener.updateRowNotifier?.addPublishListener(
+      (result) {
+        result.fold(
+          (row) => add(RowEvent.didUpdateRow(row)),
+          (err) => Log.error(err),
+        );
+      },
+      listenWhen: () => !isClosed,
+    );
 
-    _fieldCache.addListener(() {
-      if (!isClosed) {
-        add(const RowEvent.fieldsDidUpdate());
-      }
-    });
+    _fieldCache.addListener(
+      () => add(const RowEvent.fieldsDidUpdate()),
+      listenWhen: () => !isClosed,
+    );
 
     _rowlistener.start();
   }
 
   Future<void> _loadRow(Emitter<RowState> emit) async {
-    _rowService.getRow().then((result) {
-      return result.fold(
-        (row) {
-          if (!isClosed) {
-            add(RowEvent.didLoadRow(row));
-          }
-        },
-        (err) => Log.error(err),
-      );
-    });
+    final data = await _rowCache.getRowData(state.rowData.rowId);
+    if (isClosed) {
+      return;
+    }
+    data.foldRight(null, (data, _) => add(RowEvent.didLoadRow(data)));
   }
 
   CellDataMap _makeCellDatas(Row row, List<Field> fields) {
@@ -137,12 +135,12 @@ class RowEvent with _$RowEvent {
 @freezed
 class RowState with _$RowState {
   const factory RowState({
-    required RowData rowData,
+    required GridRow rowData,
     required Future<Option<Row>> row,
     required Option<CellDataMap> cellDataMap,
   }) = _RowState;
 
-  factory RowState.initial(RowData rowData) => RowState(
+  factory RowState.initial(GridRow rowData) => RowState(
         rowData: rowData,
         row: Future(() => none()),
         cellDataMap: none(),

+ 130 - 6
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -1,5 +1,8 @@
+import 'dart:collection';
+
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
@@ -57,6 +60,115 @@ class RowService {
   }
 }
 
+class GridRowCache {
+  final String gridId;
+  UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
+  HashMap<String, Row> rowDataMap = HashMap();
+
+  List<GridRow> _rows = [];
+
+  GridRowCache({required this.gridId});
+
+  List<GridRow> get rows => [..._rows];
+
+  Future<Option<Row>> getRowData(String rowId) async {
+    final Row? data = rowDataMap[rowId];
+    if (data != null) {
+      return Future(() => Some(data));
+    }
+
+    final payload = RowIdentifierPayload.create()
+      ..gridId = gridId
+      ..rowId = rowId;
+
+    final result = await GridEventGetRow(payload).send();
+    return Future(() {
+      return result.fold(
+        (data) {
+          data.freeze();
+          rowDataMap[data.id] = data;
+          return Some(data);
+        },
+        (err) {
+          Log.error(err);
+          return none();
+        },
+      );
+    });
+  }
+
+  void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
+    _fields = fields;
+    _rows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
+      return GridRow.fromBlockRow(gridId, rowOrder, _fields);
+    }).toList();
+  }
+
+  void updateFields(UnmodifiableListView<Field> fields) {
+    if (fields.isEmpty) {
+      return;
+    }
+
+    _fields = fields;
+    _rows = _rows.map((row) => row.copyWith(fields: fields)).toList();
+  }
+
+  Option<GridListState> deleteRows(List<RowOrder> deletedRows) {
+    if (deletedRows.isEmpty) {
+      return none();
+    }
+
+    final List<GridRow> newRows = [];
+    final DeletedIndex deletedIndex = [];
+    final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
+    _rows.asMap().forEach((index, value) {
+      if (deletedRowMap[value.rowId] == null) {
+        newRows.add(value);
+      } else {
+        deletedIndex.add(Tuple2(index, value));
+      }
+    });
+    _rows = newRows;
+
+    return Some(GridListState.delete(deletedIndex));
+  }
+
+  Option<GridListState> insertRows(List<IndexRowOrder> createdRows) {
+    if (createdRows.isEmpty) {
+      return none();
+    }
+
+    InsertedIndexs insertIndexs = [];
+    for (final createdRow in createdRows) {
+      final gridRow = GridRow.fromBlockRow(gridId, createdRow.rowOrder, _fields);
+      insertIndexs.add(Tuple2(createdRow.index, gridRow.rowId));
+      _rows.insert(createdRow.index, gridRow);
+    }
+
+    return Some(GridListState.insert(insertIndexs));
+  }
+
+  void updateRows(List<RowOrder> updatedRows) {
+    if (updatedRows.isEmpty) {
+      return;
+    }
+
+    final List<int> updatedIndexs = [];
+    for (final updatedRow in updatedRows) {
+      final index = _rows.indexWhere((row) => row.rowId == updatedRow.rowId);
+      if (index != -1) {
+        _rows.removeAt(index);
+        _rows.insert(index, _toRowData(updatedRow));
+        updatedIndexs.add(index);
+      }
+    }
+  }
+
+  GridRow _toRowData(RowOrder rowOrder) {
+    return GridRow.fromBlockRow(gridId, rowOrder, _fields);
+  }
+}
+
 @freezed
 class CellData with _$CellData {
   const factory CellData({
@@ -68,20 +180,32 @@ class CellData with _$CellData {
 }
 
 @freezed
-class RowData with _$RowData {
-  const factory RowData({
+class GridRow with _$GridRow {
+  const factory GridRow({
     required String gridId,
     required String rowId,
     required List<Field> fields,
     required double height,
-  }) = _RowData;
+    required Future<Option<Row>> data,
+  }) = _GridRow;
 
-  factory RowData.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
-    return RowData(
+  factory GridRow.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
+    return GridRow(
       gridId: gridId,
-      rowId: row.rowId,
       fields: fields,
+      rowId: row.rowId,
+      data: Future(() => none()),
       height: row.height.toDouble(),
     );
   }
 }
+
+typedef InsertedIndexs = List<Tuple2<int, String>>;
+typedef DeletedIndex = List<Tuple2<int, GridRow>>;
+
+@freezed
+class GridListState with _$GridListState {
+  const factory GridListState.insert(InsertedIndexs items) = _Insert;
+  const factory GridListState.delete(DeletedIndex items) = _Delete;
+  const factory GridListState.initial() = InitialListState;
+}

+ 10 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -1,5 +1,6 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/row/row_bloc.dart';
 import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@@ -221,14 +222,19 @@ class _GridRowsState extends State<_GridRows> {
     );
   }
 
-  Widget _renderRow(BuildContext context, RowData rowData, Animation<double> animation) {
-    final fieldCache = context.read<GridBloc>().fieldCache;
+  Widget _renderRow(BuildContext context, GridRow rowData, Animation<double> animation) {
+    final bloc = context.read<GridBloc>();
+    final fieldCache = bloc.fieldCache;
+    final rowCache = bloc.rowCache;
 
     return SizeTransition(
       sizeFactor: animation,
       child: GridRowWidget(
-        data: rowData,
-        fieldCache: fieldCache,
+        blocBuilder: () => RowBloc(
+          rowData: rowData,
+          fieldCache: fieldCache,
+          rowCache: rowCache,
+        ),
         key: ValueKey(rowData.rowId),
       ),
     );

+ 8 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart

@@ -12,9 +12,12 @@ import 'package:provider/provider.dart';
 import 'row_action_sheet.dart';
 
 class GridRowWidget extends StatefulWidget {
-  final RowData data;
-  final GridFieldCache fieldCache;
-  const GridRowWidget({required this.data, required this.fieldCache, Key? key}) : super(key: key);
+  final RowBloc Function() blocBuilder;
+
+  const GridRowWidget({
+    required this.blocBuilder,
+    Key? key,
+  }) : super(key: key);
 
   @override
   State<GridRowWidget> createState() => _GridRowWidgetState();
@@ -26,7 +29,8 @@ class _GridRowWidgetState extends State<GridRowWidget> {
 
   @override
   void initState() {
-    _rowBloc = getIt<RowBloc>(param1: widget.data, param2: widget.fieldCache)..add(const RowEvent.initial());
+    _rowBloc = widget.blocBuilder();
+    _rowBloc.add(const RowEvent.initial());
     _rowStateNotifier = _RegionStateNotifier();
     super.initState();
   }

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/row/row_action_sheet.dart

@@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class GridRowActionSheet extends StatelessWidget {
-  final RowData rowData;
+  final GridRow rowData;
   const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key);
 
   @override

+ 9 - 3
frontend/app_flowy/packages/flowy_infra/lib/notifier.dart

@@ -31,12 +31,18 @@ class PublishNotifier<T> extends ChangeNotifier {
 
   T? get currentValue => _value;
 
-  void addPublishListener(void Function(T) callback) {
+  void addPublishListener(void Function(T) callback, {bool Function()? listenWhen}) {
     super.addListener(
       () {
-        if (_value != null) {
-          callback(_value!);
+        if (_value == null) {
+          return;
         }
+
+        if (listenWhen != null && listenWhen() == false) {
+          return;
+        }
+
+        callback(_value!);
       },
     );
   }