浏览代码

chore: lazy load cell

appflowy 3 年之前
父节点
当前提交
16598650ff
共有 50 个文件被更改,包括 621 次插入462 次删除
  1. 24 19
      frontend/app_flowy/lib/startup/deps_resolver.dart
  2. 1 1
      frontend/app_flowy/lib/startup/tasks/app_widget.dart
  3. 6 5
      frontend/app_flowy/lib/user/application/user_listener.dart
  4. 3 2
      frontend/app_flowy/lib/workspace/application/app/app_listener.dart
  5. 10 8
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart
  6. 7 2
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart
  7. 6 3
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart
  8. 5 3
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/number_cell_bloc.dart
  9. 5 3
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/selection_cell_bloc.dart
  10. 33 3
      frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart
  11. 47 12
      frontend/app_flowy/lib/workspace/application/grid/field/create_field_bloc.dart
  12. 10 57
      frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart
  13. 13 5
      frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart
  14. 64 23
      frontend/app_flowy/lib/workspace/application/grid/grid_bloc.dart
  15. 8 5
      frontend/app_flowy/lib/workspace/application/grid/grid_block_service.dart
  16. 4 3
      frontend/app_flowy/lib/workspace/application/grid/grid_listenr.dart
  17. 0 20
      frontend/app_flowy/lib/workspace/application/grid/grid_service.dart
  18. 84 36
      frontend/app_flowy/lib/workspace/application/grid/row/row_bloc.dart
  19. 45 5
      frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart
  20. 17 11
      frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart
  21. 3 2
      frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart
  22. 3 2
      frontend/app_flowy/lib/workspace/application/view/view_listener.dart
  23. 3 4
      frontend/app_flowy/lib/workspace/application/workspace/workspace_listener.dart
  24. 45 29
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart
  25. 8 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart
  26. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart
  27. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart
  28. 31 65
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart
  29. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart
  30. 6 6
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/selection_cell.dart
  31. 6 4
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart
  32. 26 17
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart
  33. 6 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart
  34. 10 8
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_tyep_switcher.dart
  35. 1 1
      frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/header.dart
  36. 5 7
      frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart
  37. 3 3
      frontend/rust-lib/flowy-grid/src/macros.rs
  38. 2 3
      frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs
  39. 8 8
      frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs
  40. 2 2
      frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs
  41. 22 22
      frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs
  42. 4 4
      frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs
  43. 3 3
      frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
  44. 1 1
      frontend/rust-lib/flowy-grid/src/util.rs
  45. 6 6
      frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs
  46. 2 2
      frontend/rust-lib/flowy-grid/tests/grid/script.rs
  47. 1 0
      frontend/rust-lib/flowy-text-block/tests/editor/mod.rs
  48. 14 14
      shared-lib/flowy-grid-data-model/src/entities/grid.rs
  49. 3 4
      shared-lib/flowy-grid-data-model/src/parser/id_parser.rs
  50. 2 7
      shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

+ 24 - 19
frontend/app_flowy/lib/startup/deps_resolver.dart

@@ -149,8 +149,8 @@ void _resolveGridDeps(GetIt getIt) {
 
   getIt.registerFactoryParam<RowBloc, GridRowData, void>(
     (data, _) => RowBloc(
-      rowService: RowService(data),
-      listener: RowListener(rowId: data.rowId),
+      rowData: data,
+      rowlistener: RowListener(rowId: data.rowId),
     ),
   );
 
@@ -174,38 +174,43 @@ void _resolveGridDeps(GetIt getIt) {
     ),
   );
 
