chore: cache row data

This commit is contained in:
appflowy 2022-04-16 20:20:00 +08:00
parent 792f8b95aa
commit 3cc7c8e6de
17 changed files with 247 additions and 225 deletions

View File

@ -150,13 +150,6 @@ void _resolveGridDeps(GetIt getIt) {
(view, _) => GridBloc(view: view),
);
getIt.registerFactoryParam<RowBloc, RowData, GridFieldCache>(
(data, fieldCache) => RowBloc(
rowData: data,
fieldCache: fieldCache,
),
);
getIt.registerFactoryParam<GridHeaderBloc, String, GridFieldCache>(
(gridId, fieldCache) => GridHeaderBloc(
gridId: gridId,

View File

@ -58,12 +58,11 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) {
if (!isClosed) {
add(CheckboxCellEvent.didReceiveCellUpdate(cell));
}
},
(cell) => add(CheckboxCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
}

View File

@ -57,19 +57,15 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
(notificationData) => _loadCellData(),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_cellListener.start();
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) {
if (!isClosed) {
add(DateCellEvent.didReceiveFieldUpdate(field));
}
},
(field) => add(DateCellEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_fieldListener.start();
}
@ -79,12 +75,11 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) {
if (!isClosed) {
add(DateCellEvent.didReceiveCellUpdate(cell));
}
},
(cell) => add(DateCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
}

View File

@ -84,12 +84,12 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(cell) {
if (!isClosed) {
add(NumberCellEvent.didReceiveCellUpdate(cell));
}
},
(cell) => add(NumberCellEvent.didReceiveCellUpdate(cell)),
(err) => Log.error(err),
);
}

View File

@ -49,16 +49,15 @@ class SelectionCellBloc extends Bloc<SelectionCellEvent, SelectionCellState> {
fieldId: state.cellData.field.id,
rowId: state.cellData.rowId,
);
if (isClosed) {
return;
}
result.fold(
(selectOptionContext) {
if (!isClosed) {
add(SelectionCellEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
));
}
},
(selectOptionContext) => add(SelectionCellEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
)),
(err) => Log.error(err),
);
}

View File

@ -117,16 +117,15 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
fieldId: state.field.id,
rowId: state.rowId,
);
if (isClosed) {
return;
}
result.fold(
(selectOptionContext) {
if (!isClosed) {
add(SelectOptionEditorEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
));
}
},
(selectOptionContext) => add(SelectOptionEditorEvent.didReceiveOptions(
selectOptionContext.options,
selectOptionContext.selectOptions,
)),
(err) => Log.error(err),
);
},
@ -144,14 +143,10 @@ class SelectOptionEditorBloc extends Bloc<SelectOptionEditorEvent, SelectOptionE
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) {
if (!isClosed) {
add(SelectOptionEditorEvent.didReceiveFieldUpdate(field));
}
},
(field) => add(SelectOptionEditorEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_fieldListener.start();
}
}

View File

@ -47,14 +47,10 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
void _startListening() {
_fieldListener.updateFieldNotifier?.addPublishListener((result) {
result.fold(
(field) {
if (!isClosed) {
add(FieldCellEvent.didReceiveFieldUpdate(field));
}
},
(field) => add(FieldCellEvent.didReceiveFieldUpdate(field)),
(err) => Log.error(err),
);
});
}, listenWhen: () => !isClosed);
_fieldListener.start();
}
}

View File

