diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart index 0e462c641d..8249b22a5f 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/card/card_detail/mobile_card_detail_screen.dart @@ -322,6 +322,7 @@ class MobileRowDetailPageContentState BlocProvider( create: (context) => RowBannerBloc( viewId: viewId, + fieldController: fieldController, rowMeta: rowController.rowMeta, )..add(const RowBannerEvent.initial()), child: BlocBuilder( diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart index a1676d6590..10a6916373 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/database/field/mobile_quick_field_editor.dart @@ -62,7 +62,7 @@ class _QuickEditFieldState extends State { viewId: widget.viewId, fieldController: widget.fieldController, field: widget.fieldInfo.field, - )..add(const FieldEditorEvent.initial()), + ), child: BlocConsumer( listenWhen: (previous, current) => previous.field.name != current.field.name, diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart index 39528a97c2..ac3e59f848 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/relation_cell_bloc.dart @@ -87,15 +87,11 @@ class RelationCellBloc extends Bloc { } }, onCellFieldChanged: (field) { - // hack: SingleFieldListener receives notification before - // FieldController's copy is updated. - Future.delayed(const Duration(milliseconds: 50), () { - if (!isClosed) { - final RelationTypeOptionPB typeOption = - cellController.getTypeOption(RelationTypeOptionDataParser()); - add(RelationCellEvent.didUpdateRelationTypeOption(typeOption)); - } - }); + if (!isClosed) { + final RelationTypeOptionPB typeOption = + cellController.getTypeOption(RelationTypeOptionDataParser()); + add(RelationCellEvent.didUpdateRelationTypeOption(typeOption)); + } }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart index 10c37b57ed..98361cd97a 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/cell_controller.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/field/field_info.dart'; import 'package:appflowy/plugins/database/domain/cell_listener.dart'; -import 'package:appflowy/plugins/database/domain/field_listener.dart'; import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart'; import 'package:appflowy/plugins/database/domain/row_meta_listener.dart'; @@ -49,7 +48,6 @@ class CellController { _rowCache = rowCache, _cellDataLoader = cellDataLoader, _cellDataPersistence = cellDataPersistence, - _fieldListener = SingleFieldListener(fieldId: cellContext.fieldId), _cellDataNotifier = CellDataNotifier(value: rowCache.cellCache.get(cellContext)) { _startListening(); @@ -64,10 +62,9 @@ class CellController { CellListener? _cellListener; RowMetaListener? _rowMetaListener; - SingleFieldListener? _fieldListener; CellDataNotifier? _cellDataNotifier; - void Function(FieldPB field)? _onCellFieldChanged; + void Function(FieldInfo field)? _onCellFieldChanged; VoidCallback? _onRowMetaChanged; Timer? _loadDataOperation; Timer? _saveDataOperation; @@ -105,8 +102,9 @@ class CellController { ); // 2. Listen on the field event and load the cell data if needed. - _fieldListener?.start( - onFieldChanged: (fieldPB) { + _fieldController.addSingleFieldListener( + fieldId, + onFieldChanged: (fieldInfo) { // reloadOnFieldChanged should be true if you want to reload the cell // data when the corresponding field is changed. // For example: @@ -114,7 +112,7 @@ class CellController { if (_cellDataLoader.reloadOnFieldChange) { _loadData(); } - _onCellFieldChanged?.call(fieldPB); + _onCellFieldChanged?.call(fieldInfo); }, ); @@ -132,7 +130,7 @@ class CellController { /// Add a new listener VoidCallback? addListener({ required void Function(T?) onCellChanged, - void Function(FieldPB field)? onCellFieldChanged, + void Function(FieldInfo field)? onCellFieldChanged, VoidCallback? onRowMetaChanged, }) { _onCellFieldChanged = onCellFieldChanged; @@ -220,9 +218,6 @@ class CellController { await _cellListener?.stop(); _cellListener = null; - await _fieldListener?.stop(); - _fieldListener = null; - _loadDataOperation?.cancel(); _saveDataOperation?.cancel(); _cellDataNotifier?.dispose(); diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart index db1a56071e..1b9340d93c 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart @@ -69,6 +69,7 @@ class _GridSortNotifier extends ChangeNotifier { } typedef OnReceiveUpdateFields = void Function(List); +typedef OnReceiveField = void Function(FieldInfo); typedef OnReceiveFields = void Function(List); typedef OnReceiveFilters = void Function(List); typedef OnReceiveSorts = void Function(List); @@ -686,6 +687,31 @@ class FieldController { } } + void addSingleFieldListener( + String fieldId, { + required OnReceiveField onFieldChanged, + bool Function()? listenWhen, + }) { + void key(List fieldInfos) { + final fieldInfo = fieldInfos.firstWhereOrNull( + (fieldInfo) => fieldInfo.id == fieldId, + ); + if (fieldInfo != null) { + onFieldChanged(fieldInfo); + } + } + + void callback() { + if (listenWhen != null && listenWhen() == false) { + return; + } + key(fieldInfos); + } + + _fieldCallbacks[key] = callback; + _fieldNotifier.addListener(callback); + } + void removeListener({ OnReceiveFields? onFieldsListener, OnReceiveSorts? onSortsListener, @@ -713,6 +739,25 @@ class FieldController { } } + void removeSingleFieldListener({ + required String fieldId, + required OnReceiveField onFieldChanged, + }) { + void key(List fieldInfos) { + final fieldInfo = fieldInfos.firstWhereOrNull( + (fieldInfo) => fieldInfo.id == fieldId, + ); + if (fieldInfo != null) { + onFieldChanged(fieldInfo); + } + } + + final callback = _fieldCallbacks.remove(key); + if (callback != null) { + _fieldNotifier.removeListener(callback); + } + } + /// Stop listeners, dispose notifiers and clear listener callbacks Future dispose() async { if (_isDisposed) { diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart index 316bb50bdb..15f00e79b4 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_editor_bloc.dart @@ -1,6 +1,5 @@ import 'dart:typed_data'; -import 'package:appflowy/plugins/database/domain/field_listener.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; import 'package:appflowy/plugins/database/domain/field_settings_service.dart'; import 'package:appflowy_backend/log.dart'; @@ -22,7 +21,6 @@ class FieldEditorBloc extends Bloc { this.onFieldInserted, required FieldPB field, }) : fieldId = field.id, - _singleFieldListener = SingleFieldListener(fieldId: field.id), fieldService = FieldBackendService( viewId: viewId, fieldId: field.id, @@ -30,19 +28,25 @@ class FieldEditorBloc extends Bloc { fieldSettingsService = FieldSettingsBackendService(viewId: viewId), super(FieldEditorState(field: FieldInfo.initial(field))) { _dispatch(); + _startListening(); + _init(); } final String viewId; final String fieldId; final FieldController fieldController; - final SingleFieldListener _singleFieldListener; final FieldBackendService fieldService; final FieldSettingsBackendService fieldSettingsService; final void Function(String newFieldId)? onFieldInserted; + late final OnReceiveField _listener; + @override Future close() { - _singleFieldListener.stop(); + fieldController.removeSingleFieldListener( + fieldId: fieldId, + onFieldChanged: _listener, + ); return super.close(); } @@ -50,19 +54,8 @@ class FieldEditorBloc extends Bloc { on( (event, emit) async { await event.when( - initial: () async { - _singleFieldListener.start( - onFieldChanged: (field) { - if (!isClosed) { - add(FieldEditorEvent.didReceiveFieldChanged(fieldId)); - } - }, - ); - add(FieldEditorEvent.didReceiveFieldChanged(fieldId)); - }, - didReceiveFieldChanged: (fieldId) async { - await Future.delayed(const Duration(milliseconds: 50)); - emit(state.copyWith(field: fieldController.getField(fieldId)!)); + didUpdateField: (fieldInfo) { + emit(state.copyWith(field: fieldInfo)); }, switchFieldType: (fieldType) async { await fieldService.updateType(fieldType: fieldType); @@ -111,6 +104,28 @@ class FieldEditorBloc extends Bloc { ); } + void _startListening() { + _listener = (field) { + if (!isClosed) { + add(FieldEditorEvent.didUpdateField(field)); + } + }; + fieldController.addSingleFieldListener( + fieldId, + onFieldChanged: _listener, + ); + } + + void _init() async { + await Future.delayed(const Duration(milliseconds: 50)); + if (!isClosed) { + final field = fieldController.getField(fieldId); + if (field != null) { + add(FieldEditorEvent.didUpdateField(field)); + } + } + } + void _logIfError(FlowyResult result) { result.fold( (l) => null, @@ -121,9 +136,8 @@ class FieldEditorBloc extends Bloc { @freezed class FieldEditorEvent with _$FieldEditorEvent { - const factory FieldEditorEvent.initial() = _InitialField; - const factory FieldEditorEvent.didReceiveFieldChanged(final String fieldId) = - _DidReceiveFieldChanged; + const factory FieldEditorEvent.didUpdateField(final FieldInfo fieldInfo) = + _DidUpdateField; const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) = _SwitchFieldType; const factory FieldEditorEvent.updateTypeOption( diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_banner_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_banner_bloc.dart index e946b7146d..1f05352dc5 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/row/row_banner_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/row/row_banner_bloc.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/plugins/database/domain/field_listener.dart'; +import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/domain/field_service.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy_backend/log.dart'; @@ -13,6 +13,7 @@ part 'row_banner_bloc.freezed.dart'; class RowBannerBloc extends Bloc { RowBannerBloc({ required this.viewId, + required this.fieldController, required RowMetaPB rowMeta, }) : _rowBackendSvc = RowBackendService(viewId: viewId), _metaListener = RowMetaListener(rowMeta.id), @@ -21,16 +22,13 @@ class RowBannerBloc extends Bloc { } final String viewId; + final FieldController fieldController; final RowBackendService _rowBackendSvc; final RowMetaListener _metaListener; - SingleFieldListener? _fieldListener; @override Future close() async { await _metaListener.stop(); - await _fieldListener?.stop(); - _fieldListener = null; - return super.close(); } @@ -66,11 +64,11 @@ class RowBannerBloc extends Bloc { fieldOrError.fold( (primaryField) { if (!isClosed) { - _fieldListener = SingleFieldListener(fieldId: primaryField.id); - _fieldListener?.start( + fieldController.addSingleFieldListener( + primaryField.id, onFieldChanged: (updatedField) { if (!isClosed) { - add(RowBannerEvent.didReceiveFieldUpdate(updatedField)); + add(RowBannerEvent.didReceiveFieldUpdate(updatedField.field)); } }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/field_listener.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/field_listener.dart index f620c92931..4adbe7301b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/field_listener.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/field_listener.dart @@ -2,54 +2,11 @@ import 'dart:async'; import 'dart:typed_data'; import 'package:appflowy/core/notification/grid_notification.dart'; -import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_result/appflowy_result.dart'; import 'package:flowy_infra/notifier.dart'; -typedef UpdateFieldNotifiedValue = FieldPB; - -class SingleFieldListener { - SingleFieldListener({required this.fieldId}); - - final String fieldId; - - void Function(UpdateFieldNotifiedValue)? _updateFieldNotifier; - DatabaseNotificationListener? _listener; - - void start({ - required void Function(UpdateFieldNotifiedValue) onFieldChanged, - }) { - _updateFieldNotifier = onFieldChanged; - _listener = DatabaseNotificationListener( - objectId: fieldId, - handler: _handler, - ); - } - - void _handler( - DatabaseNotification ty, - FlowyResult result, - ) { - switch (ty) { - case DatabaseNotification.DidUpdateField: - result.fold( - (payload) => _updateFieldNotifier?.call(FieldPB.fromBuffer(payload)), - (error) => Log.error(error), - ); - break; - default: - break; - } - } - - Future stop() async { - await _listener?.stop(); - _updateFieldNotifier = null; - } -} - typedef UpdateFieldsNotifiedValue = FlowyResult; diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart index f619a17c4f..96b04e7b41 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/field/field_editor.dart @@ -72,7 +72,7 @@ class _FieldEditorState extends State { field: widget.field, fieldController: widget.fieldController, onFieldInserted: widget.onFieldInserted, - )..add(const FieldEditorEvent.initial()), + ), child: _currentPage == FieldEditorPage.details ? _fieldDetails() : _fieldGeneral(), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart index 9f620118e9..a4c9f596e3 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_banner.dart @@ -1,6 +1,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; +import 'package:appflowy/plugins/database/application/field/field_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_banner_bloc.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; @@ -21,10 +22,12 @@ const _kBannerActionHeight = 40.0; class RowBanner extends StatefulWidget { const RowBanner({ super.key, + required this.fieldController, required this.rowController, required this.cellBuilder, }); + final FieldController fieldController; final RowController rowController; final EditableCellBuilder cellBuilder; @@ -47,6 +50,7 @@ class _RowBannerState extends State { return BlocProvider( create: (context) => RowBannerBloc( viewId: widget.rowController.viewId, + fieldController: widget.fieldController, rowMeta: widget.rowController.rowMeta, )..add(const RowBannerEvent.initial()), child: MouseRegion( diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart index f5172709fe..f973007471 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_detail.dart @@ -57,6 +57,7 @@ class _RowDetailPageState extends State { controller: scrollController, children: [ RowBanner( + fieldController: widget.databaseController.fieldController, rowController: widget.rowController, cellBuilder: cellBuilder, ), diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart index 5ee6559941..37b9f5f855 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/create_or_edit_field_test.dart @@ -38,7 +38,7 @@ void main() { viewId: context.gridView.id, field: fieldInfo.field, fieldController: context.fieldController, - )..add(const FieldEditorEvent.initial()); + ); await boardResponseFuture(); editorBloc.add(const FieldEditorEvent.renameField('Hello world')); diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart index 132fac933e..b4321b5244 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/group_by_unsupport_field_test.dart @@ -18,7 +18,7 @@ void main() { final fieldInfo = context.singleSelectFieldContext(); editorBloc = context.makeFieldEditor( fieldInfo: fieldInfo, - )..add(const FieldEditorEvent.initial()); + ); await boardResponseFuture(); }); diff --git a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart index 0a5ba44d20..abff7256b2 100644 --- a/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/board_test/util.dart @@ -96,8 +96,7 @@ class BoardTestContext { Future createField(FieldType fieldType) async { final editorBloc = - await createFieldEditor(databaseController: _boardDataController) - ..add(const FieldEditorEvent.initial()); + await createFieldEditor(databaseController: _boardDataController); await gridResponseFuture(); editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); await gridResponseFuture(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart index 8e57ae21ad..32b1d603d9 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/field/edit_field_test.dart @@ -11,7 +11,7 @@ Future createEditorBloc(AppFlowyGridTest gridTest) async { viewId: context.gridView.id, fieldController: context.fieldController, field: fieldInfo.field, - )..add(const FieldEditorEvent.initial()); + ); } void main() { diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart index cd67f1b889..41e85d86dd 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/util.dart @@ -31,8 +31,7 @@ class GridTestContext { Future createField(FieldType fieldType) async { final editorBloc = - await createFieldEditor(databaseController: gridController) - ..add(const FieldEditorEvent.initial()); + await createFieldEditor(databaseController: gridController); await gridResponseFuture(); editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); await gridResponseFuture();