chore: remove single field listener (#5113)

This commit is contained in:
Richard Shiue 2024-04-11 13:43:36 +08:00 committed by GitHub
parent 3de2a20278
commit 6e3c731162
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 109 additions and 100 deletions

View File

@ -322,6 +322,7 @@ class MobileRowDetailPageContentState
BlocProvider<RowBannerBloc>( BlocProvider<RowBannerBloc>(
create: (context) => RowBannerBloc( create: (context) => RowBannerBloc(
viewId: viewId, viewId: viewId,
fieldController: fieldController,
rowMeta: rowController.rowMeta, rowMeta: rowController.rowMeta,
)..add(const RowBannerEvent.initial()), )..add(const RowBannerEvent.initial()),
child: BlocBuilder<RowBannerBloc, RowBannerState>( child: BlocBuilder<RowBannerBloc, RowBannerState>(

View File

@ -62,7 +62,7 @@ class _QuickEditFieldState extends State<QuickEditField> {
viewId: widget.viewId, viewId: widget.viewId,
fieldController: widget.fieldController, fieldController: widget.fieldController,
field: widget.fieldInfo.field, field: widget.fieldInfo.field,
)..add(const FieldEditorEvent.initial()), ),
child: BlocConsumer<FieldEditorBloc, FieldEditorState>( child: BlocConsumer<FieldEditorBloc, FieldEditorState>(
listenWhen: (previous, current) => listenWhen: (previous, current) =>
previous.field.name != current.field.name, previous.field.name != current.field.name,

View File

@ -87,15 +87,11 @@ class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
} }
}, },
onCellFieldChanged: (field) { onCellFieldChanged: (field) {
// hack: SingleFieldListener receives notification before
// FieldController's copy is updated.
Future.delayed(const Duration(milliseconds: 50), () {
if (!isClosed) { if (!isClosed) {
final RelationTypeOptionPB typeOption = final RelationTypeOptionPB typeOption =
cellController.getTypeOption(RelationTypeOptionDataParser()); cellController.getTypeOption(RelationTypeOptionDataParser());
add(RelationCellEvent.didUpdateRelationTypeOption(typeOption)); add(RelationCellEvent.didUpdateRelationTypeOption(typeOption));
} }
});
}, },
); );
} }

View File

