chore: refactor row cache with row listener

This commit is contained in:
appflowy 2022-04-16 22:26:34 +08:00
parent 3cc7c8e6de
commit c82754f284
11 changed files with 148 additions and 130 deletions

View File

@ -1,13 +1,10 @@
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/log.dart';
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/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'field/grid_listenr.dart';
import 'grid_listener.dart';
import 'grid_service.dart';
import 'row/row_service.dart';
@ -15,16 +12,12 @@ part 'grid_bloc.freezed.dart';
class GridBloc extends Bloc<GridEvent, GridState> {
final GridService _gridService;
final GridListener _gridListener;
final GridFieldsListener _fieldListener;
final GridFieldCache fieldCache;
final GridRowCache rowCache;
GridBloc({required View view})
: _fieldListener = GridFieldsListener(gridId: view.id),
_gridService = GridService(gridId: view.id),
_gridListener = GridListener(gridId: view.id),
fieldCache = GridFieldCache(),
: _gridService = GridService(gridId: view.id),
fieldCache = GridFieldCache(gridId: view.id),
rowCache = GridRowCache(gridId: view.id),
super(GridState.initial(view.id)) {
on<GridEvent>(
@ -41,7 +34,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
emit(state.copyWith(rows: value.rows, listState: value.listState));
},
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
emit(state.copyWith(rows: rowCache.rows, fields: value.fields));
emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields));
},
);
},
@ -51,44 +44,22 @@ class GridBloc extends Bloc<GridEvent, GridState> {
@override
Future<void> close() async {
await _gridService.closeGrid();
await _fieldListener.stop();
await _gridListener.stop();
await fieldCache.dispose();
await rowCache.dispose();
fieldCache.dispose();
return super.close();
}
void _startListening() {
_fieldListener.updateFieldsNotifier?.addPublishListener((result) {
result.fold(
(changeset) {
fieldCache.applyChangeset(changeset);
rowCache.updateFields(fieldCache.unmodifiableFields);
add(GridEvent.didReceiveFieldUpdate(fieldCache.clonedFields));
},
(err) => Log.error(err),
);
});
_fieldListener.start();
fieldCache.addListener(
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed,
);
_gridListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(changesets) {
for (final changeset in changesets) {
rowCache
.deleteRows(changeset.deletedRows)
.foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(rowCache.rows, listState)));
rowCache
.insertRows(changeset.insertedRows)
.foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(rowCache.rows, listState)));
rowCache.updateRows(changeset.updatedRows);
}
},
(err) => Log.error(err),
);
});
_gridListener.start();
rowCache.addListener(
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
listenWhen: () => !isClosed,
);
}
Future<void> _loadGrid(Emitter<GridState> emit) async {
@ -106,13 +77,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
return Future(
() => result.fold(
(fields) {
fieldCache.clonedFields = fields.items;
fieldCache.fields = fields.items;
rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
emit(state.copyWith(
grid: Some(grid),
fields: fieldCache.clonedFields,
rows: rowCache.rows,
rows: rowCache.clonedRows,
loadingState: GridLoadingState.finish(left(unit)),
));
},
@ -126,7 +97,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridListState listState) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridRowChangeReason listState) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
}
@ -138,7 +109,7 @@ class GridState with _$GridState {
required List<Field> fields,
required List<GridRow> rows,
required GridLoadingState loadingState,
required GridListState listState,
required GridRowChangeReason listState,
}) = _GridState;
factory GridState.initial(String gridId) => GridState(

View File

@ -37,7 +37,6 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
Future<void> _startListening() async {
fieldCache.addListener(
() {},
onChanged: (fields) => add(GridHeaderEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed,
);

View File

@ -7,12 +7,12 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:app_flowy/core/notification_helper.dart';
class GridListener {
class GridRowListener {
final String gridId;
PublishNotifier<Either<List<GridRowsChangeset>, FlowyError>> rowsUpdateNotifier = PublishNotifier(comparable: null);
GridNotificationListener? _listener;
GridListener({required this.gridId});
GridRowListener({required this.gridId});
void start() {
_listener = GridNotificationListener(

View File

@ -1,5 +1,7 @@
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
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';
@ -50,42 +52,56 @@ class FieldsNotifier extends ChangeNotifier {
}
class GridFieldCache {
final String gridId;
late final GridFieldsListener _fieldListener;
final FieldsNotifier _fieldNotifier = FieldsNotifier();
GridFieldCache();
void applyChangeset(GridFieldChangeset changeset) {
_removeFields(changeset.deletedFields);
_insertFields(changeset.insertedFields);
_updateFields(changeset.updatedFields);
GridFieldCache({required this.gridId}) {
_fieldListener = GridFieldsListener(gridId: gridId);
_fieldListener.updateFieldsNotifier?.addPublishListener((result) {
result.fold(
(changeset) {
_deleteFields(changeset.deletedFields);
_insertFields(changeset.insertedFields);
_updateFields(changeset.updatedFields);
},
(err) => Log.error(err),
);
});
_fieldListener.start();
}
Future<void> dispose() async {
await _fieldListener.stop();
_fieldNotifier.dispose();
}
void applyChangeset(GridFieldChangeset changeset) {}
UnmodifiableListView<Field> get unmodifiableFields => UnmodifiableListView(_fieldNotifier.fields);
List<Field> get clonedFields => [..._fieldNotifier.fields];
set clonedFields(List<Field> fields) {
set fields(List<Field> fields) {
_fieldNotifier.fields = [...fields];
}
void listenOnFieldChanged(void Function(List<Field>) onFieldChanged) {
_fieldNotifier.addListener(() => onFieldChanged(clonedFields));
}
void addListener(VoidCallback listener, {void Function(List<Field>)? onChanged, bool Function()? listenWhen}) {
void addListener({VoidCallback? listener, void Function(List<Field>)? onChanged, bool Function()? listenWhen}) {
_fieldNotifier.addListener(() {
if (onChanged != null) {
onChanged(clonedFields);
}
if (listenWhen != null && listenWhen() == false) {
return;
}
listener();
if (onChanged != null) {
onChanged(clonedFields);
}
if (listener != null) {
listener();
}
});
}
void _removeFields(List<FieldOrder> deletedFields) {
void _deleteFields(List<FieldOrder> deletedFields) {
if (deletedFields.isEmpty) {
return;
}
@ -127,8 +143,4 @@ class GridFieldCache {
}
_fieldNotifier.fields = fields;
}
void dispose() {
_fieldNotifier.dispose();
}
}

View File

@ -92,7 +92,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
);
_fieldCache.addListener(
() => add(const RowEvent.fieldsDidUpdate()),
listener: () => add(const RowEvent.fieldsDidUpdate()),
listenWhen: () => !isClosed,
);

View File

@ -1,11 +1,13 @@
import 'dart:collection';
import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.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:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'row_service.freezed.dart';
@ -60,19 +62,71 @@ class RowService {
}
}
class RowsNotifier extends ChangeNotifier {
List<GridRow> _rows = [];
GridRowChangeReason _changeReason = const InitialListState();
void updateRows(List<GridRow> rows, GridRowChangeReason changeReason) {
_rows = rows;
_changeReason = changeReason;
changeReason.map(
insert: (_) => notifyListeners(),
delete: (_) => notifyListeners(),
update: (_) => notifyListeners(),
initial: (_) {},
);
}
List<GridRow> get rows => _rows;
}
class GridRowCache {
final String gridId;
late GridRowListener _rowsListener;
final HashMap<String, Row> _rowDataMap = HashMap();
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
HashMap<String, Row> rowDataMap = HashMap();
final RowsNotifier _rowNotifier = RowsNotifier();
List<GridRow> _rows = [];
GridRowCache({required this.gridId}) {
_rowsListener = GridRowListener(gridId: gridId);
_rowsListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold(
(changesets) {
for (final changeset in changesets) {
_deleteRows(changeset.deletedRows);
_insertRows(changeset.insertedRows);
_updateRows(changeset.updatedRows);
}
},
(err) => Log.error(err),
);
});
_rowsListener.start();
}
GridRowCache({required this.gridId});
List<GridRow> get clonedRows => [..._rowNotifier.rows];
List<GridRow> get rows => [..._rows];
Future<void> dispose() async {
await _rowsListener.stop();
_rowNotifier.dispose();
}
void addListener({void Function(List<GridRow>, GridRowChangeReason)? onChanged, bool Function()? listenWhen}) {
_rowNotifier.addListener(() {
if (listenWhen != null && listenWhen() == false) {
return;
}
if (onChanged != null) {
onChanged(clonedRows, _rowNotifier._changeReason);
}
});
}
Future<Option<Row>> getRowData(String rowId) async {
final Row? data = rowDataMap[rowId];
final Row? data = _rowDataMap[rowId];
if (data != null) {
return Future(() => Some(data));
}
@ -86,7 +140,7 @@ class GridRowCache {
return result.fold(
(data) {
data.freeze();
rowDataMap[data.id] = data;
_rowDataMap[data.id] = data;
return Some(data);
},
(err) {
@ -99,73 +153,65 @@ class GridRowCache {
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
_fields = fields;
_rows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
final newRows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
return GridRow.fromBlockRow(gridId, rowOrder, _fields);
}).toList();
_rowNotifier.updateRows(newRows, const GridRowChangeReason.initial());
}
void updateFields(UnmodifiableListView<Field> fields) {
if (fields.isEmpty) {
return;
}
_fields = fields;
_rows = _rows.map((row) => row.copyWith(fields: fields)).toList();
}
Option<GridListState> deleteRows(List<RowOrder> deletedRows) {
void _deleteRows(List<RowOrder> deletedRows) {
if (deletedRows.isEmpty) {
return none();
return;
}
final List<GridRow> newRows = [];
final DeletedIndex deletedIndex = [];
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
_rows.asMap().forEach((index, value) {
_rowNotifier.rows.asMap().forEach((index, value) {
if (deletedRowMap[value.rowId] == null) {
newRows.add(value);
} else {
deletedIndex.add(Tuple2(index, value));
}
});
_rows = newRows;
return Some(GridListState.delete(deletedIndex));
_rowNotifier.updateRows(newRows, GridRowChangeReason.delete(deletedIndex));
}
Option<GridListState> insertRows(List<IndexRowOrder> createdRows) {
void _insertRows(List<IndexRowOrder> createdRows) {
if (createdRows.isEmpty) {
return none();
return;
}
InsertedIndexs insertIndexs = [];
final List<GridRow> newRows = _rowNotifier.rows;
for (final createdRow in createdRows) {
final gridRow = GridRow.fromBlockRow(gridId, createdRow.rowOrder, _fields);
insertIndexs.add(Tuple2(createdRow.index, gridRow.rowId));
_rows.insert(createdRow.index, gridRow);
newRows.insert(createdRow.index, gridRow);
}
return Some(GridListState.insert(insertIndexs));
_rowNotifier.updateRows(newRows, GridRowChangeReason.insert(insertIndexs));
}
void updateRows(List<RowOrder> updatedRows) {
void _updateRows(List<RowOrder> updatedRows) {
if (updatedRows.isEmpty) {
return;
}
final List<int> updatedIndexs = [];
for (final updatedRow in updatedRows) {
final index = _rows.indexWhere((row) => row.rowId == updatedRow.rowId);
final List<GridRow> newRows = _rowNotifier.rows;
for (final rowOrder in updatedRows) {
final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
if (index != -1) {
_rows.removeAt(index);
_rows.insert(index, _toRowData(updatedRow));
newRows.removeAt(index);
newRows.insert(index, GridRow.fromBlockRow(gridId, rowOrder, _fields));
updatedIndexs.add(index);
}
}
}
GridRow _toRowData(RowOrder rowOrder) {
return GridRow.fromBlockRow(gridId, rowOrder, _fields);
_rowNotifier.updateRows(newRows, GridRowChangeReason.update(updatedIndexs));
}
}
@ -204,8 +250,9 @@ typedef InsertedIndexs = List<Tuple2<int, String>>;
typedef DeletedIndex = List<Tuple2<int, GridRow>>;
@freezed
class GridListState with _$GridListState {
const factory GridListState.insert(InsertedIndexs items) = _Insert;
const factory GridListState.delete(DeletedIndex items) = _Delete;
const factory GridListState.initial() = InitialListState;
class GridRowChangeReason with _$GridRowChangeReason {
const factory GridRowChangeReason.insert(InsertedIndexs items) = _Insert;
const factory GridRowChangeReason.delete(DeletedIndex items) = _Delete;
const factory GridRowChangeReason.update(List<int> indexs) = _Update;
const factory GridRowChangeReason.initial() = InitialListState;
}

View File

@ -1,5 +1,4 @@
import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
import 'package:app_flowy/workspace/application/grid/field/grid_listenr.dart';
import 'package:app_flowy/workspace/application/grid/grid_service.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
@ -11,13 +10,11 @@ part 'property_bloc.freezed.dart';
class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
final FieldService _service;
final GridFieldsListener _fieldListener;
final GridFieldCache _fieldCache;
GridPropertyBloc({required String gridId, required List<Field> fields})
: _service = FieldService(gridId: gridId),
_fieldListener = GridFieldsListener(gridId: gridId),
_fieldCache = GridFieldCache(),
_fieldCache = GridFieldCache(gridId: gridId),
super(GridPropertyState.initial(gridId, fields)) {
on<GridPropertyEvent>(
(event, emit) async {
@ -45,22 +42,15 @@ class GridPropertyBloc extends Bloc<GridPropertyEvent, GridPropertyState> {
@override
Future<void> close() async {
await _fieldListener.stop();
_fieldCache.dispose();
await _fieldCache.dispose();
return super.close();
}
void _startListening() {
_fieldListener.updateFieldsNotifier?.addPublishListener((result) {
result.fold(
(changeset) {
_fieldCache.applyChangeset(changeset);
add(GridPropertyEvent.didReceiveFieldUpdate(_fieldCache.clonedFields));
},
(err) => Log.error(err),
);
});
_fieldListener.start();
_fieldCache.addListener(
onChanged: (fields) => add(GridPropertyEvent.didReceiveFieldUpdate(_fieldCache.clonedFields)),
listenWhen: () => !isClosed,
);
}
}

View File

@ -205,7 +205,8 @@ class _GridRowsState extends State<_GridRows> {
);
}
},
initial: (updatedIndexs) {},
initial: (_) {},
update: (_) {},
);
},
buildWhen: (previous, current) => false,

View File

@ -5,7 +5,6 @@ import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' hide Row;
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';

View File

@ -1,4 +1,3 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/prelude.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';

View File

@ -215,7 +215,7 @@ impl GridMetaPad {
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
None => Ok(None),
Some(index) => {
debug_assert_eq!(index, from_index);
// debug_assert_eq!(index, from_index);
let field_meta = grid_meta.fields.remove(index);
grid_meta.fields.insert(to_index, field_meta);
Ok(Some(()))