@ -18,14 +18,14 @@ class GridBloc extends Bloc<GridEvent, GridState> {
final GridListener _gridListener;
final GridFieldsListener _fieldListener;
final GridFieldCache fieldCache;
final GridRowCache _rowCache;
final GridRowCache rowCache;
GridBloc({required View view})
: _fieldListener = GridFieldsListener(gridId: view.id),
_gridService = GridService(gridId: view.id),
_gridListener = GridListener(gridId: view.id),
fieldCache = GridFieldCache(),
_rowCache = GridRowCache(gridId: view.id),
rowCache = GridRowCache(gridId: view.id),
super(GridState.initial(view.id)) {
on<GridEvent>(
(event, emit) async {
@ -41,7 +41,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.rows, fields: value.fields));
},
);
},
@ -62,7 +62,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
result.fold(
(changeset) {
fieldCache.applyChangeset(changeset);
_rowCache.updateFields(fieldCache.unmodifiableFields);
rowCache.updateFields(fieldCache.unmodifiableFields);
add(GridEvent.didReceiveFieldUpdate(fieldCache.clonedFields));
},
(err) => Log.error(err),
@ -74,15 +74,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
result.fold(
(changesets) {
for (final changeset in changesets) {
_rowCache
rowCache
.deleteRows(changeset.deletedRows)
.foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(_rowCache.rows, listState)));
.foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(rowCache.rows, listState)));
_rowCache
rowCache
.insertRows(changeset.insertedRows)
.foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(_rowCache.rows, listState)));
.foldRight(null, (listState, _) => add(GridEvent.didReceiveRowUpdate(rowCache.rows, listState)));
_rowCache.updateRows(changeset.updatedRows);
rowCache.updateRows(changeset.updatedRows);
}
},
(err) => Log.error(err),
@ -107,12 +107,12 @@ class GridBloc extends Bloc<GridEvent, GridState> {
() => result.fold(
(fields) {
fieldCache.clonedFields = fields.items;
_rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
rowCache.updateWithBlock(grid.blockOrders, fieldCache.unmodifiableFields);
emit(state.copyWith(
grid: Some(grid),
fields: fieldCache.clonedFields,
rows: _rowCache.rows,
rows: rowCache.rows,
loadingState: GridLoadingState.finish(left(unit)),
));
},
@ -126,7 +126,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<RowData> rows, GridListState listState) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveRowUpdate(List<GridRow> rows, GridListState listState) = _DidReceiveRowUpdate;
const factory GridEvent.didReceiveFieldUpdate(List<Field> fields) = _DidReceiveFieldUpdate;
}
@ -136,7 +136,7 @@ class GridState with _$GridState {
required String gridId,
required Option<Grid> grid,
required List<Field> fields,
required List<RowData> rows,
required List<GridRow> rows,
required GridLoadingState loadingState,
required GridListState listState,
}) = _GridState;

View File

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

View File

@ -5,9 +5,6 @@ import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart';
import 'package:flutter/foundation.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
part 'grid_service.freezed.dart';
class GridService {
final String gridId;
@ -74,11 +71,16 @@ class GridFieldCache {
_fieldNotifier.addListener(() => onFieldChanged(clonedFields));
}
void addListener(VoidCallback listener, {void Function(List<Field>)? onChanged}) {
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();
});
}
@ -130,98 +132,3 @@ class GridFieldCache {
_fieldNotifier.dispose();
}
}
class GridRowCache {
final String gridId;
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
List<RowData> _rows = [];
GridRowCache({required this.gridId});
List<RowData> get rows => [..._rows];
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
_fields = fields;
_rows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
return RowData.fromBlockRow(gridId, rowOrder, _fields);
}).toList();
}
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) {
if (deletedRows.isEmpty) {
return none();
}
final List<RowData> newRows = [];
final DeletedIndex deletedIndex = [];
final Map<String, RowOrder> deletedRowMap = {for (var rowOrder in deletedRows) rowOrder.rowId: rowOrder};
_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));
}
Option<GridListState> insertRows(List<IndexRowOrder> createdRows) {
if (createdRows.isEmpty) {
return none();
}
InsertedIndexs insertIndexs = [];
for (final newRow in createdRows) {
if (newRow.hasIndex()) {
insertIndexs.add(Tuple2(newRow.index, newRow.rowOrder.rowId));
_rows.insert(newRow.index, _toRowData(newRow.rowOrder));
} else {
insertIndexs.add(Tuple2(newRow.index, newRow.rowOrder.rowId));
_rows.add(_toRowData(newRow.rowOrder));
}
}
return Some(GridListState.insert(insertIndexs));
}
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);
if (index != -1) {
_rows.removeAt(index);
_rows.insert(index, _toRowData(updatedRow));
updatedIndexs.add(index);
}
}
}
RowData _toRowData(RowOrder rowOrder) {
return RowData.fromBlockRow(gridId, rowOrder, _fields);
}
}
typedef InsertedIndexs = List<Tuple2<int, String>>;
typedef DeletedIndex = List<Tuple2<int, RowData>>;
@freezed
class GridListState with _$GridListState {
const factory GridListState.insert(InsertedIndexs items) = _Insert;
const factory GridListState.delete(DeletedIndex items) = _Delete;
const factory GridListState.initial() = InitialListState;
}

View File