-  getIt.registerFactoryParam<TextCellBloc, GridCellData, void>(
-    (context, _) => TextCellBloc(
-      service: CellService(context),
+  getIt.registerFactoryParam<TextCellBloc, FutureCellData, void>(
+    (cellData, _) => TextCellBloc(
+      service: CellService(),
+      cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<SelectionCellBloc, GridCellData, void>(
-    (context, _) => SelectionCellBloc(
-      service: CellService(context),
+  getIt.registerFactoryParam<SelectionCellBloc, FutureCellData, void>(
+    (cellData, _) => SelectionCellBloc(
+      service: CellService(),
+      cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<NumberCellBloc, GridCellData, void>(
-    (context, _) => NumberCellBloc(
-      service: CellService(context),
+  getIt.registerFactoryParam<NumberCellBloc, FutureCellData, void>(
+    (cellData, _) => NumberCellBloc(
+      service: CellService(),
+      cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<DateCellBloc, GridCellData, void>(
-    (context, _) => DateCellBloc(
-      service: CellService(context),
+  getIt.registerFactoryParam<DateCellBloc, FutureCellData, void>(
+    (cellData, _) => DateCellBloc(
+      service: CellService(),
+      cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<CheckboxCellBloc, GridCellData, void>(
-    (context, _) => CheckboxCellBloc(
-      service: CellService(context),
+  getIt.registerFactoryParam<CheckboxCellBloc, FutureCellData, void>(
+    (cellData, _) => CheckboxCellBloc(
+      service: CellService(),
+      cellData: cellData,
     ),
   );
 
-  getIt.registerFactoryParam<SwitchFieldTypeBloc, EditFieldContext, void>(
-    (editContext, _) => SwitchFieldTypeBloc(editContext),
+  getIt.registerFactoryParam<SwitchFieldTypeBloc, SwitchFieldContext, void>(
+    (context, _) => SwitchFieldTypeBloc(context),
   );
 
   getIt.registerFactory<SelectionTypeOptionBloc>(

+ 1 - 1
frontend/app_flowy/lib/startup/tasks/app_widget.dart

@@ -112,7 +112,7 @@ class ApplicationBlocObserver extends BlocObserver {
   // ignore: unnecessary_overrides
   void onTransition(Bloc bloc, Transition transition) {
     // Log.debug("[current]: ${transition.currentState} \n\n[next]: ${transition.nextState}");
-    //Log.debug("${transition.nextState}");
+    Log.debug("${transition.nextState}");
     super.onTransition(bloc, transition);
   }
 

+ 6 - 5
frontend/app_flowy/lib/user/application/user_listener.dart

@@ -12,7 +12,6 @@ import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-user/dart_notification.pb.dart' as user;
 import 'package:flowy_sdk/rust_stream.dart';
 
-
 typedef UserProfileUpdatedNotifierValue = Either<UserProfile, FlowyError>;
 typedef AuthNotifierValue = Either<Unit, FlowyError>;
 typedef WorkspaceUpdatedNotifierValue = Either<List<Workspace>, FlowyError>;
@@ -23,8 +22,8 @@ class UserListener {
   final authDidChangedNotifier = PublishNotifier<AuthNotifierValue>();
   final workspaceUpdatedNotifier = PublishNotifier<WorkspaceUpdatedNotifierValue>();
 
-  late FolderNotificationParser _workspaceParser;
-  late UserNotificationParser _userParser;
+  FolderNotificationParser? _workspaceParser;
+  UserNotificationParser? _userParser;
   late UserProfile _user;
   UserListener({
     required UserProfile user,
@@ -36,12 +35,14 @@ class UserListener {
     _workspaceParser = FolderNotificationParser(id: _user.token, callback: _notificationCallback);
     _userParser = UserNotificationParser(id: _user.token, callback: _userNotificationCallback);
     _subscription = RustStreamReceiver.listen((observable) {
-      _workspaceParser.parse(observable);
-      _userParser.parse(observable);
+      _workspaceParser?.parse(observable);
+      _userParser?.parse(observable);
     });
   }
 
   Future<void> stop() async {
+    _workspaceParser = null;
+    _userParser = null;
     await _subscription?.cancel();
     profileUpdatedNotifier.dispose();
     authDidChangedNotifier.dispose();

+ 3 - 2
frontend/app_flowy/lib/workspace/application/app/app_listener.dart

@@ -17,7 +17,7 @@ class AppListener {
   StreamSubscription<SubscribeObject>? _subscription;
   ViewsDidChangeCallback? _viewsChanged;
   AppDidUpdateCallback? _updated;
-  late FolderNotificationParser _parser;
+  FolderNotificationParser? _parser;
   String appId;
 
   AppListener({
@@ -28,7 +28,7 @@ class AppListener {
     _viewsChanged = viewsChanged;
     _updated = appUpdated;
     _parser = FolderNotificationParser(id: appId, callback: _bservableCallback);
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _bservableCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@@ -61,6 +61,7 @@ class AppListener {
   }
 
   Future<void> close() async {
+    _parser = null;
     await _subscription?.cancel();
     _viewsChanged = null;
     _updated = null;

+ 10 - 8
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/cell_service.dart

@@ -1,19 +1,21 @@
-import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
 
 class CellService {
-  final GridCellData context;
+  CellService();
 
-  CellService(this.context);
-
-  Future<Either<void, FlowyError>> updateCell({required String data}) {
+  Future<Either<void, FlowyError>> updateCell({
+    required String gridId,
+    required String fieldId,
+    required String rowId,
+    required String data,
+  }) {
     final payload = CellMetaChangeset.create()
-      ..gridId = context.gridId
-      ..fieldId = context.field.id
-      ..rowId = context.rowId
+      ..gridId = gridId
+      ..fieldId = fieldId
+      ..rowId = rowId
       ..data = data;
     return GridEventUpdateCell(payload).send();
   }

+ 7 - 2
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/checkbox_cell_bloc.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -8,10 +9,14 @@ part 'checkbox_cell_bloc.freezed.dart';
 
 class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
   final CellService service;
+  // final FutureCellData cellData;
 
   CheckboxCellBloc({
     required this.service,
-  }) : super(CheckboxCellState.initial(service.context.cell)) {
+    required FutureCellData cellData,
+  }) : super(CheckboxCellState.initial()) {
+    cellData.then((a) {});
+
     on<CheckboxCellEvent>(
       (event, emit) async {
         await event.map(
@@ -38,5 +43,5 @@ class CheckboxCellState with _$CheckboxCellState {
     required Cell? cell,
   }) = _CheckboxCellState;
 
-  factory CheckboxCellState.initial(Cell? cell) => CheckboxCellState(cell: cell);
+  factory CheckboxCellState.initial() => const CheckboxCellState(cell: null);
 }

+ 6 - 3
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/date_cell_bloc.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -8,10 +9,12 @@ part 'date_cell_bloc.freezed.dart';
 
 class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
   final CellService service;
+  final FutureCellData cellData;
 
   DateCellBloc({
     required this.service,
-  }) : super(DateCellState.initial(service.context.cell)) {
+    required this.cellData,
+  }) : super(DateCellState.initial()) {
     on<DateCellEvent>(
       (event, emit) async {
         await event.map(
@@ -35,8 +38,8 @@ class DateCellEvent with _$DateCellEvent {
 @freezed
 class DateCellState with _$DateCellState {
   const factory DateCellState({
-    required Cell? cell,
+    Cell? cell,
   }) = _DateCellState;
 
-  factory DateCellState.initial(Cell? cell) => DateCellState(cell: cell);
+  factory DateCellState.initial() => const DateCellState();
 }

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

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -11,7 +12,8 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
 
   NumberCellBloc({
     required this.service,
-  }) : super(NumberCellState.initial(service.context.cell)) {
+    required FutureCellData cellData,
+  }) : super(NumberCellState.initial()) {
     on<NumberCellEvent>(
       (event, emit) async {
         await event.map(
@@ -35,8 +37,8 @@ class NumberCellEvent with _$NumberCellEvent {
 @freezed
 class NumberCellState with _$NumberCellState {
   const factory NumberCellState({
-    required Cell? cell,
+    Cell? cell,
   }) = _NumberCellState;
 
-  factory NumberCellState.initial(Cell? cell) => NumberCellState(cell: cell);
+  factory NumberCellState.initial() => const NumberCellState();
 }

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

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
@@ -11,7 +12,8 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
 
   SelectionCellBloc({
     required this.service,
-  }) : super(SelectionCellState.initial(service.context.cell)) {
+    required FutureCellData cellData,
+  }) : super(SelectionCellState.initial()) {
     on<SelectionCellEvent>(
       (event, emit) async {
         await event.map(
@@ -35,8 +37,8 @@ class SelectionCellEvent with _$SelectionCellEvent {
 @freezed
 class SelectionCellState with _$SelectionCellState {
   const factory SelectionCellState({
-    required Cell? cell,
+    Cell? cell,
   }) = _SelectionCellState;
 
-  factory SelectionCellState.initial(Cell? cell) => SelectionCellState(cell: cell);
+  factory SelectionCellState.initial() => const SelectionCellState();
 }

+ 33 - 3
frontend/app_flowy/lib/workspace/application/grid/cell_bloc/text_cell_bloc.dart

@@ -1,3 +1,4 @@
+import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
@@ -10,20 +11,47 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 
   TextCellBloc({
     required this.service,
-  }) : super(TextCellState.initial(service.context.cell?.content ?? "")) {
+    required FutureCellData cellData,
+  }) : super(TextCellState.initial()) {
+    cellData.then((cellData) {
+      if (cellData != null) {
+        add(TextCellEvent.didReceiveCellData(cellData));
+      }
+    });
+
     on<TextCellEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialCell value) async {},
           updateText: (_UpdateText value) {
-            service.updateCell(data: value.text);
+            updateCellContent(value.text);
             emit(state.copyWith(content: value.text));
           },
+          didReceiveCellData: (_DidReceiveCellData value) {
+            emit(state.copyWith(
+              cellData: value.cellData,
+              content: value.cellData.cell?.content ?? "",
+            ));
+          },
         );
       },
     );
   }
 
+  void updateCellContent(String content) {
+    if (state.cellData != null) {
+      final fieldId = state.cellData!.field.id;
+      final gridId = state.cellData!.gridId;
+      final rowId = state.cellData!.rowId;
+      service.updateCell(
+        data: content,
+        fieldId: fieldId,
+        gridId: gridId,
+        rowId: rowId,
+      );
+    }
+  }
+
   @override
   Future<void> close() async {
     return super.close();
@@ -33,6 +61,7 @@ class TextCellBloc extends Bloc<TextCellEvent, TextCellState> {
 @freezed
 class TextCellEvent with _$TextCellEvent {
   const factory TextCellEvent.initial() = _InitialCell;
+  const factory TextCellEvent.didReceiveCellData(GridCellData cellData) = _DidReceiveCellData;
   const factory TextCellEvent.updateText(String text) = _UpdateText;
 }
 
@@ -40,7 +69,8 @@ class TextCellEvent with _$TextCellEvent {
 class TextCellState with _$TextCellState {
   const factory TextCellState({
     required String content,
+    GridCellData? cellData,
   }) = _TextCellState;
 
-  factory TextCellState.initial(String content) => TextCellState(content: content);
+  factory TextCellState.initial() => const TextCellState(content: "");
 }

+ 47 - 12
frontend/app_flowy/lib/workspace/application/grid/field/create_field_bloc.dart

@@ -1,3 +1,5 @@
+import 'dart:typed_data';
+
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
@@ -12,19 +14,22 @@ part 'create_field_bloc.freezed.dart';
 class CreateFieldBloc extends Bloc<CreateFieldEvent, CreateFieldState> {
   final FieldService service;
 
-  CreateFieldBloc({required this.service}) : super(CreateFieldState.initial()) {
+  CreateFieldBloc({required this.service}) : super(CreateFieldState.initial(service.gridId)) {
     on<CreateFieldEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialField value) async {
-            final result = await service.getEditFieldContext(FieldType.RichText);
-            result.fold(
-              (editContext) => emit(state.copyWith(editContext: Some(editContext))),
-              (err) => Log.error(err),
-            );
+            await _getEditFieldContext(emit);
+          },
+          updateName: (_UpdateName value) {
+            emit(state.copyWith(fieldName: value.name));
+          },
+          switchField: (_SwitchField value) {
+            emit(state.copyWith(field: Some(value.field), typeOptionData: value.typeOptionData));
+          },
+          done: (_Done value) async {
+            await _saveField(emit);
           },
-          updateName: (_UpdateName value) {},
-          done: (_Done value) {},
         );
       },
     );
@@ -34,24 +39,54 @@ class CreateFieldBloc extends Bloc<CreateFieldEvent, CreateFieldState> {
   Future<void> close() async {
     return super.close();
   }
+
+  Future<void> _saveField(Emitter<CreateFieldState> emit) async {
+    await state.field.fold(
+      () async => null,
+      (field) async {
+        final result = await service.createField(field: field, typeOptionData: state.typeOptionData);
+        result.fold((l) => null, (r) => null);
+      },
+    );
+  }
+
+  Future<void> _getEditFieldContext(Emitter<CreateFieldState> emit) async {
+    final result = await service.getEditFieldContext(FieldType.RichText);
+    result.fold(
+      (editContext) {
+        emit(state.copyWith(
+          field: Some(editContext.gridField),
+          typeOptionData: editContext.typeOptionData,
+        ));
+      },
+      (err) => Log.error(err),
+    );
+  }
 }
 
 @freezed
 class CreateFieldEvent with _$CreateFieldEvent {
   const factory CreateFieldEvent.initial() = _InitialField;
-  const factory CreateFieldEvent.updateName(String newName) = _UpdateName;
+  const factory CreateFieldEvent.updateName(String name) = _UpdateName;
+  const factory CreateFieldEvent.switchField(Field field, Uint8List typeOptionData) = _SwitchField;
   const factory CreateFieldEvent.done() = _Done;
 }
 
 @freezed
 class CreateFieldState with _$CreateFieldState {
   const factory CreateFieldState({
+    required String fieldName,
+    required String gridId,
     required String errorText,
-    required Option<EditFieldContext> editContext,
+    required Option<Field> field,
+    required List<int> typeOptionData,
   }) = _CreateFieldState;
 
-  factory CreateFieldState.initial() => CreateFieldState(
-        editContext: none(),
+  factory CreateFieldState.initial(String gridId) => CreateFieldState(
+        gridId: gridId,
+        fieldName: '',
+        field: none(),
         errorText: '',
+        typeOptionData: List<int>.empty(),
       );
 }

+ 10 - 57
frontend/app_flowy/lib/workspace/application/grid/field/field_service.dart

@@ -21,66 +21,19 @@ class FieldService {
     return GridEventCreateEditFieldContext(payload).send();
   }
 
-  Future<Either<Unit, FlowyError>> createTextField(
-    String gridId,
-    Field field,
-    RichTextTypeOption typeOption,
+  Future<Either<Unit, FlowyError>> createField({
+    required Field field,
+    List<int>? typeOptionData,
     String? startFieldId,
-  ) {
-    final typeOptionData = typeOption.writeToBuffer();
-    return createField(field, typeOptionData, startFieldId);
-  }
-
-  Future<Either<Unit, FlowyError>> createSingleSelectField(
-    String gridId,
-    Field field,
-    SingleSelectTypeOption typeOption,
-    String? startFieldId,
-  ) {
-    final typeOptionData = typeOption.writeToBuffer();
-    return createField(field, typeOptionData, startFieldId);
-  }
-
-  Future<Either<Unit, FlowyError>> createMultiSelectField(
-    String gridId,
-    Field field,
-    MultiSelectTypeOption typeOption,
-    String? startFieldId,
-  ) {
-    final typeOptionData = typeOption.writeToBuffer();
-    return createField(field, typeOptionData, startFieldId);
-  }
-
-  Future<Either<Unit, FlowyError>> createNumberField(
-    String gridId,
-    Field field,
-    NumberTypeOption typeOption,
-    String? startFieldId,
-  ) {
-    final typeOptionData = typeOption.writeToBuffer();
-    return createField(field, typeOptionData, startFieldId);
-  }
-
-  Future<Either<Unit, FlowyError>> createDateField(
-    String gridId,
-    Field field,
-    DateTypeOption typeOption,
-    String? startFieldId,
-  ) {
-    final typeOptionData = typeOption.writeToBuffer();
-    return createField(field, typeOptionData, startFieldId);
-  }
-
-  Future<Either<Unit, FlowyError>> createField(
-    Field field,
-    Uint8List? typeOptionData,
-    String? startFieldId,
-  ) {
-    final payload = CreateFieldPayload.create()
+  }) {
+    var payload = CreateFieldPayload.create()
       ..gridId = gridId
       ..field_2 = field
-      ..typeOptionData = typeOptionData ?? Uint8List.fromList([])
-      ..startFieldId = startFieldId ?? "";
+      ..typeOptionData = typeOptionData ?? [];
+
+    if (startFieldId != null) {
+      payload.startFieldId = startFieldId;
+    }
 
     return GridEventCreateField(payload).send();
   }

+ 13 - 5
frontend/app_flowy/lib/workspace/application/grid/field/switch_field_type_bloc.dart

@@ -10,7 +10,7 @@ import 'field_service.dart';
 part 'switch_field_type_bloc.freezed.dart';
 
 class SwitchFieldTypeBloc extends Bloc<SwitchFieldTypeEvent, SwitchFieldTypeState> {
-  SwitchFieldTypeBloc(EditFieldContext editContext) : super(SwitchFieldTypeState.initial(editContext)) {
+  SwitchFieldTypeBloc(SwitchFieldContext editContext) : super(SwitchFieldTypeState.initial(editContext)) {
     on<SwitchFieldTypeEvent>(
       (event, emit) async {
         await event.map(
@@ -53,9 +53,17 @@ class SwitchFieldTypeState with _$SwitchFieldTypeState {
     required Uint8List typeOptionData,
   }) = _SwitchFieldTypeState;
 
-  factory SwitchFieldTypeState.initial(EditFieldContext editContext) => SwitchFieldTypeState(
-        gridId: editContext.gridId,
-        field: editContext.gridField,
-        typeOptionData: Uint8List.fromList(editContext.typeOptionData),
+  factory SwitchFieldTypeState.initial(SwitchFieldContext switchContext) => SwitchFieldTypeState(
+        gridId: switchContext.gridId,
+        field: switchContext.field,
+        typeOptionData: Uint8List.fromList(switchContext.typeOptionData),
       );
 }
+
+class SwitchFieldContext {
+  final String gridId;
+  final Field field;
+  final List<int> typeOptionData;
+
+  SwitchFieldContext(this.gridId, this.field, this.typeOptionData);
+}

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

@@ -6,6 +6,7 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:equatable/equatable.dart';
 import 'grid_block_service.dart';
 import 'grid_listenr.dart';
 import 'grid_service.dart';
@@ -63,6 +64,20 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     await _loadGrid(emit);
   }
 
+  Future<void> _initGridBlock(Grid grid) async {
+    _blockService = GridBlockService(
+      gridId: grid.id,
+      blockOrders: grid.blockOrders,
+    );
+
+    _blockService.blocksUpdateNotifier?.addPublishListener((result) {
+      result.fold(
+        (blockMap) => add(GridEvent.rowsDidUpdate(_buildRows(blockMap))),
+        (err) => Log.error('$err'),
+      );
+    });
+  }
+
   Future<void> _loadGrid(Emitter<GridState> emit) async {
     final result = await service.openGrid(gridId: view.id);
     return Future(
@@ -78,7 +93,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     return Future(
       () => result.fold(
         (fields) {
-          _initGridBlockService(grid);
+          _initGridBlock(grid);
           emit(state.copyWith(
             grid: Some(grid),
             fields: fields.items,
@@ -90,29 +105,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
     );
   }
 
-  Future<void> _initGridBlockService(Grid grid) async {
-    _blockService = GridBlockService(
-      gridId: grid.id,
-      blockOrders: grid.blockOrders,
-    );
-
-    _blockService.blocksUpdateNotifier.addPublishListener((result) {
-      result.fold(
-        (blockMap) => add(GridEvent.rowsDidUpdate(_buildRows(blockMap))),
-        (err) => Log.error('$err'),
-      );
-    });
-
-    _gridListener.start();
-  }
-
-  List<GridRowData> _buildRows(GridBlockMap blockMap) {
-    List<GridRowData> rows = [];
+  List<GridBlockRow> _buildRows(GridBlockMap blockMap) {
+    List<GridBlockRow> rows = [];
     blockMap.forEach((_, GridBlock gridBlock) {
       rows.addAll(gridBlock.rowOrders.map(
-        (rowOrder) => GridRowData(
+        (rowOrder) => GridBlockRow(
           gridId: view.id,
-          fields: state.fields,
           blockId: gridBlock.id,
           rowId: rowOrder.rowId,
           height: rowOrder.height.toDouble(),
@@ -130,7 +128,7 @@ class GridEvent with _$GridEvent {
   const factory GridEvent.updateDesc(String gridId, String desc) = _Desc;
   const factory GridEvent.delete(String gridId) = _Delete;
   const factory GridEvent.createRow() = _CreateRow;
-  const factory GridEvent.rowsDidUpdate(List<GridRowData> rows) = _RowsDidUpdate;
+  const factory GridEvent.rowsDidUpdate(List<GridBlockRow> rows) = _RowsDidUpdate;
   const factory GridEvent.fieldsDidUpdate(List<Field> fields) = _FieldsDidUpdate;
 }
 
@@ -139,7 +137,7 @@ class GridState with _$GridState {
   const factory GridState({
     required GridLoadingState loadingState,
     required List<Field> fields,
-    required List<GridRowData> rows,
+    required List<GridBlockRow> rows,
     required Option<Grid> grid,
   }) = _GridState;
 
@@ -156,3 +154,46 @@ class GridLoadingState with _$GridLoadingState {
   const factory GridLoadingState.loading() = _Loading;
   const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
 }
+
+class GridBlockRow {
+  final String gridId;
+  final String rowId;
+  final String blockId;
+  final double height;
+
+  const GridBlockRow({
+    required this.gridId,
+    required this.rowId,
+    required this.blockId,
+    required this.height,
+  });
+}
+
+class GridRowData extends Equatable {
+  final String gridId;
+  final String rowId;
+  final String blockId;
+  final List<Field> fields;
+  final double height;
+
+  const GridRowData({
+    required this.gridId,
+    required this.rowId,
+    required this.blockId,
+    required this.fields,
+    required this.height,
+  });
+
+  factory GridRowData.fromBlockRow(GridBlockRow row, List<Field> fields) {
+    return GridRowData(
+      gridId: row.gridId,
+      rowId: row.rowId,
+      blockId: row.blockId,
+      fields: fields,
+      height: row.height,
+    );
+  }
+
+  @override
+  List<Object> get props => [rowId, fields];
+}

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

@@ -19,7 +19,7 @@ class GridBlockService {
   String gridId;
   GridBlockMap blockMap = GridBlockMap();
   late GridBlockListener _blockListener;
-  PublishNotifier<BlocksUpdateNotifierValue> blocksUpdateNotifier = PublishNotifier();
+  PublishNotifier<BlocksUpdateNotifierValue>? blocksUpdateNotifier = PublishNotifier();
 
   GridBlockService({required this.gridId, required List<GridBlockOrder> blockOrders}) {
     _loadGridBlocks(blockOrders);
@@ -36,6 +36,8 @@ class GridBlockService {
 
   Future<void> stop() async {
     await _blockListener.stop();
+    blocksUpdateNotifier?.dispose();
+    blocksUpdateNotifier = null;
   }
 
   void _loadGridBlocks(List<GridBlockOrder> blockOrders) {
@@ -49,9 +51,9 @@ class GridBlockService {
           for (final gridBlock in repeatedBlocks.items) {
             blockMap[gridBlock.id] = gridBlock;
           }
-          blocksUpdateNotifier.value = left(blockMap);
+          blocksUpdateNotifier?.value = left(blockMap);
         },
-        (err) => blocksUpdateNotifier.value = right(err),
+        (err) => blocksUpdateNotifier?.value = right(err),
       );
     });
   }
@@ -61,7 +63,7 @@ class GridBlockListener {
   final String gridId;
   PublishNotifier<Either<List<GridBlockOrder>, FlowyError>> blockUpdateNotifier = PublishNotifier(comparable: null);
   StreamSubscription<SubscribeObject>? _subscription;
-  late GridNotificationParser _parser;
+  GridNotificationParser? _parser;
 
   GridBlockListener({required this.gridId});
 
@@ -73,7 +75,7 @@ class GridBlockListener {
       },
     );
 
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
@@ -91,6 +93,7 @@ class GridBlockListener {
   }
 
   Future<void> stop() async {
+    _parser = null;
     await _subscription?.cancel();
     blockUpdateNotifier.dispose();
   }

+ 4 - 3
frontend/app_flowy/lib/workspace/application/grid/grid_listenr.dart

@@ -13,9 +13,9 @@ import 'package:app_flowy/core/notification_helper.dart';
 
 class GridListener {
   final String gridId;
-  PublishNotifier<Either<List<Field>, FlowyError>> fieldsUpdateNotifier = PublishNotifier(comparable: null);
+  PublishNotifier<Either<List<Field>, FlowyError>> fieldsUpdateNotifier = PublishNotifier();
   StreamSubscription<SubscribeObject>? _subscription;
-  late GridNotificationParser _parser;
+  GridNotificationParser? _parser;
   GridListener({required this.gridId});
 
   void start() {
@@ -26,7 +26,7 @@ class GridListener {
       },
     );
 
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
@@ -43,6 +43,7 @@ class GridListener {
   }
 
   Future<void> stop() async {
+    _parser = null;
     await _subscription?.cancel();
     fieldsUpdateNotifier.dispose();
   }

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

@@ -3,7 +3,6 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 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:dartz/dartz.dart';
-import 'package:equatable/equatable.dart';
 
 class GridService {
   Future<Either<Grid, FlowyError>> openGrid({required String gridId}) async {
@@ -34,22 +33,3 @@ class GridService {
     return GridEventGetFields(payload).send();
   }
 }
-
-class GridRowData extends Equatable {
-  final String gridId;
-  final String rowId;
-  final String blockId;
-  final List<Field> fields;
-  final double height;
-
-  const GridRowData({
-    required this.gridId,
-    required this.rowId,
-    required this.blockId,
-    required this.fields,
-    required this.height,
-  });
-
-  @override
-  List<Object> get props => [rowId];
-}

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

@@ -1,4 +1,6 @@
-import 'package:app_flowy/workspace/application/grid/grid_service.dart';
+import 'dart:collection';
+
+import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
 import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
@@ -6,20 +8,34 @@ import 'package:freezed_annotation/freezed_annotation.dart';
 import 'dart:async';
 import 'row_listener.dart';
 import 'row_service.dart';
+import 'package:dartz/dartz.dart';
 
 part 'row_bloc.freezed.dart';
 
+typedef CellDataMap = HashMap<String, GridCellData>;
+
 class RowBloc extends Bloc<RowEvent, RowState> {
   final RowService rowService;
-  final RowListener listener;
+  final RowListener rowlistener;
+  final RowFieldListener fieldListener;
 
-  RowBloc({required this.rowService, required this.listener}) : super(RowState.initial(rowService.rowData)) {
+  RowBloc({required GridRowData rowData, required this.rowlistener})
+      : rowService = RowService(
+          gridId: rowData.gridId,
+          blockId: rowData.blockId,
+          rowId: rowData.rowId,
+        ),
+        fieldListener = RowFieldListener(
+          gridId: rowData.gridId,
+        ),
+        super(RowState.initial(rowData)) {
     on<RowEvent>(
       (event, emit) async {
         await event.map(
           initial: (_InitialRow value) async {
-            _startRowListening();
+            _startListening();
             await _loadRow(emit);
+            add(const RowEvent.didUpdateCell());
           },
           createRow: (_CreateRow value) {
             rowService.createRow();
@@ -30,6 +46,19 @@ class RowBloc extends Bloc<RowEvent, RowState> {
           disactiveRow: (_DisactiveRow value) {
             emit(state.copyWith(active: false));
           },
+          didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
+            emit(state.copyWith(fields: value.fields));
+            add(const RowEvent.didUpdateCell());
+          },
+          didUpdateCell: (_DidUpdateCell value) {
+            final Future<CellDataMap> cellDataMap = state.row.then(
+              (someRow) => someRow.fold(
+                () => HashMap.identity(),
+                (row) => _makeCellDatas(row),
+              ),
+            );
+            emit(state.copyWith(cellDataMap: cellDataMap));
+          },
         );
       },
     );
@@ -37,53 +66,66 @@ class RowBloc extends Bloc<RowEvent, RowState> {
 
   @override
   Future<void> close() async {
-    await listener.close();
+    await rowlistener.close();
+    await fieldListener.close();
     return super.close();
   }
 
-  Future<void> _startRowListening() async {
-    listener.updateRowNotifier.addPublishListener((result) {
-      result.fold((row) {
-        //
-      }, (err) => null);
+  Future<void> _startListening() async {
+    rowlistener.updateRowNotifier.addPublishListener((result) {
+      result.fold(
+        (row) {
+          //
+        },
+        (err) => Log.error(err),
+      );
+    });
+
+    rowlistener.updateCellNotifier.addPublishListener((result) {
+      result.fold(
+        (repeatedCell) {
+          Log.info("$repeatedCell");
+        },
+        (err) => Log.error(err),
+      );
     });
 
-    listener.updateCellNotifier.addPublishListener((result) {
-      result.fold((repeatedCvell) {
-        //
-        Log.info("$repeatedCvell");
-      }, (r) => null);
+    fieldListener.updateFieldNotifier.addPublishListener((result) {
+      result.fold(
+        (fields) => add(RowEvent.didReceiveFieldUpdate(fields)),
+        (err) => Log.error(err),
+      );
     });
 
-    listener.start();
+    rowlistener.start();
+    fieldListener.start();
   }
 
   Future<void> _loadRow(Emitter<RowState> emit) async {
-    final Future<List<GridCellData>> cellDatas = rowService.getRow().then((result) {
+    final Future<Option<Row>> row = rowService.getRow().then((result) {
       return result.fold(
-        (row) => _makeCellDatas(row),
-        (e) {
-          Log.error(e);
-          return [];
+        (row) => Some(row),
+        (err) {
+          Log.error(err);
+          return none();
         },
       );
     });
-    emit(state.copyWith(cellDatas: cellDatas));
+    emit(state.copyWith(row: row));
   }
 
-  List<GridCellData> _makeCellDatas(Row row) {
-    return rowService.rowData.fields.map((field) {
-      final cell = row.cellByFieldId[field.id];
-      final rowData = rowService.rowData;
-
-      return GridCellData(
+  CellDataMap _makeCellDatas(Row row) {
+    var map = CellDataMap.new();
+    for (final field in state.fields) {
+      map[field.id] = GridCellData(
         rowId: row.id,
-        gridId: rowData.gridId,
-        blockId: rowData.blockId,
-        cell: cell,
+        gridId: rowService.gridId,
+        blockId: rowService.blockId,
+        cell: row.cellByFieldId[field.id],
         field: field,
       );
-    }).toList();
+    }
+    return map;
   }
 }
 
@@ -93,21 +135,27 @@ class RowEvent with _$RowEvent {
   const factory RowEvent.createRow() = _CreateRow;
   const factory RowEvent.activeRow() = _ActiveRow;
   const factory RowEvent.disactiveRow() = _DisactiveRow;
+  const factory RowEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
+  const factory RowEvent.didUpdateCell() = _DidUpdateCell;
 }
 
 @freezed
 class RowState with _$RowState {
   const factory RowState({
     required String rowId,
-    required double rowHeight,
-    required Future<List<GridCellData>> cellDatas,
     required bool active,
+    required double rowHeight,
+    required List<Field> fields,
+    required Future<Option<Row>> row,
+    required Future<CellDataMap> cellDataMap,
   }) = _RowState;
 
   factory RowState.initial(GridRowData data) => RowState(
         rowId: data.rowId,
-        rowHeight: data.height,
-        cellDatas: Future(() => []),
         active: false,
+        rowHeight: data.height,
+        fields: data.fields,
+        row: Future(() => none()),
+        cellDataMap: Future(() => CellDataMap.identity()),
       );
 }

+ 45 - 5
frontend/app_flowy/lib/workspace/application/grid/row/row_listener.dart

@@ -11,13 +11,14 @@ import 'package:dartz/dartz.dart';
 
 typedef UpdateCellNotifiedValue = Either<RepeatedCell, FlowyError>;
 typedef UpdateRowNotifiedValue = Either<Row, FlowyError>;
+typedef UpdateFieldNotifiedValue = Either<List<Field>, FlowyError>;
 
 class RowListener {
   final String rowId;
-  PublishNotifier<UpdateCellNotifiedValue> updateCellNotifier = PublishNotifier<UpdateCellNotifiedValue>();
-  PublishNotifier<UpdateRowNotifiedValue> updateRowNotifier = PublishNotifier<UpdateRowNotifiedValue>();
+  PublishNotifier<UpdateCellNotifiedValue> updateCellNotifier = PublishNotifier();
+  PublishNotifier<UpdateRowNotifiedValue> updateRowNotifier = PublishNotifier();
   StreamSubscription<SubscribeObject>? _subscription;
-  late GridNotificationParser _parser;
+  GridNotificationParser? _parser;
 
   RowListener({required this.rowId});
 
@@ -29,7 +30,7 @@ class RowListener {
       },
     );
 
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
@@ -40,15 +41,54 @@ class RowListener {
           (error) => updateCellNotifier.value = right(error),
         );
         break;
-
       default:
         break;
     }
   }
 
   Future<void> close() async {
+    _parser = null;
     await _subscription?.cancel();
     updateCellNotifier.dispose();
     updateRowNotifier.dispose();
   }
 }
+
+class RowFieldListener {
+  final String gridId;
+  PublishNotifier<UpdateFieldNotifiedValue> updateFieldNotifier = PublishNotifier();
+  StreamSubscription<SubscribeObject>? _subscription;
+  GridNotificationParser? _parser;
+
+  RowFieldListener({required this.gridId});
+
+  void start() {
+    _parser = GridNotificationParser(
+      id: gridId,
+      callback: (ty, result) {
+        _handleObservableType(ty, result);
+      },
+    );
+
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
+  }
+
+  void _handleObservableType(GridNotification ty, Either<Uint8List, FlowyError> result) {
+    switch (ty) {
+      case GridNotification.DidUpdateFields:
+        result.fold(
+          (payload) => updateFieldNotifier.value = left(RepeatedField.fromBuffer(payload).items),
+          (error) => updateFieldNotifier.value = right(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> close() async {
+    _parser = null;
+    await _subscription?.cancel();
+    updateFieldNotifier.dispose();
+  }
+}

+ 17 - 11
frontend/app_flowy/lib/workspace/application/grid/row/row_service.dart

@@ -1,45 +1,51 @@
 import 'package:dartz/dartz.dart';
+import 'package:equatable/equatable.dart';
 import 'package:flowy_sdk/dispatch/dispatch.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:app_flowy/workspace/application/grid/prelude.dart';
-
 class RowService {
-  final GridRowData rowData;
+  final String gridId;
+  final String rowId;
+  final String blockId;
 
-  RowService(this.rowData);
+  RowService({required this.gridId, required this.rowId, required this.blockId});
 
   Future<Either<Row, FlowyError>> createRow() {
     CreateRowPayload payload = CreateRowPayload.create()
-      ..gridId = rowData.gridId
-      ..startRowId = rowData.rowId;
+      ..gridId = gridId
+      ..startRowId = rowId;
 
     return GridEventCreateRow(payload).send();
   }
 
   Future<Either<Row, FlowyError>> getRow() {
     QueryRowPayload payload = QueryRowPayload.create()
-      ..gridId = rowData.gridId
-      ..blockId = rowData.blockId
-      ..rowId = rowData.rowId;
+      ..gridId = gridId
+      ..blockId = blockId
+      ..rowId = rowId;
 
     return GridEventGetRow(payload).send();
   }
 }
 
-class GridCellData {
+typedef FutureCellData = Future<GridCellData?>;
+
+class GridCellData extends Equatable {
   final String gridId;
   final String rowId;
   final String blockId;
   final Field field;
   final Cell? cell;
 
-  GridCellData({
+  const GridCellData({
     required this.rowId,
     required this.gridId,
     required this.blockId,
     required this.field,
     required this.cell,
   });
+
+  @override
+  List<Object?> get props => [cell, field];
 }

+ 3 - 2
frontend/app_flowy/lib/workspace/application/trash/trash_listener.dart

@@ -13,12 +13,12 @@ typedef TrashUpdatedCallback = void Function(Either<List<Trash>, FlowyError> tra
 class TrashListener {
   StreamSubscription<SubscribeObject>? _subscription;
   TrashUpdatedCallback? _trashUpdated;
-  late FolderNotificationParser _parser;
+  FolderNotificationParser? _parser;
 
   void start({TrashUpdatedCallback? trashUpdated}) {
     _trashUpdated = trashUpdated;
     _parser = FolderNotificationParser(callback: _bservableCallback);
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _bservableCallback(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@@ -40,6 +40,7 @@ class TrashListener {
   }
 
   Future<void> close() async {
+    _parser = null;
     await _subscription?.cancel();
     _trashUpdated = null;
   }

+ 3 - 2
frontend/app_flowy/lib/workspace/application/view/view_listener.dart

@@ -18,7 +18,7 @@ class ViewListener {
   PublishNotifier<UpdateViewNotifiedValue> updatedNotifier = PublishNotifier<UpdateViewNotifiedValue>();
   PublishNotifier<DeleteViewNotifyValue> deletedNotifier = PublishNotifier<DeleteViewNotifyValue>();
   PublishNotifier<RestoreViewNotifiedValue> restoredNotifier = PublishNotifier<RestoreViewNotifiedValue>();
-  late FolderNotificationParser _parser;
+  FolderNotificationParser? _parser;
   View view;
 
   ViewListener({
@@ -33,7 +33,7 @@ class ViewListener {
       },
     );
 
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _handleObservableType(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@@ -62,6 +62,7 @@ class ViewListener {
   }
 
   Future<void> close() async {
+    _parser = null;
     await _subscription?.cancel();
     updatedNotifier.dispose();
     deletedNotifier.dispose();

+ 3 - 4
frontend/app_flowy/lib/workspace/application/workspace/workspace_listener.dart

@@ -12,7 +12,6 @@ import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-folder/dart_notification.pb.dart';
 import 'package:flowy_sdk/rust_stream.dart';
 
-
 typedef WorkspaceAppsChangedCallback = void Function(Either<List<App>, FlowyError> appsOrFail);
 typedef WorkspaceUpdatedCallback = void Function(String name, String desc);
 
@@ -31,12 +30,11 @@ class WorkspaceListener {
   }
 }
 
-
 class WorkspaceListenerService {
   StreamSubscription<SubscribeObject>? _subscription;
   WorkspaceAppsChangedCallback? _appsChanged;
   WorkspaceUpdatedCallback? _update;
-  late FolderNotificationParser _parser;
+  FolderNotificationParser? _parser;
   final UserProfile user;
   final String workspaceId;
 
@@ -59,7 +57,7 @@ class WorkspaceListenerService {
       },
     );
 
-    _subscription = RustStreamReceiver.listen((observable) => _parser.parse(observable));
+    _subscription = RustStreamReceiver.listen((observable) => _parser?.parse(observable));
   }
 
   void _handleObservableType(FolderNotification ty, Either<Uint8List, FlowyError> result) {
@@ -91,6 +89,7 @@ class WorkspaceListenerService {
   }
 
   Future<void> close() async {
+    _parser = null;
     await _subscription?.cancel();
     // _appsChanged = null;
     // _update = null;

+ 45 - 29
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/grid_page.dart

@@ -1,15 +1,13 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/grid_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';
 import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
 import 'package:flowy_infra_ui/widget/error_page.dart';
 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_bloc/flutter_bloc.dart';
 import 'package:flutter/material.dart';
-import 'package:styled_widget/styled_widget.dart';
-
 import 'controller/grid_scroll.dart';
 import 'layout/layout.dart';
 import 'layout/sizes.dart';
@@ -92,16 +90,33 @@ class _FlowyGridState extends State<FlowyGrid> {
           return const Center(child: CircularProgressIndicator.adaptive());
         }
 
-        return _wrapScrollbar(state.fields, [
-          _buildHeader(gridId, state.fields),
-          _buildRows(context),
-          const GridFooter(),
-        ]);
+        final child = BlocBuilder<GridBloc, GridState>(
+          builder: (context, state) {
+            return SizedBox(
+              width: GridLayout.headerWidth(state.fields),
+              child: ScrollConfiguration(
+                behavior: const ScrollBehavior().copyWith(scrollbars: false),
+                child: CustomScrollView(
+                  shrinkWrap: true,
+                  physics: StyledScrollPhysics(),
+                  controller: _scrollController.verticalController,
+                  slivers: [
+                    _buildHeader(gridId),
+                    _buildRows(context),
+                    const GridFooter(),
+                  ],
+                ),
+              ),
+            );
+          },
+        );
+
+        return _wrapScrollbar(child);
       },
     );
   }
 
-  Widget _wrapScrollbar(List<Field> fields, List<Widget> children) {
+  Widget _wrapScrollbar(Widget child) {
     return ScrollbarListStack(
       axis: Axis.vertical,
       controller: _scrollController.verticalController,
@@ -109,38 +124,39 @@ class _FlowyGridState extends State<FlowyGrid> {
       child: StyledSingleChildScrollView(
         controller: _scrollController.horizontalController,
         axis: Axis.horizontal,
-        child: SizedBox(
-          width: GridLayout.headerWidth(fields),
-          child: ScrollConfiguration(
-            behavior: const ScrollBehavior().copyWith(scrollbars: false),
-            child: CustomScrollView(
-              physics: StyledScrollPhysics(),
-              controller: _scrollController.verticalController,
-              slivers: <Widget>[...children],
-            ),
-          ),
-        ),
+        child: child,
       ),
-    ).padding(right: 0, top: GridSize.headerHeight, bottom: GridSize.scrollBarSize);
+    );
   }
 
-  Widget _buildHeader(String gridId, List<Field> fields) {
-    return SliverPersistentHeader(
-      delegate: GridHeaderDelegate(gridId: gridId, fields: fields),
-      floating: true,
-      pinned: true,
+  Widget _buildHeader(String gridId) {
+    return BlocBuilder<GridBloc, GridState>(
+      buildWhen: (previous, current) => previous.fields.length != current.fields.length,
+      builder: (context, state) {
+        return SliverPersistentHeader(
+          delegate: GridHeaderDelegate(gridId: gridId, fields: state.fields),
+          floating: true,
+          pinned: true,
+        );
+      },
     );
   }
 
   Widget _buildRows(BuildContext context) {
     return BlocBuilder<GridBloc, GridState>(
-      buildWhen: (previous, current) => previous.rows.length != current.rows.length,
+      buildWhen: (previous, current) {
+        final rowChanged = previous.rows.length != current.rows.length;
+        // final fieldChanged = previous.fields.length != current.fields.length;
+        return rowChanged;
+      },
       builder: (context, state) {
         return SliverList(
           delegate: SliverChildBuilderDelegate(
             (context, index) {
-              final rowData = context.read<GridBloc>().state.rows[index];
-              return GridRowWidget(data: rowData);
+              final blockRow = context.read<GridBloc>().state.rows[index];
+              final fields = context.read<GridBloc>().state.fields;
+              final rowData = GridRowData.fromBlockRow(blockRow, fields);
+              return GridRowWidget(data: rowData, key: ValueKey(rowData.rowId));
             },
             childCount: context.read<GridBloc>().state.rows.length,
             addRepaintBoundaries: true,

+ 8 - 8
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/cell_builder.dart

@@ -7,20 +7,20 @@ import 'number_cell.dart';
 import 'selection_cell.dart';
 import 'text_cell.dart';
 
-Widget buildGridCell(GridCellData cellData) {
-  switch (cellData.field.fieldType) {
+Widget buildGridCell(FieldType fieldType, FutureCellData cellData) {
+  switch (fieldType) {
     case FieldType.Checkbox:
-      return CheckboxCell(cellData: cellData);
+      return CheckboxCell(cellData: cellData, key: ObjectKey(cellData));
     case FieldType.DateTime:
-      return DateCell(cellData: cellData);
+      return DateCell(cellData: cellData, key: ObjectKey(cellData));
     case FieldType.MultiSelect:
-      return MultiSelectCell(cellContext: cellData);
+      return MultiSelectCell(cellData: cellData, key: ObjectKey(cellData));
     case FieldType.Number:
-      return NumberCell(cellData: cellData);
+      return NumberCell(cellData: cellData, key: ObjectKey(cellData));
     case FieldType.RichText:
-      return GridTextCell(cellData: cellData);
+      return GridTextCell(cellData: cellData, key: ObjectKey(cellData));
     case FieldType.SingleSelect:
-      return SingleSelectCell(cellContext: cellData);
+      return SingleSelectCell(cellData: cellData, key: ObjectKey(cellData));
     default:
       throw UnimplementedError;
   }

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/checkbox_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class CheckboxCell extends StatefulWidget {
-  final GridCellData cellData;
+  final FutureCellData cellData;
 
   const CheckboxCell({
     required this.cellData,

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/date_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class DateCell extends StatefulWidget {
-  final GridCellData cellData;
+  final FutureCellData cellData;
 
   const DateCell({
     required this.cellData,

+ 31 - 65
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/grid_row.dart

@@ -4,61 +4,38 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.d
 import 'package:flowy_infra/image.dart';
 import 'package:flowy_infra/theme.dart';
 import 'package:flowy_infra_ui/style_widget/icon_button.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'cell_builder.dart';
 import 'cell_container.dart';
 
-class GridRowWidget extends StatefulWidget {
+class GridRowWidget extends StatelessWidget {
   final GridRowData data;
-  GridRowWidget({required this.data, Key? key}) : super(key: ValueKey(data.rowId));
-
-  @override
-  State<GridRowWidget> createState() => _GridRowWidgetState();
-}
-
-class _GridRowWidgetState extends State<GridRowWidget> {
-  late RowBloc _rowBloc;
-
-  @override
-  void initState() {
-    _rowBloc = getIt<RowBloc>(param1: widget.data)..add(const RowEvent.initial());
-    super.initState();
-  }
+  const GridRowWidget({required this.data, Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return BlocProvider.value(
-      value: _rowBloc,
-      child: MouseRegion(
-        cursor: SystemMouseCursors.click,
-        onEnter: (p) => _rowBloc.add(const RowEvent.activeRow()),
-        onExit: (p) => _rowBloc.add(const RowEvent.disactiveRow()),
-        child: BlocBuilder<RowBloc, RowState>(
-          buildWhen: (p, c) => p.rowHeight != c.rowHeight,
-          builder: (context, state) {
-            return SizedBox(
-              height: _rowBloc.state.rowHeight,
-              child: Row(
-                crossAxisAlignment: CrossAxisAlignment.stretch,
-                children: const [
-                  _RowLeading(),
-                  _RowCells(),
-                  _RowTrailing(),
-                ],
-              ),
-            );
-          },
-        ),
+    return BlocProvider(
+      create: (context) => getIt<RowBloc>(param1: data)..add(const RowEvent.initial()),
+      child: BlocBuilder<RowBloc, RowState>(
+        buildWhen: (p, c) => p.rowHeight != c.rowHeight,
+        builder: (context, state) {
+          return SizedBox(
+            height: state.rowHeight,
+            child: Row(
+              crossAxisAlignment: CrossAxisAlignment.stretch,
+              children: const [
+                _RowLeading(),
+                _RowCells(),
+                _RowTrailing(),
+              ],
+            ),
+          );
+        },
       ),
     );
   }
-
-  @override
-  Future<void> dispose() async {
-    _rowBloc.close();
-    super.dispose();
-  }
 }
 
 class _RowLeading extends StatelessWidget {
@@ -115,30 +92,19 @@ class _RowCells extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     return BlocBuilder<RowBloc, RowState>(
-      buildWhen: (previous, current) => previous.cellDatas != current.cellDatas,
       builder: (context, state) {
-        return FutureBuilder(
-          future: state.cellDatas,
-          builder: builder,
-        );
+        return Row(children: [
+          ...state.fields.map(
+            (field) {
+              final cellData = state.cellDataMap.then((fut) => fut[field.id]);
+              return CellContainer(
+                width: field.width.toDouble(),
+                child: buildGridCell(field.fieldType, cellData),
+              );
+            },
+          ),
+        ]);
       },
     );
   }
-
-  Widget builder(context, AsyncSnapshot<dynamic> snapshot) {
-    switch (snapshot.connectionState) {
-      case ConnectionState.done:
-        List<GridCellData> cellDatas = snapshot.data;
-        return Row(children: cellDatas.map(_toCell).toList());
-      default:
-        return const SizedBox();
-    }
-  }
-
-  Widget _toCell(GridCellData data) {
-    return CellContainer(
-      width: data.field.width.toDouble(),
-      child: buildGridCell(data),
-    );
-  }
 }

+ 1 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/number_cell.dart

@@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
 class NumberCell extends StatefulWidget {
-  final GridCellData cellData;
+  final FutureCellData cellData;
 
   const NumberCell({
     required this.cellData,

+ 6 - 6
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/selection_cell.dart

@@ -3,10 +3,10 @@ import 'package:app_flowy/workspace/application/grid/prelude.dart';
 import 'package:flutter/material.dart';
 
 class SingleSelectCell extends StatefulWidget {
-  final GridCellData cellContext;
+  final FutureCellData cellData;
 
   const SingleSelectCell({
-    required this.cellContext,
+    required this.cellData,
     Key? key,
   }) : super(key: key);
 
@@ -19,7 +19,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext);
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData);
     super.initState();
   }
 
@@ -37,10 +37,10 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
 
 //----------------------------------------------------------------
 class MultiSelectCell extends StatefulWidget {
-  final GridCellData cellContext;
+  final FutureCellData cellData;
 
   const MultiSelectCell({
-    required this.cellContext,
+    required this.cellData,
     Key? key,
   }) : super(key: key);
 
@@ -53,7 +53,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
 
   @override
   void initState() {
-    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellContext);
+    _cellBloc = getIt<SelectionCellBloc>(param1: widget.cellData);
     super.initState();
   }
 

+ 6 - 4
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/content/text_cell.dart

@@ -8,7 +8,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
 /// The interface of base cell.
 
 class GridTextCell extends StatefulWidget {
-  final GridCellData cellData;
+  final FutureCellData cellData;
   const GridTextCell({
     required this.cellData,
     Key? key,
@@ -36,9 +36,11 @@ class _GridTextCellState extends State<GridTextCell> {
   Widget build(BuildContext context) {
     return BlocProvider.value(
       value: _cellBloc!,
-      child: BlocBuilder<TextCellBloc, TextCellState>(
-        buildWhen: (previous, current) {
-          return _controller.text != current.content;
+      child: BlocConsumer<TextCellBloc, TextCellState>(
+        listener: (context, state) {
+          if (_controller.text != state.content) {
+            _controller.text = state.content;
+          }
         },
         builder: (context, state) {
           return TextField(

+ 26 - 17
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/create_field_pannel.dart

@@ -1,10 +1,10 @@
 import 'package:app_flowy/startup/startup.dart';
 import 'package:app_flowy/workspace/application/grid/field/create_field_bloc.dart';
+import 'package:app_flowy/workspace/application/grid/field/switch_field_type_bloc.dart';
 import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flowy_infra_ui/style_widget/text.dart';
 import 'package:flowy_infra_ui/widget/spacing.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
-import 'package:flowy_sdk/protobuf/flowy-grid-data-model/meta.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'field_name_input.dart';
@@ -21,7 +21,7 @@ class CreateFieldPannel extends FlowyOverlayDelegate {
     FlowyOverlay.of(context).insertWithAnchor(
       widget: OverlayContainer(
         child: _CreateFieldPannelWidget(_createFieldBloc),
-        constraints: BoxConstraints.loose(const Size(220, 200)),
+        constraints: BoxConstraints.loose(const Size(220, 400)),
       ),
       identifier: identifier(),
       anchorContext: context,
@@ -36,7 +36,7 @@ class CreateFieldPannel extends FlowyOverlayDelegate {
   }
 
   @override
-  void didRemove() async {
+  void didRemove() {
     _createFieldBloc.add(const CreateFieldEvent.done());
   }
 }
@@ -51,16 +51,16 @@ class _CreateFieldPannelWidget extends StatelessWidget {
       value: createFieldBloc,
       child: BlocBuilder<CreateFieldBloc, CreateFieldState>(
         builder: (context, state) {
-          return state.editContext.fold(
+          return state.field.fold(
             () => const SizedBox(),
-            (editContext) => ListView(
+            (field) => ListView(
               shrinkWrap: true,
               children: [
                 const FlowyText.medium("Edit property", fontSize: 12),
                 const VSpace(10),
-                _FieldNameTextField(editContext.gridField),
+                const _FieldNameTextField(),
                 const VSpace(10),
-                _FieldTypeSwitcher(editContext),
+                _FieldTypeSwitcher(SwitchFieldContext(state.gridId, field, state.typeOptionData)),
                 const VSpace(10),
               ],
             ),
@@ -72,26 +72,35 @@ class _CreateFieldPannelWidget extends StatelessWidget {
 }
 
 class _FieldTypeSwitcher extends StatelessWidget {
-  final EditFieldContext editContext;
-  const _FieldTypeSwitcher(this.editContext, {Key? key}) : super(key: key);
+  final SwitchFieldContext switchContext;
+  const _FieldTypeSwitcher(this.switchContext, {Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return FieldTypeSwitcher(editContext: editContext);
+    return FieldTypeSwitcher(
+      switchContext: switchContext,
+      onSelected: (field, typeOptionData) {
+        context.read<CreateFieldBloc>().add(CreateFieldEvent.switchField(field, typeOptionData));
+      },
+    );
   }
 }
 
 class _FieldNameTextField extends StatelessWidget {
-  final Field field;
-  const _FieldNameTextField(this.field, {Key? key}) : super(key: key);
+  const _FieldNameTextField({Key? key}) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
-    return FieldNameTextField(
-      name: field.name,
-      errorText: context.read<CreateFieldBloc>().state.errorText,
-      onNameChanged: (newName) {
-        context.read<CreateFieldBloc>().add(CreateFieldEvent.updateName(newName));
+    return BlocBuilder<CreateFieldBloc, CreateFieldState>(
+      buildWhen: (previous, current) => previous.fieldName != current.fieldName,
+      builder: (context, state) {
+        return FieldNameTextField(
+          name: state.fieldName,
+          errorText: context.read<CreateFieldBloc>().state.errorText,
+          onNameChanged: (newName) {
+            context.read<CreateFieldBloc>().add(CreateFieldEvent.updateName(newName));
+          },
+        );
       },
     );
   }

+ 6 - 1
frontend/app_flowy/lib/workspace/presentation/plugins/grid/src/widgets/header/field_editor.dart

@@ -60,7 +60,12 @@ class _FieldTypeSwitcher extends StatelessWidget {
     return BlocBuilder<EditFieldBloc, EditFieldState>(
       builder: (context, state) {
         final editContext = context.read<EditFieldBloc>().state.editContext;
-        return FieldTypeSwitcher(editContext: editContext);
+        final switchContext = SwitchFieldContext(
+          editContext.gridId,
+          editContext.gridField,
+          editContext.typeOptionData,
+        );
+        return FieldTypeSwitcher(switchContext: switchContext, onSelected: (field, typeOptionData) {});
       },
     );
   }

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

@@ -17,20 +17,22 @@ import 'package:flowy_sdk/protobuf/flowy-grid/text_type_option.pb.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 
-typedef SelectFieldCallback = void Function(FieldType);
+typedef SelectFieldCallback = void Function(Field, Uint8List);
 
 class FieldTypeSwitcher extends StatelessWidget {
-  final EditFieldContext editContext;
+  final SwitchFieldContext switchContext;
+  final SelectFieldCallback onSelected;
 
   const FieldTypeSwitcher({
-    required this.editContext,
+    required this.switchContext,
+    required this.onSelected,
     Key? key,
   }) : super(key: key);
 
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => getIt<SwitchFieldTypeBloc>(param1: editContext),
+      create: (context) => getIt<SwitchFieldTypeBloc>(param1: switchContext),
       child: BlocBuilder<SwitchFieldTypeBloc, SwitchFieldTypeState>(
         builder: (context, state) {
           List<Widget> children = [
@@ -133,7 +135,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) => getIt<NumberTypeOptionBloc>(),
-      child: Container(),
+      child: Container(height: 30, color: Colors.green),
     );
   }
 }
@@ -157,7 +159,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) => getIt<DateTypeOptionBloc>(),
-      child: Container(),
+      child: Container(height: 80, color: Colors.red),
     );
   }
 }
@@ -194,7 +196,7 @@ class SingleSelectTypeOptionWidget extends TypeOptionWidget {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) => getIt<SelectionTypeOptionBloc>(),
-      child: Container(),
+      child: Container(height: 100, color: Colors.yellow),
     );
   }
 }
@@ -219,7 +221,7 @@ class MultiSelectTypeOptionWidget extends TypeOptionWidget {
   Widget build(BuildContext context) {
     return BlocProvider(
       create: (context) => getIt<SelectionTypeOptionBloc>(),
-      child: Container(),
+      child: Container(height: 100, color: Colors.blue),
     );
   }
 }

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

@@ -20,7 +20,7 @@ class GridHeaderDelegate extends SliverPersistentHeaderDelegate {
 
   @override
   Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
-    return GridHeader(gridId: gridId, fields: fields);
+    return GridHeader(gridId: gridId, fields: fields, key: ObjectKey(fields));
   }
 
   @override

+ 5 - 7
frontend/app_flowy/packages/flowy_infra_ui/lib/style_widget/scrolling/styled_scrollview.dart

@@ -63,13 +63,11 @@ class _StyledSingleChildScrollViewState extends State<StyledSingleChildScrollVie
       barSize: widget.barSize,
       trackColor: widget.trackColor,
       handleColor: widget.handleColor,
-      child: SizedBox.expand(
-        child: SingleChildScrollView(
-          scrollDirection: widget.axis,
-          physics: StyledScrollPhysics(),
-          controller: scrollController,
-          child: widget.child,
-        ),
+      child: SingleChildScrollView(
+        scrollDirection: widget.axis,
+        physics: StyledScrollPhysics(),
+        controller: scrollController,
+        child: widget.child,
       ),
     );
   }

+ 3 - 3
frontend/rust-lib/flowy-grid/src/macros.rs

@@ -1,9 +1,9 @@
 #[macro_export]
 macro_rules! impl_into_box_type_option_builder {
     ($target: ident) => {
-        impl std::convert::Into<BoxTypeOptionBuilder> for $target {
-            fn into(self) -> Box<dyn TypeOptionBuilder> {
-                Box::new(self)
+        impl std::convert::From<$target> for BoxTypeOptionBuilder {
+            fn from(target: $target) -> BoxTypeOptionBuilder {
+                Box::new(target)
             }
         }
     };

+ 2 - 3
frontend/rust-lib/flowy-grid/src/services/block_meta_editor.rs

@@ -6,8 +6,7 @@ use crate::dart_notification::{send_dart_notification, GridNotification};
 use dashmap::DashMap;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    FieldMeta, GridBlockId, GridBlockMeta, GridBlockMetaChangeset, GridBlockOrder, RepeatedCell, RowMeta,
-    RowMetaChangeset, RowOrder,
+    FieldMeta, GridBlockMeta, GridBlockMetaChangeset, GridBlockOrder, RepeatedCell, RowMeta, RowMetaChangeset, RowOrder,
 };
 use flowy_revision::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_revision::{
@@ -71,7 +70,7 @@ impl GridBlockMetaEditorManager {
             .insert(row_meta.id.clone(), row_meta.block_id.clone());
         let editor = self.get_editor(&row_meta.block_id).await?;
         let row_count = editor.create_row(row_meta, start_row_id).await?;
-        self.notify_block_did_update_row(&block_id).await?;
+        self.notify_block_did_update_row(block_id).await?;
         Ok(row_count)
     }
 

+ 8 - 8
frontend/rust-lib/flowy-grid/src/services/field/type_options/checkbox_type_option.rs

@@ -69,20 +69,20 @@ fn string_to_bool(bool_str: &str) -> bool {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::CheckboxTypeOption;
+    use crate::services::field::CheckboxTypeOption;
     use crate::services::row::CellDataSerde;
 
     #[test]
     fn checkout_box_description_test() {
         let type_option = CheckboxTypeOption::default();
-        assert_eq!(description.serialize_cell_data("true").unwrap(), "1".to_owned());
-        assert_eq!(description.serialize_cell_data("1").unwrap(), "1".to_owned());
-        assert_eq!(description.serialize_cell_data("yes").unwrap(), "1".to_owned());
+        assert_eq!(type_option.serialize_cell_data("true").unwrap(), "1".to_owned());
+        assert_eq!(type_option.serialize_cell_data("1").unwrap(), "1".to_owned());
+        assert_eq!(type_option.serialize_cell_data("yes").unwrap(), "1".to_owned());
 
-        assert_eq!(description.serialize_cell_data("false").unwrap(), "0".to_owned());
-        assert_eq!(description.serialize_cell_data("no").unwrap(), "0".to_owned());
-        assert_eq!(description.serialize_cell_data("123").unwrap(), "0".to_owned());
+        assert_eq!(type_option.serialize_cell_data("false").unwrap(), "0".to_owned());
+        assert_eq!(type_option.serialize_cell_data("no").unwrap(), "0".to_owned());
+        assert_eq!(type_option.serialize_cell_data("123").unwrap(), "0".to_owned());
 
-        assert_eq!(description.deserialize_cell_data("1".to_owned()), "1".to_owned());
+        assert_eq!(type_option.deserialize_cell_data("1".to_owned()), "1".to_owned());
     }
 }

+ 2 - 2
frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option.rs

@@ -177,7 +177,7 @@ impl std::default::Default for TimeFormat {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::{DateFormat, DateTypeOption, TimeFormat};
+    use crate::services::field::{DateFormat, DateTypeOption, TimeFormat};
     use crate::services::row::CellDataSerde;
     use strum::IntoEnumIterator;
 
@@ -267,6 +267,6 @@ mod tests {
     #[should_panic]
     fn date_description_invalid_data_test() {
         let type_option = DateTypeOption::default();
-        description.serialize_cell_data("he").unwrap();
+        type_option.serialize_cell_data("he").unwrap();
     }
 }

+ 22 - 22
frontend/rust-lib/flowy-grid/src/services/field/type_options/number_type_option.rs

@@ -192,41 +192,41 @@ fn make_strip_symbol() -> Vec<String> {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::{NumberFormat, NumberTypeOption};
+    use crate::services::field::{NumberFormat, NumberTypeOption};
     use crate::services::row::CellDataSerde;
     use strum::IntoEnumIterator;
 
     #[test]
     fn number_description_test() {
-        let mut description = NumberTypeOption::default();
-        assert_eq!(description.serialize_cell_data("¥18,443").unwrap(), "18443".to_owned());
-        assert_eq!(description.serialize_cell_data("$18,443").unwrap(), "18443".to_owned());
-        assert_eq!(description.serialize_cell_data("€18.443").unwrap(), "18443".to_owned());
+        let mut type_option = NumberTypeOption::default();
+        assert_eq!(type_option.serialize_cell_data("¥18,443").unwrap(), "18443".to_owned());
+        assert_eq!(type_option.serialize_cell_data("$18,443").unwrap(), "18443".to_owned());
+        assert_eq!(type_option.serialize_cell_data("€18.443").unwrap(), "18443".to_owned());
 
         for format in NumberFormat::iter() {
-            description.format = format;
+            type_option.format = format;
             match format {
                 NumberFormat::Number => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "18443".to_owned()
                     );
                 }
                 NumberFormat::USD => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "$18,443".to_owned()
                     );
                 }
                 NumberFormat::CNY => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "¥18,443".to_owned()
                     );
                 }
                 NumberFormat::EUR => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "€18.443".to_owned()
                     );
                 }
@@ -236,35 +236,35 @@ mod tests {
 
     #[test]
     fn number_description_scale_test() {
-        let mut description = NumberTypeOption {
+        let mut type_option = NumberTypeOption {
             scale: 1,
             ..Default::default()
         };
 
         for format in NumberFormat::iter() {
-            description.format = format;
+            type_option.format = format;
             match format {
                 NumberFormat::Number => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "18443".to_owned()
                     );
                 }
                 NumberFormat::USD => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "$1,844.3".to_owned()
                     );
                 }
                 NumberFormat::CNY => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "¥1,844.3".to_owned()
                     );
                 }
                 NumberFormat::EUR => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "€1.844,3".to_owned()
                     );
                 }
@@ -274,35 +274,35 @@ mod tests {
 
     #[test]
     fn number_description_sign_test() {
-        let mut description = NumberTypeOption {
+        let mut type_option = NumberTypeOption {
             sign_positive: false,
             ..Default::default()
         };
 
         for format in NumberFormat::iter() {
-            description.format = format;
+            type_option.format = format;
             match format {
                 NumberFormat::Number => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "18443".to_owned()
                     );
                 }
                 NumberFormat::USD => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "-$18,443".to_owned()
                     );
                 }
                 NumberFormat::CNY => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "-¥18,443".to_owned()
                     );
                 }
                 NumberFormat::EUR => {
                     assert_eq!(
-                        description.deserialize_cell_data("18443".to_owned()),
+                        type_option.deserialize_cell_data("18443".to_owned()),
                         "-€18.443".to_owned()
                     );
                 }

+ 4 - 4
frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option.rs

@@ -166,16 +166,16 @@ impl SelectOption {
 
 #[cfg(test)]
 mod tests {
-    use crate::services::cell::{MultiSelectDescription, SingleSelectTypeOption};
+    use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
     use crate::services::row::CellDataSerde;
 
     #[test]
     #[should_panic]
     fn selection_description_test() {
         let type_option = SingleSelectTypeOption::default();
-        assert_eq!(description.serialize_cell_data("1,2,3").unwrap(), "1".to_owned());
+        assert_eq!(type_option.serialize_cell_data("1,2,3").unwrap(), "1".to_owned());
 
-        let type_option = MultiSelectDescription::default();
-        assert_eq!(description.serialize_cell_data("1,2,3").unwrap(), "1,2,3".to_owned());
+        let type_option = MultiSelectTypeOption::default();
+        assert_eq!(type_option.serialize_cell_data("1,2,3").unwrap(), "1,2,3".to_owned());
     }
 }

+ 3 - 3
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -1,7 +1,7 @@
 use crate::dart_notification::{send_dart_notification, GridNotification};
 use crate::manager::GridUser;
 use crate::services::block_meta_editor::GridBlockMetaEditorManager;
-use crate::services::field::{default_type_option_builder_from_type, type_option_json_str_from_bytes, FieldBuilder};
+use crate::services::field::{type_option_json_str_from_bytes, FieldBuilder};
 use crate::services::row::*;
 use bytes::Bytes;
 use flowy_error::{FlowyError, FlowyResult};
@@ -78,8 +78,8 @@ impl ClientGridEditor {
     }
 
     pub async fn default_field_meta(&self, field_type: &FieldType) -> FlowyResult<FieldMeta> {
-        let name = format!("Property {}", self.pad.read().await.fields().len());
-        let field_meta = FieldBuilder::from_field_type(&field_type).name(&name).build();
+        let name = format!("Property {}", self.pad.read().await.fields().len() + 1);
+        let field_meta = FieldBuilder::from_field_type(field_type).name(&name).build();
         Ok(field_meta)
     }
 

+ 1 - 1
frontend/rust-lib/flowy-grid/src/util.rs

@@ -1,5 +1,5 @@
 use crate::services::field::*;
-use flowy_grid_data_model::entities::{BuildGridContext, FieldType};
+use flowy_grid_data_model::entities::BuildGridContext;
 use flowy_sync::client_grid::GridBuilder;
 
 pub fn make_default_grid() -> BuildGridContext {

+ 6 - 6
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -254,19 +254,19 @@ async fn grid_row_add_cells_test() {
             }
             FieldType::SingleSelect => {
                 let type_option = SingleSelectTypeOption::from(field);
-                let options = description.options.first().unwrap();
-                let data = description.serialize_cell_data(&options.id).unwrap();
+                let options = type_option.options.first().unwrap();
+                let data = type_option.serialize_cell_data(&options.id).unwrap();
                 builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::MultiSelect => {
                 let type_option = MultiSelectTypeOption::from(field);
-                let options = description
+                let options = type_option
                     .options
                     .iter()
                     .map(|option| option.id.clone())
                     .collect::<Vec<_>>()
                     .join(SELECTION_IDS_SEPARATOR);
-                let data = description.serialize_cell_data(&options).unwrap();
+                let data = type_option.serialize_cell_data(&options).unwrap();
                 builder.add_cell(&field.id, data).unwrap();
             }
             FieldType::Checkbox => {
@@ -383,11 +383,11 @@ async fn grid_cell_update() {
                     FieldType::DateTime => "123".to_string(),
                     FieldType::SingleSelect => {
                         let type_option = SingleSelectTypeOption::from(field_meta);
-                        description.options.first().unwrap().id.clone()
+                        type_option.options.first().unwrap().id.clone()
                     }
                     FieldType::MultiSelect => {
                         let type_option = MultiSelectTypeOption::from(field_meta);
-                        description.options.first().unwrap().id.clone()
+                        type_option.options.first().unwrap().id.clone()
                     }
                     FieldType::Checkbox => "1".to_string(),
                 };

+ 2 - 2
frontend/rust-lib/flowy-grid/tests/grid/script.rs

@@ -3,8 +3,8 @@ use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
 use flowy_grid::services::row::CreateRowMetaPayload;
 use flowy_grid_data_model::entities::{
-    BuildGridContext, CellMetaChangeset, CreateFieldPayload, Field, FieldChangeset, FieldMeta, FieldType,
-    GridBlockMeta, GridBlockMetaChangeset, RowMeta, RowMetaChangeset, RowOrder,
+    BuildGridContext, CellMetaChangeset, Field, FieldChangeset, FieldMeta, FieldType, GridBlockMeta,
+    GridBlockMetaChangeset, RowMeta, RowMetaChangeset, RowOrder,
 };
 use flowy_grid_data_model::parser::CreateFieldParams;
 use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;

+ 1 - 0
frontend/rust-lib/flowy-text-block/tests/editor/mod.rs

@@ -4,6 +4,7 @@ mod op_test;
 mod serde_test;
 mod undo_redo_test;
 
+use derive_more::Display;
 use flowy_sync::client_document::{ClientDocument, InitialDocumentText};
 use lib_ot::{
     core::*,

+ 14 - 14
shared-lib/flowy-grid-data-model/src/entities/grid.rs

@@ -39,20 +39,6 @@ pub struct Field {
     pub width: i32,
 }
 
-#[derive(Debug, Clone, Default, ProtoBuf)]
-pub struct FieldOrder {
-    #[pb(index = 1)]
-    pub field_id: String,
-}
-
-impl std::convert::From<&FieldMeta> for FieldOrder {
-    fn from(field_meta: &FieldMeta) -> Self {
-        Self {
-            field_id: field_meta.id.clone(),
-        }
-    }
-}
-
 impl std::convert::From<FieldMeta> for Field {
     fn from(field_meta: FieldMeta) -> Self {
         Self {
@@ -67,6 +53,20 @@ impl std::convert::From<FieldMeta> for Field {
     }
 }
 
+#[derive(Debug, Clone, Default, ProtoBuf)]
+pub struct FieldOrder {
+    #[pb(index = 1)]
+    pub field_id: String,
+}
+
+impl std::convert::From<&FieldMeta> for FieldOrder {
+    fn from(field_meta: &FieldMeta) -> Self {
+        Self {
+            field_id: field_meta.id.clone(),
+        }
+    }
+}
+
 #[derive(Debug, Default, ProtoBuf)]
 pub struct CreateEditFieldContextParams {
     #[pb(index = 1)]

+ 3 - 4
shared-lib/flowy-grid-data-model/src/parser/id_parser.rs

@@ -4,12 +4,11 @@ use uuid::Uuid;
 pub struct NotEmptyUuid(pub String);
 
 impl NotEmptyUuid {
-    pub fn parse(s: String) -> Result<NotEmptyUuid, ()> {
-        debug_assert!(Uuid::parse_str(&s).is_ok());
-
+    pub fn parse(s: String) -> Result<Self, String> {
         if s.trim().is_empty() {
-            return Err(());
+            return Err("Input string is empty".to_owned());
         }
+        debug_assert!(Uuid::parse_str(&s).is_ok());
 
         Ok(Self(s))
     }

+ 2 - 7
shared-lib/flowy-sync/src/client_grid/grid_meta_pad.rs

@@ -5,7 +5,7 @@ use bytes::Bytes;
 use flowy_grid_data_model::entities::{
     FieldChangeset, FieldMeta, FieldOrder, GridBlockMeta, GridBlockMetaChangeset, GridMeta, RepeatedFieldOrder,
 };
-use flowy_grid_data_model::parser::CreateFieldParams;
+
 use lib_infra::uuid;
 use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
 use std::collections::HashMap;
@@ -43,12 +43,7 @@ impl GridMetaPad {
     ) -> CollaborateResult<Option<GridChangeset>> {
         self.modify_grid(|grid| {
             // Check if the field exists or not
-            if grid
-                .fields
-                .iter()
-                .find(|field_meta| field_meta.id == new_field_meta.id)
-                .is_some()
-            {
+            if grid.fields.iter().any(|field_meta| field_meta.id == new_field_meta.id) {
                 tracing::warn!("Duplicate grid field");
                 return Ok(None);
             }