@ -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_controller.dart';
import 'package:appflowy/plugins/database/application/field/field_info.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/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/field/type_option/type_option_data_parser.dart';
import 'package:appflowy/plugins/database/application/row/row_cache.dart'; import 'package:appflowy/plugins/database/application/row/row_cache.dart';
import 'package:appflowy/plugins/database/domain/row_meta_listener.dart'; import 'package:appflowy/plugins/database/domain/row_meta_listener.dart';
@ -49,7 +48,6 @@ class CellController<T, D> {
_rowCache = rowCache, _rowCache = rowCache,
_cellDataLoader = cellDataLoader, _cellDataLoader = cellDataLoader,
_cellDataPersistence = cellDataPersistence, _cellDataPersistence = cellDataPersistence,
_fieldListener = SingleFieldListener(fieldId: cellContext.fieldId),
_cellDataNotifier = _cellDataNotifier =
CellDataNotifier(value: rowCache.cellCache.get(cellContext)) { CellDataNotifier(value: rowCache.cellCache.get(cellContext)) {
_startListening(); _startListening();
@ -64,10 +62,9 @@ class CellController<T, D> {
CellListener? _cellListener; CellListener? _cellListener;
RowMetaListener? _rowMetaListener; RowMetaListener? _rowMetaListener;
SingleFieldListener? _fieldListener;
CellDataNotifier<T?>? _cellDataNotifier; CellDataNotifier<T?>? _cellDataNotifier;
void Function(FieldPB field)? _onCellFieldChanged; void Function(FieldInfo field)? _onCellFieldChanged;
VoidCallback? _onRowMetaChanged; VoidCallback? _onRowMetaChanged;
Timer? _loadDataOperation; Timer? _loadDataOperation;
Timer? _saveDataOperation; Timer? _saveDataOperation;
@ -105,8 +102,9 @@ class CellController<T, D> {
); );
// 2. Listen on the field event and load the cell data if needed. // 2. Listen on the field event and load the cell data if needed.
_fieldListener?.start( _fieldController.addSingleFieldListener(
onFieldChanged: (fieldPB) { fieldId,
onFieldChanged: (fieldInfo) {
// reloadOnFieldChanged should be true if you want to reload the cell // reloadOnFieldChanged should be true if you want to reload the cell
// data when the corresponding field is changed. // data when the corresponding field is changed.
// For example: // For example:
@ -114,7 +112,7 @@ class CellController<T, D> {
if (_cellDataLoader.reloadOnFieldChange) { if (_cellDataLoader.reloadOnFieldChange) {
_loadData(); _loadData();
} }
_onCellFieldChanged?.call(fieldPB); _onCellFieldChanged?.call(fieldInfo);
}, },
); );
@ -132,7 +130,7 @@ class CellController<T, D> {
/// Add a new listener /// Add a new listener
VoidCallback? addListener({ VoidCallback? addListener({
required void Function(T?) onCellChanged, required void Function(T?) onCellChanged,
void Function(FieldPB field)? onCellFieldChanged, void Function(FieldInfo field)? onCellFieldChanged,
VoidCallback? onRowMetaChanged, VoidCallback? onRowMetaChanged,
}) { }) {
_onCellFieldChanged = onCellFieldChanged; _onCellFieldChanged = onCellFieldChanged;
@ -220,9 +218,6 @@ class CellController<T, D> {
await _cellListener?.stop(); await _cellListener?.stop();
_cellListener = null; _cellListener = null;
await _fieldListener?.stop();
_fieldListener = null;
_loadDataOperation?.cancel(); _loadDataOperation?.cancel();
_saveDataOperation?.cancel(); _saveDataOperation?.cancel();
_cellDataNotifier?.dispose(); _cellDataNotifier?.dispose();

View File

@ -69,6 +69,7 @@ class _GridSortNotifier extends ChangeNotifier {
} }
typedef OnReceiveUpdateFields = void Function(List<FieldInfo>); typedef OnReceiveUpdateFields = void Function(List<FieldInfo>);
typedef OnReceiveField = void Function(FieldInfo);
typedef OnReceiveFields = void Function(List<FieldInfo>); typedef OnReceiveFields = void Function(List<FieldInfo>);
typedef OnReceiveFilters = void Function(List<FilterInfo>); typedef OnReceiveFilters = void Function(List<FilterInfo>);
typedef OnReceiveSorts = void Function(List<SortInfo>); typedef OnReceiveSorts = void Function(List<SortInfo>);
@ -686,6 +687,31 @@ class FieldController {
} }
} }
void addSingleFieldListener(
String fieldId, {
required OnReceiveField onFieldChanged,
bool Function()? listenWhen,
}) {
void key(List<FieldInfo> 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({ void removeListener({
OnReceiveFields? onFieldsListener, OnReceiveFields? onFieldsListener,
OnReceiveSorts? onSortsListener, OnReceiveSorts? onSortsListener,
@ -713,6 +739,25 @@ class FieldController {
} }
} }
void removeSingleFieldListener({
required String fieldId,
required OnReceiveField onFieldChanged,
}) {
void key(List<FieldInfo> 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 /// Stop listeners, dispose notifiers and clear listener callbacks
Future<void> dispose() async { Future<void> dispose() async {
if (_isDisposed) { if (_isDisposed) {

View File

@ -1,6 +1,5 @@
import 'dart:typed_data'; 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_service.dart';
import 'package:appflowy/plugins/database/domain/field_settings_service.dart'; import 'package:appflowy/plugins/database/domain/field_settings_service.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
@ -22,7 +21,6 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
this.onFieldInserted, this.onFieldInserted,
required FieldPB field, required FieldPB field,
}) : fieldId = field.id, }) : fieldId = field.id,
_singleFieldListener = SingleFieldListener(fieldId: field.id),
fieldService = FieldBackendService( fieldService = FieldBackendService(
viewId: viewId, viewId: viewId,
fieldId: field.id, fieldId: field.id,
@ -30,19 +28,25 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
fieldSettingsService = FieldSettingsBackendService(viewId: viewId), fieldSettingsService = FieldSettingsBackendService(viewId: viewId),
super(FieldEditorState(field: FieldInfo.initial(field))) { super(FieldEditorState(field: FieldInfo.initial(field))) {
_dispatch(); _dispatch();
_startListening();
_init();
} }
final String viewId; final String viewId;
final String fieldId; final String fieldId;
final FieldController fieldController; final FieldController fieldController;
final SingleFieldListener _singleFieldListener;
final FieldBackendService fieldService; final FieldBackendService fieldService;
final FieldSettingsBackendService fieldSettingsService; final FieldSettingsBackendService fieldSettingsService;
final void Function(String newFieldId)? onFieldInserted; final void Function(String newFieldId)? onFieldInserted;
late final OnReceiveField _listener;
@override @override
Future<void> close() { Future<void> close() {
_singleFieldListener.stop(); fieldController.removeSingleFieldListener(
fieldId: fieldId,
onFieldChanged: _listener,
);
return super.close(); return super.close();
} }
@ -50,19 +54,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
on<FieldEditorEvent>( on<FieldEditorEvent>(
(event, emit) async { (event, emit) async {
await event.when( await event.when(
initial: () async { didUpdateField: (fieldInfo) {
_singleFieldListener.start( emit(state.copyWith(field: fieldInfo));
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)!));
}, },
switchFieldType: (fieldType) async { switchFieldType: (fieldType) async {
await fieldService.updateType(fieldType: fieldType); await fieldService.updateType(fieldType: fieldType);
@ -111,6 +104,28 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
); );
} }
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<void, FlowyError> result) { void _logIfError(FlowyResult<void, FlowyError> result) {
result.fold( result.fold(
(l) => null, (l) => null,
@ -121,9 +136,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
@freezed @freezed
class FieldEditorEvent with _$FieldEditorEvent { class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.initial() = _InitialField; const factory FieldEditorEvent.didUpdateField(final FieldInfo fieldInfo) =
const factory FieldEditorEvent.didReceiveFieldChanged(final String fieldId) = _DidUpdateField;
_DidReceiveFieldChanged;
const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) = const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) =
_SwitchFieldType; _SwitchFieldType;
const factory FieldEditorEvent.updateTypeOption( const factory FieldEditorEvent.updateTypeOption(

View File

@ -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/domain/field_service.dart';
import 'package:appflowy/plugins/database/application/row/row_service.dart'; import 'package:appflowy/plugins/database/application/row/row_service.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
@ -13,6 +13,7 @@ part 'row_banner_bloc.freezed.dart';
class RowBannerBloc extends Bloc<RowBannerEvent, RowBannerState> { class RowBannerBloc extends Bloc<RowBannerEvent, RowBannerState> {
RowBannerBloc({ RowBannerBloc({
required this.viewId, required this.viewId,
required this.fieldController,
required RowMetaPB rowMeta, required RowMetaPB rowMeta,
}) : _rowBackendSvc = RowBackendService(viewId: viewId), }) : _rowBackendSvc = RowBackendService(viewId: viewId),
_metaListener = RowMetaListener(rowMeta.id), _metaListener = RowMetaListener(rowMeta.id),
@ -21,16 +22,13 @@ class RowBannerBloc extends Bloc<RowBannerEvent, RowBannerState> {
} }
final String viewId; final String viewId;
final FieldController fieldController;
final RowBackendService _rowBackendSvc; final RowBackendService _rowBackendSvc;
final RowMetaListener _metaListener; final RowMetaListener _metaListener;
SingleFieldListener? _fieldListener;
@override @override
Future<void> close() async { Future<void> close() async {
await _metaListener.stop(); await _metaListener.stop();
await _fieldListener?.stop();
_fieldListener = null;
return super.close(); return super.close();
} }
@ -66,11 +64,11 @@ class RowBannerBloc extends Bloc<RowBannerEvent, RowBannerState> {
fieldOrError.fold( fieldOrError.fold(
(primaryField) { (primaryField) {
if (!isClosed) { if (!isClosed) {
_fieldListener = SingleFieldListener(fieldId: primaryField.id); fieldController.addSingleFieldListener(
_fieldListener?.start( primaryField.id,
onFieldChanged: (updatedField) { onFieldChanged: (updatedField) {
if (!isClosed) { if (!isClosed) {
add(RowBannerEvent.didReceiveFieldUpdate(updatedField)); add(RowBannerEvent.didReceiveFieldUpdate(updatedField.field));
} }
}, },
); );

View File

@ -2,54 +2,11 @@ import 'dart:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:appflowy/core/notification/grid_notification.dart'; 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-database2/protobuf.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_result/appflowy_result.dart'; import 'package:appflowy_result/appflowy_result.dart';
import 'package:flowy_infra/notifier.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<Uint8List, FlowyError> result,
) {
switch (ty) {
case DatabaseNotification.DidUpdateField:
result.fold(
(payload) => _updateFieldNotifier?.call(FieldPB.fromBuffer(payload)),
(error) => Log.error(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_updateFieldNotifier = null;
}
}
typedef UpdateFieldsNotifiedValue typedef UpdateFieldsNotifiedValue
= FlowyResult<DatabaseFieldChangesetPB, FlowyError>; = FlowyResult<DatabaseFieldChangesetPB, FlowyError>;

View File

@ -72,7 +72,7 @@ class _FieldEditorState extends State<FieldEditor> {
field: widget.field, field: widget.field,
fieldController: widget.fieldController, fieldController: widget.fieldController,
onFieldInserted: widget.onFieldInserted, onFieldInserted: widget.onFieldInserted,
)..add(const FieldEditorEvent.initial()), ),
child: _currentPage == FieldEditorPage.details child: _currentPage == FieldEditorPage.details
? _fieldDetails() ? _fieldDetails()
: _fieldGeneral(), : _fieldGeneral(),

View File

@ -1,6 +1,7 @@
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.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/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_banner_bloc.dart';
import 'package:appflowy/plugins/database/application/row/row_controller.dart'; import 'package:appflowy/plugins/database/application/row/row_controller.dart';
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart'; import 'package:appflowy/plugins/database/widgets/cell/editable_cell_builder.dart';
@ -21,10 +22,12 @@ const _kBannerActionHeight = 40.0;
class RowBanner extends StatefulWidget { class RowBanner extends StatefulWidget {
const RowBanner({ const RowBanner({
super.key, super.key,
required this.fieldController,
required this.rowController, required this.rowController,
required this.cellBuilder, required this.cellBuilder,
}); });
final FieldController fieldController;
final RowController rowController; final RowController rowController;
final EditableCellBuilder cellBuilder; final EditableCellBuilder cellBuilder;
@ -47,6 +50,7 @@ class _RowBannerState extends State<RowBanner> {
return BlocProvider<RowBannerBloc>( return BlocProvider<RowBannerBloc>(
create: (context) => RowBannerBloc( create: (context) => RowBannerBloc(
viewId: widget.rowController.viewId, viewId: widget.rowController.viewId,
fieldController: widget.fieldController,
rowMeta: widget.rowController.rowMeta, rowMeta: widget.rowController.rowMeta,
)..add(const RowBannerEvent.initial()), )..add(const RowBannerEvent.initial()),
child: MouseRegion( child: MouseRegion(

View File

@ -57,6 +57,7 @@ class _RowDetailPageState extends State<RowDetailPage> {
controller: scrollController, controller: scrollController,
children: [ children: [
RowBanner( RowBanner(
fieldController: widget.databaseController.fieldController,
rowController: widget.rowController, rowController: widget.rowController,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
), ),

View File

@ -38,7 +38,7 @@ void main() {
viewId: context.gridView.id, viewId: context.gridView.id,
field: fieldInfo.field, field: fieldInfo.field,
fieldController: context.fieldController, fieldController: context.fieldController,
)..add(const FieldEditorEvent.initial()); );
await boardResponseFuture(); await boardResponseFuture();
editorBloc.add(const FieldEditorEvent.renameField('Hello world')); editorBloc.add(const FieldEditorEvent.renameField('Hello world'));

View File

@ -18,7 +18,7 @@ void main() {
final fieldInfo = context.singleSelectFieldContext(); final fieldInfo = context.singleSelectFieldContext();
editorBloc = context.makeFieldEditor( editorBloc = context.makeFieldEditor(
fieldInfo: fieldInfo, fieldInfo: fieldInfo,
)..add(const FieldEditorEvent.initial()); );
await boardResponseFuture(); await boardResponseFuture();
}); });

View File

@ -96,8 +96,7 @@ class BoardTestContext {
Future<FieldEditorBloc> createField(FieldType fieldType) async { Future<FieldEditorBloc> createField(FieldType fieldType) async {
final editorBloc = final editorBloc =
await createFieldEditor(databaseController: _boardDataController) await createFieldEditor(databaseController: _boardDataController);
..add(const FieldEditorEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); editorBloc.add(FieldEditorEvent.switchFieldType(fieldType));
await gridResponseFuture(); await gridResponseFuture();

View File

@ -11,7 +11,7 @@ Future<FieldEditorBloc> createEditorBloc(AppFlowyGridTest gridTest) async {
viewId: context.gridView.id, viewId: context.gridView.id,
fieldController: context.fieldController, fieldController: context.fieldController,
field: fieldInfo.field, field: fieldInfo.field,
)..add(const FieldEditorEvent.initial()); );
} }
void main() { void main() {

View File

@ -31,8 +31,7 @@ class GridTestContext {
Future<FieldEditorBloc> createField(FieldType fieldType) async { Future<FieldEditorBloc> createField(FieldType fieldType) async {
final editorBloc = final editorBloc =
await createFieldEditor(databaseController: gridController) await createFieldEditor(databaseController: gridController);
..add(const FieldEditorEvent.initial());
await gridResponseFuture(); await gridResponseFuture();
editorBloc.add(FieldEditorEvent.switchFieldType(fieldType)); editorBloc.add(FieldEditorEvent.switchFieldType(fieldType));
await gridResponseFuture(); await gridResponseFuture();