@ -11,7 +11,7 @@ part 'row_action_sheet_bloc.freezed.dart';
class RowActionSheetBloc extends Bloc<RowActionSheetEvent, RowActionSheetState> {
final RowService _rowService;
RowActionSheetBloc({required RowData rowData})
RowActionSheetBloc({required GridRow rowData})
: _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
super(RowActionSheetState.initial(rowData)) {
on<RowActionSheetEvent>(
@ -49,10 +49,10 @@ class RowActionSheetEvent with _$RowActionSheetEvent {
@freezed
class RowActionSheetState with _$RowActionSheetState {
const factory RowActionSheetState({
required RowData rowData,
required GridRow rowData,
}) = _RowActionSheetState;
factory RowActionSheetState.initial(RowData rowData) => RowActionSheetState(
factory RowActionSheetState.initial(GridRow rowData) => RowActionSheetState(
rowData: rowData,
);
}

View File

@ -18,11 +18,16 @@ class RowBloc extends Bloc<RowEvent, RowState> {
final RowService _rowService;
final RowListener _rowlistener;
final GridFieldCache _fieldCache;
final GridRowCache _rowCache;
RowBloc({required RowData rowData, required GridFieldCache fieldCache})
: _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
_fieldCache = fieldCache,
RowBloc({
required GridRow rowData,
required GridFieldCache fieldCache,
required GridRowCache rowCache,
}) : _rowService = RowService(gridId: rowData.gridId, rowId: rowData.rowId),
_rowlistener = RowListener(rowId: rowData.rowId),
_fieldCache = fieldCache,
_rowCache = rowCache,
super(RowState.initial(rowData)) {
on<RowEvent>(
(event, emit) async {
@ -76,37 +81,30 @@ class RowBloc extends Bloc<RowEvent, RowState> {
}
Future<void> _startListening() async {
_rowlistener.updateRowNotifier?.addPublishListener((result) {
result.fold(
(row) {
if (!isClosed) {
add(RowEvent.didUpdateRow(row));
}
},
(err) => Log.error(err),
);
});
_rowlistener.updateRowNotifier?.addPublishListener(
(result) {
result.fold(
(row) => add(RowEvent.didUpdateRow(row)),
(err) => Log.error(err),
);
},
listenWhen: () => !isClosed,
);
_fieldCache.addListener(() {
if (!isClosed) {
add(const RowEvent.fieldsDidUpdate());
}
});
_fieldCache.addListener(
() => add(const RowEvent.fieldsDidUpdate()),
listenWhen: () => !isClosed,
);
_rowlistener.start();
}
Future<void> _loadRow(Emitter<RowState> emit) async {
_rowService.getRow().then((result) {
return result.fold(
(row) {
if (!isClosed) {
add(RowEvent.didLoadRow(row));
}
},
(err) => Log.error(err),
);
});
final data = await _rowCache.getRowData(state.rowData.rowId);
if (isClosed) {
return;
}
data.foldRight(null, (data, _) => add(RowEvent.didLoadRow(data)));
}
CellDataMap _makeCellDatas(Row row, List<Field> fields) {
@ -137,12 +135,12 @@ class RowEvent with _$RowEvent {
@freezed
class RowState with _$RowState {
const factory RowState({
required RowData rowData,
required GridRow rowData,
required Future<Option<Row>> row,
required Option<CellDataMap> cellDataMap,
}) = _RowState;
factory RowState.initial(RowData rowData) => RowState(
factory RowState.initial(GridRow rowData) => RowState(
rowData: rowData,
row: Future(() => none()),
cellDataMap: none(),

View File

@ -1,5 +1,8 @@
import 'dart:collection';
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';
@ -57,6 +60,115 @@ class RowService {
}
}
class GridRowCache {
final String gridId;
UnmodifiableListView<Field> _fields = UnmodifiableListView([]);
HashMap<String, Row> rowDataMap = HashMap();
List<GridRow> _rows = [];
GridRowCache({required this.gridId});
List<GridRow> get rows => [..._rows];
Future<Option<Row>> getRowData(String rowId) async {
final Row? data = rowDataMap[rowId];
if (data != null) {
return Future(() => Some(data));
}
final payload = RowIdentifierPayload.create()
..gridId = gridId
..rowId = rowId;
final result = await GridEventGetRow(payload).send();
return Future(() {
return result.fold(
(data) {
data.freeze();
rowDataMap[data.id] = data;
return Some(data);
},
(err) {
Log.error(err);
return none();
},
);
});
}
void updateWithBlock(List<GridBlockOrder> blocks, UnmodifiableListView<Field> fields) {
_fields = fields;
_rows = blocks.expand((block) => block.rowOrders).map((rowOrder) {
return GridRow.fromBlockRow(gridId, rowOrder, _fields);
}).toList();
}
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) {
if (deletedRows.isEmpty) {
return none();
}
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) {
if (deletedRowMap[value.rowId] == null) {
newRows.add(value);
} else {
deletedIndex.add(Tuple2(index, value));
}
});
_rows = newRows;
return Some(GridListState.delete(deletedIndex));
}
Option<GridListState> insertRows(List<IndexRowOrder> createdRows) {
if (createdRows.isEmpty) {
return none();
}
InsertedIndexs insertIndexs = [];
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);
}
return Some(GridListState.insert(insertIndexs));
}
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);
if (index != -1) {
_rows.removeAt(index);
_rows.insert(index, _toRowData(updatedRow));
updatedIndexs.add(index);
}
}
}
GridRow _toRowData(RowOrder rowOrder) {
return GridRow.fromBlockRow(gridId, rowOrder, _fields);
}
}
@freezed
class CellData with _$CellData {
const factory CellData({
@ -68,20 +180,32 @@ class CellData with _$CellData {
}
@freezed
class RowData with _$RowData {
const factory RowData({
class GridRow with _$GridRow {
const factory GridRow({
required String gridId,
required String rowId,
required List<Field> fields,
required double height,
}) = _RowData;
required Future<Option<Row>> data,
}) = _GridRow;
factory RowData.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
return RowData(
factory GridRow.fromBlockRow(String gridId, RowOrder row, List<Field> fields) {
return GridRow(
gridId: gridId,
rowId: row.rowId,
fields: fields,
rowId: row.rowId,
data: Future(() => none()),
height: row.height.toDouble(),
);
}
}
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;
}

View File

@ -1,5 +1,6 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/workspace/application/grid/grid_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_bloc.dart';
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@ -221,14 +222,19 @@ class _GridRowsState extends State<_GridRows> {
);
}
Widget _renderRow(BuildContext context, RowData rowData, Animation<double> animation) {
final fieldCache = context.read<GridBloc>().fieldCache;
Widget _renderRow(BuildContext context, GridRow rowData, Animation<double> animation) {
final bloc = context.read<GridBloc>();
final fieldCache = bloc.fieldCache;
final rowCache = bloc.rowCache;
return SizeTransition(
sizeFactor: animation,
child: GridRowWidget(
data: rowData,
fieldCache: fieldCache,
blocBuilder: () => RowBloc(
rowData: rowData,
fieldCache: fieldCache,
rowCache: rowCache,
),
key: ValueKey(rowData.rowId),
),
);

View File

@ -12,9 +12,12 @@ import 'package:provider/provider.dart';
import 'row_action_sheet.dart';
class GridRowWidget extends StatefulWidget {
final RowData data;
final GridFieldCache fieldCache;
const GridRowWidget({required this.data, required this.fieldCache, Key? key}) : super(key: key);
final RowBloc Function() blocBuilder;
const GridRowWidget({
required this.blocBuilder,
Key? key,
}) : super(key: key);
@override
State<GridRowWidget> createState() => _GridRowWidgetState();
@ -26,7 +29,8 @@ class _GridRowWidgetState extends State<GridRowWidget> {
@override
void initState() {
_rowBloc = getIt<RowBloc>(param1: widget.data, param2: widget.fieldCache)..add(const RowEvent.initial());
_rowBloc = widget.blocBuilder();
_rowBloc.add(const RowEvent.initial());
_rowStateNotifier = _RegionStateNotifier();
super.initState();
}

View File

@ -14,7 +14,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class GridRowActionSheet extends StatelessWidget {
final RowData rowData;
final GridRow rowData;
const GridRowActionSheet({required this.rowData, Key? key}) : super(key: key);
@override

View File

@ -31,12 +31,18 @@ class PublishNotifier<T> extends ChangeNotifier {
T? get currentValue => _value;
void addPublishListener(void Function(T) callback) {
void addPublishListener(void Function(T) callback, {bool Function()? listenWhen}) {
super.addListener(
() {
if (_value != null) {
callback(_value!);
if (_value == null) {
return;
}
if (listenWhen != null && listenWhen() == false) {
return;
}
callback(_value!);
},
);
}