chore: refactor row cache

This commit is contained in:
appflowy 2022-04-19 11:41:37 +08:00
parent 5ee4b8a2bc
commit b3a99be7f8
6 changed files with 133 additions and 102 deletions

View File

@ -51,13 +51,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
void _startListening() { void _startListening() {
fieldCache.addListener( fieldCache.addListener(
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
listenWhen: () => !isClosed, listenWhen: () => !isClosed,
onChanged: (fields) => add(GridEvent.didReceiveFieldUpdate(fields)),
); );
rowCache.addListener( rowCache.addListener(
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
listenWhen: () => !isClosed, listenWhen: () => !isClosed,
onChanged: (rows, listState) => add(GridEvent.didReceiveRowUpdate(rowCache.clonedRows, listState)),
); );
} }

View File

@ -53,23 +53,17 @@ class RowBloc extends Bloc<RowEvent, RowState> {
void _handleRowUpdate(Row row, Emitter<RowState> emit) { void _handleRowUpdate(Row row, Emitter<RowState> emit) {
final CellDataMap cellDataMap = _makeCellDatas(row, state.rowData.fields); final CellDataMap cellDataMap = _makeCellDatas(row, state.rowData.fields);
emit(state.copyWith( emit(state.copyWith(cellDataMap: Some(cellDataMap)));
row: Future(() => Some(row)),
cellDataMap: Some(cellDataMap),
));
} }
Future<void> _handleFieldUpdate(Emitter<RowState> emit) async { Future<void> _handleFieldUpdate(Emitter<RowState> emit) async {
final optionRow = await state.row; final data = state.rowData.data;
final CellDataMap cellDataMap = optionRow.fold( if (data == null) {
() => CellDataMap.identity(), return;
(row) => _makeCellDatas(row, _fieldCache.unmodifiableFields), }
);
emit(state.copyWith( final CellDataMap cellDataMap = _makeCellDatas(data, state.rowData.fields);
rowData: state.rowData.copyWith(fields: _fieldCache.unmodifiableFields), emit(state.copyWith(cellDataMap: Some(cellDataMap)));
cellDataMap: Some(cellDataMap),
));
} }
@override @override
@ -98,11 +92,12 @@ class RowBloc extends Bloc<RowEvent, RowState> {
} }
Future<void> _loadRow(Emitter<RowState> emit) async { Future<void> _loadRow(Emitter<RowState> emit) async {
final data = await _rowCache.getRowData(state.rowData.rowId); final data = _rowCache.loadRow(state.rowData.rowId);
if (isClosed) { data.foldRight(null, (data, _) {
return; if (!isClosed) {
} add(RowEvent.didLoadRow(data));
data.foldRight(null, (data, _) => add(RowEvent.didLoadRow(data))); }
});
} }
CellDataMap _makeCellDatas(Row row, List<Field> fields) { CellDataMap _makeCellDatas(Row row, List<Field> fields) {
@ -134,13 +129,11 @@ class RowEvent with _$RowEvent {
class RowState with _$RowState { class RowState with _$RowState {
const factory RowState({ const factory RowState({
required GridRow rowData, required GridRow rowData,
required Future<Option<Row>> row,
required Option<CellDataMap> cellDataMap, required Option<CellDataMap> cellDataMap,
}) = _RowState; }) = _RowState;
factory RowState.initial(GridRow rowData) => RowState( factory RowState.initial(GridRow rowData) => RowState(
rowData: rowData, rowData: rowData,
row: Future(() => none()),
cellDataMap: none(), cellDataMap: none(),
); );
} }

View File

@ -1,4 +1,5 @@
import 'dart:collection'; import 'dart:collection';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart'; import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart'; import 'package:flowy_sdk/log.dart';
@ -7,37 +8,31 @@ 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:flowy_sdk/protobuf/flowy-grid/row_entities.pb.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/grid_listener.dart'; import 'package:app_flowy/workspace/application/grid/grid_listener.dart';
part 'row_service.freezed.dart'; part 'row_service.freezed.dart';
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 { class GridRowCache {
final String gridId; final String gridId;
final GridRowListener _rowsListener; final GridRowListener _rowsListener;
final RowsNotifier _rowNotifier = RowsNotifier(); late final _RowsNotifier _rowNotifier;
final HashMap<String, Row> _rowDataMap = HashMap();
UnmodifiableListView<Field> _fields = UnmodifiableListView([]); UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
List<GridRow> get clonedRows => _rowNotifier.clonedRows;
GridRowCache({required this.gridId}) : _rowsListener = GridRowListener(gridId: gridId) { GridRowCache({required this.gridId}) : _rowsListener = GridRowListener(gridId: gridId) {
_rowNotifier = _RowsNotifier(
rowBuilder: (rowOrder) {
return GridRow(
gridId: gridId,
fields: _fields,
rowId: rowOrder.rowId,
height: rowOrder.height.toDouble(),
);
},
);
_rowsListener.rowsUpdateNotifier.addPublishListener((result) { _rowsListener.rowsUpdateNotifier.addPublishListener((result) {
result.fold( result.fold(
(changesets) { (changesets) {
@ -58,8 +53,6 @@ class GridRowCache {
_rowNotifier.dispose(); _rowNotifier.dispose();
} }
List<GridRow> get clonedRows => [..._rowNotifier.rows];
void addListener({ void addListener({
void Function(List<GridRow>, GridRowChangeReason)? onChanged, void Function(List<GridRow>, GridRowChangeReason)? onChanged,
bool Function()? listenWhen, bool Function()? listenWhen,
@ -80,7 +73,7 @@ class GridRowCache {
void Function(Row)? onUpdated, void Function(Row)? onUpdated,
bool Function()? listenWhen, bool Function()? listenWhen,
}) { }) {
f() { listenrHandler() {
if (onUpdated == null) { if (onUpdated == null) {
return; return;
} }
@ -90,57 +83,76 @@ class GridRowCache {
} }
_rowNotifier._changeReason.whenOrNull(update: (indexs) { _rowNotifier._changeReason.whenOrNull(update: (indexs) {
final row = _rowDataMap[rowId]; final row = _rowNotifier.rowDataWithId(rowId);
if (indexs[rowId] != null && row != null) { if (indexs[rowId] != null && row != null) {
onUpdated(row); onUpdated(row);
} }
}); });
} }
_rowNotifier.addListener(f); _rowNotifier.addListener(listenrHandler);
return f; return listenrHandler;
} }
void removeRowListener(VoidCallback callback) { void removeRowListener(VoidCallback callback) {
_rowNotifier.removeListener(callback); _rowNotifier.removeListener(callback);
} }
Future<Option<Row>> getRowData(String rowId) async { Option<Row> loadRow(String rowId) {
final Row? data = _rowDataMap[rowId]; final Row? data = _rowNotifier.rowDataWithId(rowId);
if (data != null) { if (data != null) {
return Future(() => Some(data)); return Some(data);
} }
final payload = RowIdentifierPayload.create() final payload = RowIdentifierPayload.create()
..gridId = gridId ..gridId = gridId
..rowId = rowId; ..rowId = rowId;
final result = await GridEventGetRow(payload).send(); GridEventGetRow(payload).send().then((result) {
return Future(() { result.fold(
return result.fold( (rowData) => _rowNotifier.rowData = rowData,
(data) { (err) => Log.error(err),
data.freeze();
_rowDataMap[data.id] = data;
return Some(data);
},
(err) {
Log.error(err);
return none();
},
); );
}); });
return none();
} }
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) { void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
_fields = fields; _fields = fields;
final newRows = blocks.expand((block) => block.rowOrders).map((rowOrder) { final rowOrders = blocks.expand((block) => block.rowOrders).toList();
return GridRow.fromBlockRow(gridId, rowOrder, _fields); _rowNotifier.reset(rowOrders);
}).toList();
_rowNotifier.updateRows(newRows, const GridRowChangeReason.initial());
} }
void _deleteRows(List<RowOrder> deletedRows) { void _deleteRows(List<RowOrder> deletedRows) {
_rowNotifier.deleteRows(deletedRows);
}
void _insertRows(List<IndexRowOrder> createdRows) {
_rowNotifier.insertRows(createdRows);
}
void _updateRows(List<RowOrder> rowOrders) {
_rowNotifier.updateRows(rowOrders);
}
}
class _RowsNotifier extends ChangeNotifier {
List<GridRow> _rows = [];
HashMap<String, Row> _rowDataMap = HashMap();
GridRowChangeReason _changeReason = const InitialListState();
final GridRow Function(RowOrder) rowBuilder;
_RowsNotifier({
required this.rowBuilder,
});
void reset(List<RowOrder> rowOrders) {
_rowDataMap = HashMap();
final rows = rowOrders.map((rowOrder) => rowBuilder(rowOrder)).toList();
_update(rows, const GridRowChangeReason.initial());
}
void deleteRows(List<RowOrder> deletedRows) {
if (deletedRows.isEmpty) { if (deletedRows.isEmpty) {
return; return;
} }
@ -149,7 +161,7 @@ class GridRowCache {
final DeletedIndexs deletedIndex = []; final DeletedIndexs deletedIndex = [];
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder}; final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
_rowNotifier.rows.asMap().forEach((index, row) { _rows.asMap().forEach((index, row) {
if (deletedRowMap[row.rowId] == null) { if (deletedRowMap[row.rowId] == null) {
newRows.add(row); newRows.add(row);
} else { } else {
@ -157,48 +169,81 @@ class GridRowCache {
} }
}); });
_rowNotifier.updateRows(newRows, GridRowChangeReason.delete(deletedIndex)); _update(newRows, GridRowChangeReason.delete(deletedIndex));
} }
void _insertRows(List<IndexRowOrder> createdRows) { void insertRows(List<IndexRowOrder> createdRows) {
if (createdRows.isEmpty) { if (createdRows.isEmpty) {
return; return;
} }
InsertedIndexs insertIndexs = []; InsertedIndexs insertIndexs = [];
final List<GridRow> newRows = _rowNotifier.rows; final List<GridRow> newRows = _rows;
for (final createdRow in createdRows) { for (final createdRow in createdRows) {
final gridRow = GridRow.fromBlockRow(gridId, createdRow.rowOrder, _fields); final rowOrder = createdRow.rowOrder;
insertIndexs.add( final insertIndex = InsertedIndex(index: createdRow.index, rowId: rowOrder.rowId);
InsertedIndex( insertIndexs.add(insertIndex);
index: createdRow.index, newRows.insert(createdRow.index, (rowBuilder(rowOrder)));
rowId: gridRow.rowId,
),
);
newRows.insert(createdRow.index, gridRow);
} }
_rowNotifier.updateRows(newRows, GridRowChangeReason.insert(insertIndexs)); _update(newRows, GridRowChangeReason.insert(insertIndexs));
} }
void _updateRows(List<RowOrder> updatedRows) { void updateRows(List<RowOrder> updatedRows) {
if (updatedRows.isEmpty) { if (updatedRows.isEmpty) {
return; return;
} }
final UpdatedIndexs updatedIndexs = UpdatedIndexs(); final UpdatedIndexs updatedIndexs = UpdatedIndexs();
final List<GridRow> newRows = _rowNotifier.rows; final List<GridRow> newRows = _rows;
for (final rowOrder in updatedRows) { for (final rowOrder in updatedRows) {
final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId); final index = newRows.indexWhere((row) => row.rowId == rowOrder.rowId);
if (index != -1) { if (index != -1) {
newRows.removeAt(index); newRows.removeAt(index);
newRows.insert(index, GridRow.fromBlockRow(gridId, rowOrder, _fields)); // Remove the cache data
_rowDataMap.remove(rowOrder.rowId); _rowDataMap.remove(rowOrder.rowId);
newRows.insert(index, rowBuilder(rowOrder));
updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId); updatedIndexs[rowOrder.rowId] = UpdatedIndex(index: index, rowId: rowOrder.rowId);
} }
} }
_rowNotifier.updateRows(newRows, GridRowChangeReason.update(updatedIndexs)); _update(newRows, GridRowChangeReason.update(updatedIndexs));
} }
void _update(List<GridRow> rows, GridRowChangeReason changeReason) {
_rows = rows;
_changeReason = changeReason;
changeReason.map(
insert: (_) => notifyListeners(),
delete: (_) => notifyListeners(),
update: (_) => notifyListeners(),
initial: (_) {},
);
}
set rowData(Row rowData) {
rowData.freeze();
_rowDataMap[rowData.id] = rowData;
final index = _rows.indexWhere((row) => row.rowId == rowData.id);
if (index != -1) {
if (_rows[index].data != rowData) {
final row = _rows.removeAt(index).copyWith(data: rowData);
_rows.insert(index, row);
final UpdatedIndexs updatedIndexs = UpdatedIndexs();
updatedIndexs[row.rowId] = UpdatedIndex(index: index, rowId: row.rowId);
_changeReason = GridRowChangeReason.update(updatedIndexs);
notifyListeners();
}
}
}
Row? rowDataWithId(String rowId) {
return _rowDataMap[rowId];
}
List<GridRow> get clonedRows => [..._rows];
} }
class RowService { class RowService {
@ -258,7 +303,7 @@ class GridCellIdentifier with _$GridCellIdentifier {
required String rowId, required String rowId,
required Field field, required Field field,
Cell? cell, Cell? cell,
}) = _CellData; }) = _GridCellIdentifier;
} }
@freezed @freezed
@ -268,18 +313,8 @@ class GridRow with _$GridRow {
required String rowId, required String rowId,
required List<Field> fields, required List<Field> fields,
required double height, required double height,
required Future<Option<Row>> data, Row? data,
}) = _GridRow; }) = _GridRow;
factory GridRow.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
return GridRow(
gridId: gridId,
fields: fields,
rowId: row.rowId,
data: Future(() => none()),
height: row.height.toDouble(),
);
}
} }
typedef InsertedIndexs = List<InsertedIndex>; typedef InsertedIndexs = List<InsertedIndex>;

View File

@ -222,7 +222,11 @@ class _GridRowsState extends State<_GridRows> {
); );
} }
Widget _renderRow(BuildContext context, GridRow rowData, Animation<double> animation) { Widget _renderRow(
BuildContext context,
GridRow rowData,
Animation<double> animation,
) {
final bloc = context.read<GridBloc>(); final bloc = context.read<GridBloc>();
final fieldCache = bloc.fieldCache; final fieldCache = bloc.fieldCache;
final rowCache = bloc.rowCache; final rowCache = bloc.rowCache;

View File

@ -47,7 +47,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
() => widget.setFocus(context, false), () => widget.setFocus(context, false),
); );
}, },
child: Row(children: children), child: ClipRRect(child: Row(children: children)),
), ),
); );
}, },
@ -103,7 +103,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
() => widget.setFocus(context, false), () => widget.setFocus(context, false),
); );
}, },
child: Row(children: children), child: ClipRRect(child: Row(children: children)),
), ),
); );
}, },

View File

@ -7,7 +7,6 @@ import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'row_action_sheet.dart'; import 'row_action_sheet.dart';
class GridRowWidget extends StatefulWidget { class GridRowWidget extends StatefulWidget {