瀏覽代碼

refactor: add documentation to GridCellContext

appflowy 2 年之前
父節點
當前提交
3844d6aad1

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/block/block_service.dart

@@ -9,18 +9,18 @@ import 'block_listener.dart';
 class GridBlockCacheService {
   final String gridId;
   final GridBlock block;
-  late GridRowCacheService _rowCache;
+  late GridRowsCache _rowCache;
   late GridBlockListener _listener;
 
   List<GridRow> get rows => _rowCache.rows;
-  GridRowCacheService get rowCache => _rowCache;
+  GridRowsCache get rowCache => _rowCache;
 
   GridBlockCacheService({
     required this.gridId,
     required this.block,
     required GridFieldCache fieldCache,
   }) {
-    _rowCache = GridRowCacheService(
+    _rowCache = GridRowsCache(
       gridId: gridId,
       block: block,
       delegate: GridRowCacheDelegateImpl(fieldCache),

+ 9 - 51
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cache.dart

@@ -3,7 +3,7 @@ part of 'cell_service.dart';
 typedef GridCellMap = LinkedHashMap<String, GridCell>;
 
 class _GridCellCacheObject {
-  _GridCellCacheKey key;
+  GridCellCacheKey key;
   dynamic object;
   _GridCellCacheObject({
     required this.key,
@@ -11,67 +11,26 @@ class _GridCellCacheObject {
   });
 }
 
-class _GridCellCacheKey {
+class GridCellCacheKey {
   final String fieldId;
   final String rowId;
-  _GridCellCacheKey({
+  GridCellCacheKey({
     required this.fieldId,
     required this.rowId,
   });
 }
 
-abstract class GridCellCacheDelegate {
-  void onFieldUpdated(void Function(Field) callback);
-}
-
-class GridCellCacheService {
+class GridCellsCache {
   final String gridId;
-  final GridCellCacheDelegate delegate;
-
-  /// fieldId: {objectId: callback}
-  final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
 
   /// fieldId: {cacheKey: cacheData}
   final Map<String, Map<String, dynamic>> _cellDataByFieldId = {};
-  GridCellCacheService({
+  GridCellsCache({
     required this.gridId,
-    required this.delegate,
-  }) {
-    delegate.onFieldUpdated((field) {
-      _cellDataByFieldId.remove(field.id);
-      final map = _fieldListenerByFieldId[field.id];
-      if (map != null) {
-        for (final callbacks in map.values) {
-          for (final callback in callbacks) {
-            callback();
-          }
-        }
-      }
-    });
-  }
-
-  void addFieldListener(_GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
-    var map = _fieldListenerByFieldId[cacheKey.fieldId];
-    if (map == null) {
-      _fieldListenerByFieldId[cacheKey.fieldId] = {};
-      map = _fieldListenerByFieldId[cacheKey.fieldId];
-      map![cacheKey.rowId] = [onFieldChanged];
-    } else {
-      var objects = map[cacheKey.rowId];
-      if (objects == null) {
-        map[cacheKey.rowId] = [onFieldChanged];
-      } else {
-        objects.add(onFieldChanged);
-      }
-    }
-  }
+  });
 
-  void removeFieldListener(_GridCellCacheKey cacheKey, VoidCallback fn) {
-    var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
-    final index = callbacks?.indexWhere((callback) => callback == fn);
-    if (index != null && index != -1) {
-      callbacks?.removeAt(index);
-    }
+  void remove(String fieldId) {
+    _cellDataByFieldId.remove(fieldId);
   }
 
   void insert<T extends _GridCellCacheObject>(T item) {
@@ -84,7 +43,7 @@ class GridCellCacheService {
     map![item.key.rowId] = item.object;
   }
 
-  T? get<T>(_GridCellCacheKey key) {
+  T? get<T>(GridCellCacheKey key) {
     final map = _cellDataByFieldId[key.fieldId];
     if (map == null) {
       return null;
@@ -103,7 +62,6 @@ class GridCellCacheService {
   }
 
   Future<void> dispose() async {
-    _fieldListenerByFieldId.clear();
     _cellDataByFieldId.clear();
   }
 }

+ 0 - 33
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_loader.dart

@@ -3,23 +3,13 @@ part of 'cell_service.dart';
 abstract class IGridCellDataConfig {
   // The cell data will reload if it receives the field's change notification.
   bool get reloadOnFieldChanged;
-
-  // When the reloadOnCellChanged is true, it will load the cell data after user input.
-  // For example: The number cell reload the cell data that carries the format
-  // user input: 12
-  // cell display: $12
-  bool get reloadOnCellChanged;
 }
 
 class GridCellDataConfig implements IGridCellDataConfig {
-  @override
-  final bool reloadOnCellChanged;
-
   @override
   final bool reloadOnFieldChanged;
 
   const GridCellDataConfig({
-    this.reloadOnCellChanged = false,
     this.reloadOnFieldChanged = false,
   });
 }
@@ -72,29 +62,6 @@ class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
   }
 }
 
-class SelectOptionCellDataLoader extends IGridCellDataLoader<SelectOptionCellData> {
-  final SelectOptionService service;
-  final GridCell gridCell;
-  SelectOptionCellDataLoader({
-    required this.gridCell,
-  }) : service = SelectOptionService(gridCell: gridCell);
-  @override
-  Future<SelectOptionCellData?> loadData() async {
-    return service.getOpitonContext().then((result) {
-      return result.fold(
-        (data) => data,
-        (err) {
-          Log.error(err);
-          return null;
-        },
-      );
-    });
-  }
-
-  @override
-  IGridCellDataConfig get config => const GridCellDataConfig(reloadOnFieldChanged: true);
-}
-
 class StringCellDataParser implements ICellDataParser<String> {
   @override
   String? parserData(List<int> data) {

+ 3 - 3
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_data_persistence.dart

@@ -1,10 +1,10 @@
 part of 'cell_service.dart';
 
-abstract class _GridCellDataPersistence<D> {
+abstract class IGridCellDataPersistence<D> {
   Future<Option<FlowyError>> save(D data);
 }
 
-class CellDataPersistence implements _GridCellDataPersistence<String> {
+class CellDataPersistence implements IGridCellDataPersistence<String> {
   final GridCell gridCell;
 
   CellDataPersistence({
@@ -35,7 +35,7 @@ class CalendarData with _$CalendarData {
   const factory CalendarData({required DateTime date, String? time}) = _CalendarData;
 }
 
-class DateCellDataPersistence implements _GridCellDataPersistence<CalendarData> {
+class DateCellDataPersistence implements IGridCellDataPersistence<CalendarData> {
   final GridCell gridCell;
   DateCellDataPersistence({
     required this.gridCell,

+ 56 - 0
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_field_notifier.dart

@@ -0,0 +1,56 @@
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flutter/foundation.dart';
+
+import 'cell_service.dart';
+
+abstract class GridFieldChangedNotifier {
+  void onFieldChanged(void Function(Field) callback);
+}
+
+class GridCellFieldNotifier {
+  /// fieldId: {objectId: callback}
+  final Map<String, Map<String, List<VoidCallback>>> _fieldListenerByFieldId = {};
+
+  GridCellFieldNotifier({required GridFieldChangedNotifier notifier}) {
+    notifier.onFieldChanged(
+      (field) {
+        final map = _fieldListenerByFieldId[field.id];
+        if (map != null) {
+          for (final callbacks in map.values) {
+            for (final callback in callbacks) {
+              callback();
+            }
+          }
+        }
+      },
+    );
+  }
+
+  void addFieldListener(GridCellCacheKey cacheKey, VoidCallback onFieldChanged) {
+    var map = _fieldListenerByFieldId[cacheKey.fieldId];
+    if (map == null) {
+      _fieldListenerByFieldId[cacheKey.fieldId] = {};
+      map = _fieldListenerByFieldId[cacheKey.fieldId];
+      map![cacheKey.rowId] = [onFieldChanged];
+    } else {
+      var objects = map[cacheKey.rowId];
+      if (objects == null) {
+        map[cacheKey.rowId] = [onFieldChanged];
+      } else {
+        objects.add(onFieldChanged);
+      }
+    }
+  }
+
+  void removeFieldListener(GridCellCacheKey cacheKey, VoidCallback fn) {
+    var callbacks = _fieldListenerByFieldId[cacheKey.fieldId]?[cacheKey.rowId];
+    final index = callbacks?.indexWhere((callback) => callback == fn);
+    if (index != null && index != -1) {
+      callbacks?.removeAt(index);
+    }
+  }
+
+  Future<void> dispose() async {
+    _fieldListenerByFieldId.clear();
+  }
+}

+ 0 - 1
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/cell_service.dart

@@ -14,7 +14,6 @@ import 'package:flowy_sdk/protobuf/flowy-grid/url_type_option.pb.dart';
 import 'package:flutter/foundation.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:app_flowy/workspace/application/grid/cell/cell_listener.dart';
-import 'package:app_flowy/workspace/application/grid/cell/select_option_service.dart';
 import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
 import 'dart:convert' show utf8;
 part 'cell_service.freezed.dart';

+ 86 - 40
frontend/app_flowy/lib/workspace/application/grid/cell/cell_service/context_builder.dart

@@ -6,15 +6,19 @@ typedef GridDateCellContext = _GridCellContext<DateCellData, CalendarData>;
 typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
 
 class GridCellContextBuilder {
-  final GridCellCacheService _cellCache;
   final GridCell _gridCell;
-  GridCellContextBuilder({
-    required GridCellCacheService cellCache,
-    required GridCell gridCell,
-  })  : _cellCache = cellCache,
+  final GridCellsCache _cellCache;
+  final GridFieldCache _fieldCache;
+
+  GridCellContextBuilder(
+      {required GridCell gridCell, required GridCellsCache cellCache, required GridFieldCache fieldCache})
+      : _cellCache = cellCache,
+        _fieldCache = fieldCache,
         _gridCell = gridCell;
 
   _GridCellContext build() {
+    final cellFieldNotifier = GridCellFieldNotifier(notifier: _GridFieldChangedNotifierImpl(_fieldCache));
+
     switch (_gridCell.field.fieldType) {
       case FieldType.Checkbox:
         final cellDataLoader = GridCellDataLoader(
@@ -25,6 +29,7 @@ class GridCellContextBuilder {
           gridCell: _gridCell,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
+          cellFieldNotifier: cellFieldNotifier,
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.DateTime:
@@ -38,18 +43,20 @@ class GridCellContextBuilder {
           gridCell: _gridCell,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
+          cellFieldNotifier: cellFieldNotifier,
           cellDataPersistence: DateCellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.Number:
         final cellDataLoader = GridCellDataLoader(
           gridCell: _gridCell,
           parser: StringCellDataParser(),
-          config: const GridCellDataConfig(reloadOnCellChanged: true, reloadOnFieldChanged: true),
+          config: const GridCellDataConfig(reloadOnFieldChanged: true),
         );
         return GridCellContext(
           gridCell: _gridCell,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
+          cellFieldNotifier: cellFieldNotifier,
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.RichText:
@@ -61,6 +68,7 @@ class GridCellContextBuilder {
           gridCell: _gridCell,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
+          cellFieldNotifier: cellFieldNotifier,
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
       case FieldType.MultiSelect:
@@ -75,6 +83,7 @@ class GridCellContextBuilder {
           gridCell: _gridCell,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
+          cellFieldNotifier: cellFieldNotifier,
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
 
@@ -87,6 +96,7 @@ class GridCellContextBuilder {
           gridCell: _gridCell,
           cellCache: _cellCache,
           cellDataLoader: cellDataLoader,
+          cellFieldNotifier: cellFieldNotifier,
           cellDataPersistence: CellDataPersistence(gridCell: _gridCell),
         );
     }
@@ -99,14 +109,17 @@ class GridCellContextBuilder {
 // ignore: must_be_immutable
 class _GridCellContext<T, D> extends Equatable {
   final GridCell gridCell;
-  final GridCellCacheService cellCache;
-  final _GridCellCacheKey _cacheKey;
-  final IGridCellDataLoader<T> cellDataLoader;
-  final _GridCellDataPersistence<D> cellDataPersistence;
+  final GridCellsCache _cellsCache;
+  final GridCellCacheKey _cacheKey;
   final FieldService _fieldService;
+  final GridCellFieldNotifier _cellFieldNotifier;
+  // final GridCellFieldNotifier _fieldNotifier;
+  final IGridCellDataLoader<T> _cellDataLoader;
+  final IGridCellDataPersistence<D> _cellDataPersistence;
 
   late final CellListener _cellListener;
   late final ValueNotifier<T?>? _cellDataNotifier;
+
   bool isListening = false;
   VoidCallback? _onFieldChangedFn;
   Timer? _loadDataOperation;
@@ -114,18 +127,25 @@ class _GridCellContext<T, D> extends Equatable {
 
   _GridCellContext({
     required this.gridCell,
-    required this.cellCache,
-    required this.cellDataLoader,
-    required this.cellDataPersistence,
-  })  : _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
-        _cacheKey = _GridCellCacheKey(rowId: gridCell.rowId, fieldId: gridCell.field.id);
+    required GridCellsCache cellCache,
+    required GridCellFieldNotifier cellFieldNotifier,
+    required IGridCellDataLoader<T> cellDataLoader,
+    required IGridCellDataPersistence<D> cellDataPersistence,
+    // required GridFieldChangedNotifier notifierDelegate,
+  })  : _cellsCache = cellCache,
+        _cellDataLoader = cellDataLoader,
+        _cellDataPersistence = cellDataPersistence,
+        _cellFieldNotifier = cellFieldNotifier,
+        _fieldService = FieldService(gridId: gridCell.gridId, fieldId: gridCell.field.id),
+        _cacheKey = GridCellCacheKey(rowId: gridCell.rowId, fieldId: gridCell.field.id);
 
   _GridCellContext<T, D> clone() {
     return _GridCellContext(
         gridCell: gridCell,
-        cellDataLoader: cellDataLoader,
-        cellCache: cellCache,
-        cellDataPersistence: cellDataPersistence);
+        cellDataLoader: _cellDataLoader,
+        cellCache: _cellsCache,
+        cellFieldNotifier: _cellFieldNotifier,
+        cellDataPersistence: _cellDataPersistence);
   }
 
   String get gridId => gridCell.gridId;
@@ -145,10 +165,18 @@ class _GridCellContext<T, D> extends Equatable {
       Log.error("Already started. It seems like you should call clone first");
       return null;
     }
-
     isListening = true;
-    _cellDataNotifier = ValueNotifier(cellCache.get(_cacheKey));
+
+    /// The cell data will be changed by two reasons:
+    /// 1. User edit the cell
+    /// 2. User edit the field
+    ///   For example: The number cell reload the cell data that carries the format
+    ///   user input: 12
+    ///   cell display: $12
+    _cellDataNotifier = ValueNotifier(_cellsCache.get(_cacheKey));
     _cellListener = CellListener(rowId: gridCell.rowId, fieldId: gridCell.field.id);
+
+    /// Listen on user edit event and load the new cell data if needed.
     _cellListener.start(onCellChanged: (result) {
       result.fold(
         (_) => _loadData(),
@@ -156,21 +184,14 @@ class _GridCellContext<T, D> extends Equatable {
       );
     });
 
-    if (cellDataLoader.config.reloadOnFieldChanged) {
-      _onFieldChangedFn = () {
-        _loadData();
-      };
-      cellCache.addFieldListener(_cacheKey, _onFieldChangedFn!);
-    }
-
-    onCellChangedFn() {
-      onCellChanged(_cellDataNotifier?.value);
-
-      if (cellDataLoader.config.reloadOnCellChanged) {
-        _loadData();
-      }
+    /// Listen on the field event and load the cell data if needed.
+    if (_cellDataLoader.config.reloadOnFieldChanged) {
+      _onFieldChangedFn = () => _loadData();
+      _cellFieldNotifier.addFieldListener(_cacheKey, _onFieldChangedFn!);
     }
 
+    /// Notify the listener, the cell data was changed.
+    onCellChangedFn() => onCellChanged(_cellDataNotifier?.value);
     _cellDataNotifier?.addListener(onCellChangedFn);
     return onCellChangedFn;
   }
@@ -180,7 +201,7 @@ class _GridCellContext<T, D> extends Equatable {
   }
 
   T? getCellData({bool loadIfNoCache = true}) {
-    final data = cellCache.get(_cacheKey);
+    final data = _cellsCache.get(_cacheKey);
     if (data == null && loadIfNoCache) {
       _loadData();
     }
@@ -195,13 +216,13 @@ class _GridCellContext<T, D> extends Equatable {
     if (deduplicate) {
       _loadDataOperation?.cancel();
       _loadDataOperation = Timer(const Duration(milliseconds: 300), () async {
-        final result = await cellDataPersistence.save(data);
+        final result = await _cellDataPersistence.save(data);
         if (resultCallback != null) {
           resultCallback(result);
         }
       });
     } else {
-      final result = await cellDataPersistence.save(data);
+      final result = await _cellDataPersistence.save(data);
       if (resultCallback != null) {
         resultCallback(result);
       }
@@ -211,9 +232,9 @@ class _GridCellContext<T, D> extends Equatable {
   void _loadData() {
     _loadDataOperation?.cancel();
     _loadDataOperation = Timer(const Duration(milliseconds: 10), () {
-      cellDataLoader.loadData().then((data) {
+      _cellDataLoader.loadData().then((data) {
         _cellDataNotifier?.value = data;
-        cellCache.insert(_GridCellCacheObject(key: _cacheKey, object: data));
+        _cellsCache.insert(_GridCellCacheObject(key: _cacheKey, object: data));
       });
     });
   }
@@ -224,11 +245,36 @@ class _GridCellContext<T, D> extends Equatable {
     _saveDataOperation?.cancel();
 
     if (_onFieldChangedFn != null) {
-      cellCache.removeFieldListener(_cacheKey, _onFieldChangedFn!);
+      _cellFieldNotifier.removeFieldListener(_cacheKey, _onFieldChangedFn!);
       _onFieldChangedFn = null;
     }
   }
 
   @override
-  List<Object> get props => [cellCache.get(_cacheKey) ?? "", cellId];
+  List<Object> get props => [_cellsCache.get(_cacheKey) ?? "", cellId];
+}
+
+class _GridFieldChangedNotifierImpl extends GridFieldChangedNotifier {
+  final GridFieldCache _cache;
+  FieldChangesetCallback? _onChangesetFn;
+
+  _GridFieldChangedNotifierImpl(GridFieldCache cache) : _cache = cache;
+
+  @override
+  void dispose() {
+    if (_onChangesetFn != null) {
+      _cache.removeListener(onChangsetListener: _onChangesetFn!);
+      _onChangesetFn = null;
+    }
+  }
+
+  @override
+  void onFieldChanged(void Function(Field p1) callback) {
+    _onChangesetFn = (GridFieldChangeset changeset) {
+      for (final updatedField in changeset.updatedFields) {
+        callback(updatedField);
+      }
+    };
+    _cache.addListener(onChangeset: _onChangesetFn);
+  }
 }

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

@@ -68,7 +68,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return super.close();
   }
 
-  GridRowCacheService? getRowCache(String blockId, String rowId) {
+  GridRowsCache? getRowCache(String blockId, String rowId) {
     final GridBlockCacheService? blockCache = _blocks[blockId];
     return blockCache?.rowCache;
   }

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

@@ -202,7 +202,7 @@ class GridRowCacheDelegateImpl extends GridRowCacheDelegate {
   }
 
   @override
-  void onFieldUpdated(void Function(Field) callback) {
+  void onFieldChanged(void Function(Field) callback) {
     _onChangesetFn = (GridFieldChangeset changeset) {
       for (final updatedField in changeset.updatedFields) {
         callback(updatedField);

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

@@ -11,12 +11,12 @@ part 'row_bloc.freezed.dart';
 
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService _rowService;
-  final GridRowCacheService _rowCache;
+  final GridRowsCache _rowCache;
   void Function()? _rowListenFn;
 
   RowBloc({
     required GridRow rowData,
-    required GridRowCacheService rowCache,
+    required GridRowsCache rowCache,
   })  : _rowService = RowService(
           gridId: rowData.gridId,
           blockId: rowData.blockId,

+ 2 - 2
frontend/app_flowy/lib/workspace/application/grid/row/row_detail_bloc.dart

@@ -8,12 +8,12 @@ part 'row_detail_bloc.freezed.dart';
 
 class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
   final GridRow rowData;
-  final GridRowCacheService _rowCache;
+  final GridRowsCache _rowCache;
   void Function()? _rowListenFn;
 
   RowDetailBloc({
     required this.rowData,
-    required GridRowCacheService rowCache,
+    required GridRowsCache rowCache,
   })  : _rowCache = rowCache,
         super(RowDetailState.initial()) {
     on<RowDetailEvent>(

+ 9 - 8
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -1,5 +1,4 @@
 import 'dart:collection';
-
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
@@ -15,34 +14,36 @@ part 'row_service.freezed.dart';
 
 typedef RowUpdateCallback = void Function();
 
-abstract class GridRowCacheDelegate with GridCellCacheDelegate {
+abstract class GridRowCacheDelegate {
   UnmodifiableListView<Field> get fields;
-  void onFieldsChanged(void Function() callback);
+  void onFieldsChanged(VoidCallback callback);
+  void onFieldChanged(void Function(Field) callback);
   void dispose();
 }
 
-class GridRowCacheService {
+class GridRowsCache {
   final String gridId;
   final GridBlock block;
   final _Notifier _notifier;
   List<GridRow> _rows = [];
   final HashMap<String, Row> _rowByRowId;
   final GridRowCacheDelegate _delegate;
-  final GridCellCacheService _cellCache;
+  final GridCellsCache _cellCache;
 
   List<GridRow> get rows => _rows;
-  GridCellCacheService get cellCache => _cellCache;
+  GridCellsCache get cellCache => _cellCache;
 
-  GridRowCacheService({
+  GridRowsCache({
     required this.gridId,
     required this.block,
     required GridRowCacheDelegate delegate,
-  })  : _cellCache = GridCellCacheService(gridId: gridId, delegate: delegate),
+  })  : _cellCache = GridCellsCache(gridId: gridId),
         _rowByRowId = HashMap(),
         _notifier = _Notifier(),
         _delegate = delegate {
     //
     delegate.onFieldsChanged(() => _notifier.receive(const GridRowChangeReason.fieldDidChange()));
+    delegate.onFieldChanged((field) => _cellCache.remove(field.id));
     _rows = block.rowInfos.map((rowInfo) => buildGridRow(rowInfo.rowId, rowInfo.height.toDouble())).toList();
   }
 

+ 49 - 2
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/cell/cell_builder.dart

@@ -1,4 +1,5 @@
 import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
+import 'package:app_flowy/workspace/application/grid/grid_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter/widgets.dart';
@@ -12,10 +13,56 @@ import 'select_option_cell/select_option_cell.dart';
 import 'text_cell.dart';
 import 'url_cell/url_cell.dart';
 
-GridCellWidget buildGridCellWidget(GridCell gridCell, GridCellCacheService cellCache, {GridCellStyle? style}) {
+class GridCellBuilder {
+  final GridCellsCache cellCache;
+  final GridFieldCache fieldCache;
+  GridCellBuilder({
+    required this.cellCache,
+    required this.fieldCache,
+  });
+
+  GridCellWidget build(GridCell cell, {GridCellStyle? style}) {
+    final key = ValueKey(gridCell.cellId());
+
+    final cellContextBuilder = GridCellContextBuilder(
+      gridCell: gridCell,
+      cellCache: cellCache,
+      fieldCache: fieldCache,
+    );
+
+    switch (gridCell.field.fieldType) {
+      case FieldType.Checkbox:
+        return CheckboxCell(cellContextBuilder: cellContextBuilder, key: key);
+      case FieldType.DateTime:
+        return DateCell(cellContextBuilder: cellContextBuilder, key: key, style: style);
+      case FieldType.SingleSelect:
+        return SingleSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
+      case FieldType.MultiSelect:
+        return MultiSelectCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
+      case FieldType.Number:
+        return NumberCell(cellContextBuilder: cellContextBuilder, key: key);
+      case FieldType.RichText:
+        return GridTextCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
+      case FieldType.URL:
+        return GridURLCell(cellContextBuilder: cellContextBuilder, style: style, key: key);
+    }
+    throw UnimplementedError;
+  }
+}
+
+GridCellWidget buildGridCellWidget(
+  GridCell gridCell, {
+  required GridCellsCache cellCache,
+  required GridFieldCache fieldCache,
+  GridCellStyle? style,
+}) {
   final key = ValueKey(gridCell.cellId());
 
-  final cellContextBuilder = GridCellContextBuilder(gridCell: gridCell, cellCache: cellCache);
+  final cellContextBuilder = GridCellContextBuilder(
+    gridCell: gridCell,
+    cellCache: cellCache,
+    fieldCache: fieldCache,
+  );
 
   switch (gridCell.field.fieldType) {
     case FieldType.Checkbox:

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

@@ -10,19 +10,25 @@ import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:provider/provider.dart';
-import 'row_action_sheet.dart';
 
+import 'row_action_sheet.dart';
 import 'row_detail.dart';
 
 class GridRowWidget extends StatefulWidget {
   final GridRow rowData;
-  final GridRowCacheService rowCache;
+  final GridRowsCache rowCache;
+  final GridCellBuilder cellBuilder;
 
-  const GridRowWidget({
+  GridRowWidget({
     required this.rowData,
     required this.rowCache,
+    required GridFieldCache fieldCache,
     Key? key,
-  }) : super(key: key);
+  })  : cellBuilder = GridCellBuilder(
+          cellCache: rowCache.cellCache,
+          fieldCache: fieldCache,
+        ),
+        super(key: key);
 
   @override
   State<GridRowWidget> createState() => _GridRowWidgetState();
@@ -52,7 +58,11 @@ class _GridRowWidgetState extends State<GridRowWidget> {
             return Row(
               children: [
                 const _RowLeading(),
-                Expanded(child: _RowCells(cellCache: widget.rowCache.cellCache, onExpand: () => _expandRow(context))),
+                Expanded(
+                    child: _RowCells(
+                  builder: widget.cellBuilder,
+                  onExpand: () => _expandRow(context),
+                )),
                 const _RowTrailing(),
               ],
             );
@@ -72,6 +82,7 @@ class _GridRowWidgetState extends State<GridRowWidget> {
     final page = RowDetailPage(
       rowData: widget.rowData,
       rowCache: widget.rowCache,
+      cellBuilder: widget.cellBuilder,
     );
     page.show(context);
   }
@@ -146,9 +157,13 @@ class _DeleteRowButton extends StatelessWidget {
 }
 
 class _RowCells extends StatelessWidget {
-  final GridCellCacheService cellCache;
   final VoidCallback onExpand;
-  const _RowCells({required this.cellCache, required this.onExpand, Key? key}) : super(key: key);
+  final GridCellBuilder builder;
+  const _RowCells({
+    required this.builder,
+    required this.onExpand,
+    Key? key,
+  }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
@@ -169,8 +184,7 @@ class _RowCells extends StatelessWidget {
   List<Widget> _makeCells(BuildContext context, GridCellMap gridCellMap) {
     return gridCellMap.values.map(
       (gridCell) {
-        final GridCellWidget child = buildGridCellWidget(gridCell, cellCache);
-
+        final GridCellWidget child = builder.build(gridCell);
         accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
           final builder = child.accessoryBuilder;
           List<GridCellAccessory> accessories = [];

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

@@ -22,11 +22,13 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 
 class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
   final GridRow rowData;
-  final GridRowCacheService rowCache;
+  final GridRowsCache rowCache;
+  final GridCellBuilder cellBuilder;
 
   const RowDetailPage({
     required this.rowData,
     required this.rowCache,
+    required this.cellBuilder,
     Key? key,
   }) : super(key: key);
 
@@ -74,7 +76,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
                 children: const [Spacer(), _CloseButton()],
               ),
             ),
-            Expanded(child: _PropertyList(cellCache: widget.rowCache.cellCache)),
+            Expanded(child: _PropertyList(cellBuilder: widget.cellBuilder)),
           ],
         ),
       ),
@@ -98,10 +100,10 @@ class _CloseButton extends StatelessWidget {
 }
 
 class _PropertyList extends StatelessWidget {
-  final GridCellCacheService cellCache;
+  final GridCellBuilder cellBuilder;
   final ScrollController _scrollController;
   _PropertyList({
-    required this.cellCache,
+    required this.cellBuilder,
     Key? key,
   })  : _scrollController = ScrollController(),
         super(key: key);
@@ -121,7 +123,7 @@ class _PropertyList extends StatelessWidget {
             itemBuilder: (BuildContext context, int index) {
               return _RowDetailCell(
                 gridCell: state.gridCells[index],
-                cellCache: cellCache,
+                cellBuilder: cellBuilder,
               );
             },
             separatorBuilder: (BuildContext context, int index) {
@@ -136,10 +138,10 @@ class _PropertyList extends StatelessWidget {
 
 class _RowDetailCell extends StatelessWidget {
   final GridCell gridCell;
-  final GridCellCacheService cellCache;
+  final GridCellBuilder cellBuilder;
   const _RowDetailCell({
     required this.gridCell,
-    required this.cellCache,
+    required this.cellBuilder,
     Key? key,
   }) : super(key: key);
 
@@ -147,7 +149,7 @@ class _RowDetailCell extends StatelessWidget {
   Widget build(BuildContext context) {
     final theme = context.watch<AppTheme>();
     final style = _customCellStyle(theme, gridCell.field.fieldType);
-    final cell = buildGridCellWidget(gridCell, cellCache, style: style);
+    final cell = cellBuilder.build(gridCell, style: style);
 
     final gesture = GestureDetector(
       behavior: HitTestBehavior.translucent,