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>(
create: (context) => RowBannerBloc(
viewId: viewId,
fieldController: fieldController,
rowMeta: rowController.rowMeta,
)..add(const RowBannerEvent.initial()),
child: BlocBuilder<RowBannerBloc, RowBannerState>(

View File

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

View File

@ -87,15 +87,11 @@ class RelationCellBloc extends Bloc<RelationCellEvent, RelationCellState> {
}
},
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));
}
});
},
);
}

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_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<T, D> {
_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<T, D> {
CellListener? _cellListener;
RowMetaListener? _rowMetaListener;
SingleFieldListener? _fieldListener;
CellDataNotifier<T?>? _cellDataNotifier;
void Function(FieldPB field)? _onCellFieldChanged;
void Function(FieldInfo field)? _onCellFieldChanged;
VoidCallback? _onRowMetaChanged;
Timer? _loadDataOperation;
Timer? _saveDataOperation;
@ -105,8 +102,9 @@ class CellController<T, D> {
);
// 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<T, D> {
if (_cellDataLoader.reloadOnFieldChange) {
_loadData();
}
_onCellFieldChanged?.call(fieldPB);
_onCellFieldChanged?.call(fieldInfo);
},
);
@ -132,7 +130,7 @@ class CellController<T, D> {
/// 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<T, D> {
await _cellListener?.stop();
_cellListener = null;
await _fieldListener?.stop();
_fieldListener = null;
_loadDataOperation?.cancel();
_saveDataOperation?.cancel();
_cellDataNotifier?.dispose();

View File

@ -69,6 +69,7 @@ class _GridSortNotifier extends ChangeNotifier {
}
typedef OnReceiveUpdateFields = void Function(List<FieldInfo>);
typedef OnReceiveField = void Function(FieldInfo);
typedef OnReceiveFields = void Function(List<FieldInfo>);
typedef OnReceiveFilters = void Function(List<FilterInfo>);
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({
OnReceiveFields? onFieldsListener,
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
Future<void> dispose() async {
if (_isDisposed) {

View File

@ -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<FieldEditorEvent, FieldEditorState> {
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<FieldEditorEvent, FieldEditorState> {
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<void> close() {
_singleFieldListener.stop();
fieldController.removeSingleFieldListener(
fieldId: fieldId,
onFieldChanged: _listener,
);
return super.close();
}
@ -50,19 +54,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
on<FieldEditorEvent>(
(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<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) {
result.fold(
(l) => null,
@ -121,9 +136,8 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
@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(

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/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<RowBannerEvent, RowBannerState> {
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<RowBannerEvent, RowBannerState> {
}
final String viewId;
final FieldController fieldController;
final RowBackendService _rowBackendSvc;
final RowMetaListener _metaListener;
SingleFieldListener? _fieldListener;
@override
Future<void> close() async {
await _metaListener.stop();
await _fieldListener?.stop();
_fieldListener = null;
return super.close();
}
@ -66,11 +64,11 @@ class RowBannerBloc extends Bloc<RowBannerEvent, RowBannerState> {
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));
}
},
);

View File

@ -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<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
= FlowyResult<DatabaseFieldChangesetPB, FlowyError>;

View File

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

View File

@ -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<RowBanner> {
return BlocProvider<RowBannerBloc>(
create: (context) => RowBannerBloc(
viewId: widget.rowController.viewId,
fieldController: widget.fieldController,
rowMeta: widget.rowController.rowMeta,
)..add(const RowBannerEvent.initial()),
child: MouseRegion(

View File

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

View File

@ -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'));

View File

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

View File

@ -96,8 +96,7 @@ class BoardTestContext {
Future<FieldEditorBloc> 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();

View File

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

View File

@ -31,8 +31,7 @@ class GridTestContext {
Future<FieldEditorBloc> 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();