mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: memory leaks (#3995)
* feat: add memory leak monitor * fix: memory leaks * feat: dump call stack in memory leak detector * chore: disable memory leak detector
This commit is contained in:
@ -32,6 +32,12 @@ class FlowyEmojiSearchBar extends StatefulWidget {
|
|||||||
class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
|
class _FlowyEmojiSearchBarState extends State<FlowyEmojiSearchBar> {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Padding(
|
return Padding(
|
||||||
|
@ -3,8 +3,8 @@ import 'package:appflowy_backend/log.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
import 'field_controller.dart';
|
import 'field_controller.dart';
|
||||||
@ -109,6 +109,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
|||||||
@override
|
@override
|
||||||
Future<void> close() {
|
Future<void> close() {
|
||||||
_singleFieldListener.stop();
|
_singleFieldListener.stop();
|
||||||
|
|
||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,11 +117,11 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class FieldEditorEvent with _$FieldEditorEvent {
|
class FieldEditorEvent with _$FieldEditorEvent {
|
||||||
const factory FieldEditorEvent.initial() = _InitialField;
|
const factory FieldEditorEvent.initial() = _InitialField;
|
||||||
const factory FieldEditorEvent.didReceiveFieldChanged(String fieldId) =
|
const factory FieldEditorEvent.didReceiveFieldChanged(final String fieldId) =
|
||||||
_DidReceiveFieldChanged;
|
_DidReceiveFieldChanged;
|
||||||
const factory FieldEditorEvent.switchFieldType(FieldType fieldType) =
|
const factory FieldEditorEvent.switchFieldType(final FieldType fieldType) =
|
||||||
_SwitchFieldType;
|
_SwitchFieldType;
|
||||||
const factory FieldEditorEvent.renameField(String name) = _RenameField;
|
const factory FieldEditorEvent.renameField(final String name) = _RenameField;
|
||||||
const factory FieldEditorEvent.toggleFieldVisibility() =
|
const factory FieldEditorEvent.toggleFieldVisibility() =
|
||||||
_ToggleFieldVisiblity;
|
_ToggleFieldVisiblity;
|
||||||
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
const factory FieldEditorEvent.deleteField() = _DeleteField;
|
||||||
@ -130,6 +131,6 @@ class FieldEditorEvent with _$FieldEditorEvent {
|
|||||||
@freezed
|
@freezed
|
||||||
class FieldEditorState with _$FieldEditorState {
|
class FieldEditorState with _$FieldEditorState {
|
||||||
const factory FieldEditorState({
|
const factory FieldEditorState({
|
||||||
required FieldInfo field,
|
required final FieldInfo field,
|
||||||
}) = _FieldEditorState;
|
}) = _FieldEditorState;
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||||
import 'package:flowy_infra/notifier.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:flowy_infra/notifier.dart';
|
||||||
|
import 'package:protobuf/protobuf.dart' hide FieldInfo;
|
||||||
|
|
||||||
import '../field_service.dart';
|
import '../field_service.dart';
|
||||||
import 'type_option_context.dart';
|
import 'type_option_context.dart';
|
||||||
@ -110,4 +110,8 @@ class TypeOptionController {
|
|||||||
void removeFieldListener(void Function() listener) {
|
void removeFieldListener(void Function() listener) {
|
||||||
_fieldNotifier.removeListener(listener);
|
_fieldNotifier.removeListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dispose() {
|
||||||
|
_fieldNotifier.dispose();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,8 +12,8 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/
|
|||||||
import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart';
|
import 'package:appflowy/plugins/database_view/tab_bar/tab_bar_view.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||||
import 'package:appflowy/util/platform_extension.dart';
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
@ -25,11 +25,11 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
import '../../widgets/card/cells/card_cell.dart';
|
import '../../widgets/card/card.dart';
|
||||||
import '../../widgets/card/card_cell_builder.dart';
|
import '../../widgets/card/card_cell_builder.dart';
|
||||||
|
import '../../widgets/card/cells/card_cell.dart';
|
||||||
import '../../widgets/row/cell_builder.dart';
|
import '../../widgets/row/cell_builder.dart';
|
||||||
import '../application/board_bloc.dart';
|
import '../application/board_bloc.dart';
|
||||||
import '../../widgets/card/card.dart';
|
|
||||||
import 'toolbar/board_setting_bar.dart';
|
import 'toolbar/board_setting_bar.dart';
|
||||||
import 'widgets/board_hidden_groups.dart';
|
import 'widgets/board_hidden_groups.dart';
|
||||||
|
|
||||||
@ -383,11 +383,15 @@ class _BoardTrailingState extends State<BoardTrailing> {
|
|||||||
}
|
}
|
||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
},
|
},
|
||||||
)..addListener(() {
|
)..addListener(_onFocusChanged);
|
||||||
if (!_focusNode.hasFocus) {
|
}
|
||||||
_cancelAddNewGroup();
|
|
||||||
}
|
@override
|
||||||
});
|
void dispose() {
|
||||||
|
_focusNode.removeListener(_onFocusChanged);
|
||||||
|
_focusNode.dispose();
|
||||||
|
_textController.dispose();
|
||||||
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -452,4 +456,10 @@ class _BoardTrailingState extends State<BoardTrailing> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onFocusChanged() {
|
||||||
|
if (!_focusNode.hasFocus) {
|
||||||
|
_cancelAddNewGroup();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,13 @@ import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.da
|
|||||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
|
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/common/type_option_separator.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_settings_entities.pbenum.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.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:easy_localization/easy_localization.dart';
|
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
import 'field_type_option_editor.dart';
|
import 'field_type_option_editor.dart';
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
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/plugins/database_view/application/cell/cell_service.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_controller.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
import 'package:appflowy/plugins/database_view/grid/application/row/row_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme_extension.dart';
|
import 'package:flowy_infra/theme_extension.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -13,11 +15,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import '../../../../widgets/row/accessory/cell_accessory.dart';
|
import '../../../../widgets/row/accessory/cell_accessory.dart';
|
||||||
import '../../layout/sizes.dart';
|
|
||||||
import '../../../../widgets/row/cells/cell_container.dart';
|
import '../../../../widgets/row/cells/cell_container.dart';
|
||||||
|
import '../../layout/sizes.dart';
|
||||||
import 'action.dart';
|
import 'action.dart';
|
||||||
import "package:appflowy/generated/locale_keys.g.dart";
|
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
|
||||||
|
|
||||||
class GridRow extends StatefulWidget {
|
class GridRow extends StatefulWidget {
|
||||||
final RowId viewId;
|
final RowId viewId;
|
||||||
@ -267,7 +267,6 @@ class RowContent extends StatelessWidget {
|
|||||||
return cellByFieldId.values.map(
|
return cellByFieldId.values.map(
|
||||||
(cellId) {
|
(cellId) {
|
||||||
final GridCellWidget child = builder.build(cellId);
|
final GridCellWidget child = builder.build(cellId);
|
||||||
|
|
||||||
return CellContainer(
|
return CellContainer(
|
||||||
width: cellId.fieldInfo.fieldSettings?.width.toDouble() ?? 140,
|
width: cellId.fieldInfo.fieldSettings?.width.toDouble() ?? 140,
|
||||||
isPrimary: cellId.fieldInfo.field.isPrimary,
|
isPrimary: cellId.fieldInfo.field.isPrimary,
|
||||||
|
@ -5,15 +5,15 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/act
|
|||||||
import 'package:appflowy/util/platform_extension.dart';
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.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 'card_bloc.dart';
|
import 'card_bloc.dart';
|
||||||
import 'cells/card_cell.dart';
|
|
||||||
import 'card_cell_builder.dart';
|
import 'card_cell_builder.dart';
|
||||||
|
import 'cells/card_cell.dart';
|
||||||
import 'container/accessory.dart';
|
import 'container/accessory.dart';
|
||||||
import 'container/card_container.dart';
|
import 'container/card_container.dart';
|
||||||
|
|
||||||
@ -193,7 +193,7 @@ class _RowCardState<T> extends State<RowCard<T>> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CardContent<CustomCardData> extends StatelessWidget {
|
class _CardContent<CustomCardData> extends StatefulWidget {
|
||||||
const _CardContent({
|
const _CardContent({
|
||||||
super.key,
|
super.key,
|
||||||
required this.rowNotifier,
|
required this.rowNotifier,
|
||||||
@ -211,26 +211,44 @@ class _CardContent<CustomCardData> extends StatelessWidget {
|
|||||||
final CustomCardData? cardData;
|
final CustomCardData? cardData;
|
||||||
final RowCardStyleConfiguration styleConfiguration;
|
final RowCardStyleConfiguration styleConfiguration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_CardContent<CustomCardData>> createState() =>
|
||||||
|
_CardContentState<CustomCardData>();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CardContentState<CustomCardData>
|
||||||
|
extends State<_CardContent<CustomCardData>> {
|
||||||
|
final List<EditableCardNotifier> _notifiers = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
for (final element in _notifiers) {
|
||||||
|
element.dispose();
|
||||||
|
}
|
||||||
|
_notifiers.clear();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (styleConfiguration.hoverStyle != null) {
|
if (widget.styleConfiguration.hoverStyle != null) {
|
||||||
return FlowyHover(
|
return FlowyHover(
|
||||||
style: styleConfiguration.hoverStyle,
|
style: widget.styleConfiguration.hoverStyle,
|
||||||
buildWhenOnHover: () => !rowNotifier.isEditing.value,
|
buildWhenOnHover: () => !widget.rowNotifier.isEditing.value,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: styleConfiguration.cardPadding,
|
padding: widget.styleConfiguration.cardPadding,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: _makeCells(context, cells),
|
children: _makeCells(context, widget.cells),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: styleConfiguration.cardPadding,
|
padding: widget.styleConfiguration.cardPadding,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: _makeCells(context, cells),
|
children: _makeCells(context, widget.cells),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -241,26 +259,28 @@ class _CardContent<CustomCardData> extends StatelessWidget {
|
|||||||
) {
|
) {
|
||||||
final List<Widget> children = [];
|
final List<Widget> children = [];
|
||||||
// Remove all the cell listeners.
|
// Remove all the cell listeners.
|
||||||
rowNotifier.unbind();
|
widget.rowNotifier.unbind();
|
||||||
|
|
||||||
cells.asMap().forEach((int index, DatabaseCellContext cellContext) {
|
cells.asMap().forEach((int index, DatabaseCellContext cellContext) {
|
||||||
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
|
final isEditing = index == 0 ? widget.rowNotifier.isEditing.value : false;
|
||||||
final cellNotifier = EditableCardNotifier(isEditing: isEditing);
|
final cellNotifier = EditableCardNotifier(isEditing: isEditing);
|
||||||
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
// Only use the first cell to receive user's input when click the edit
|
// Only use the first cell to receive user's input when click the edit
|
||||||
// button
|
// button
|
||||||
rowNotifier.bindCell(cellContext, cellNotifier);
|
widget.rowNotifier.bindCell(cellContext, cellNotifier);
|
||||||
|
} else {
|
||||||
|
_notifiers.add(cellNotifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
final child = Padding(
|
final child = Padding(
|
||||||
key: cellContext.key(),
|
key: cellContext.key(),
|
||||||
padding: styleConfiguration.cellPadding,
|
padding: widget.styleConfiguration.cellPadding,
|
||||||
child: cellBuilder.buildCell(
|
child: widget.cellBuilder.buildCell(
|
||||||
cellContext: cellContext,
|
cellContext: cellContext,
|
||||||
cellNotifier: cellNotifier,
|
cellNotifier: cellNotifier,
|
||||||
renderHook: renderHook,
|
renderHook: widget.renderHook,
|
||||||
cardData: cardData,
|
cardData: widget.cardData,
|
||||||
hasNotes: !cellContext.rowMeta.isDocumentEmpty,
|
hasNotes: !cellContext.rowMeta.isDocumentEmpty,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||||
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/select_option.pb.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/timestamp_entities.pb.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@ -160,11 +160,8 @@ class EditableRowNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
for (final notifier in _cells.values) {
|
unbind();
|
||||||
notifier.dispose();
|
isEditing.dispose();
|
||||||
}
|
|
||||||
|
|
||||||
_cells.clear();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,9 @@ import 'package:appflowy/mobile/presentation/database/card/row/cells/cells.dart'
|
|||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||||
import 'package:appflowy/util/platform_extension.dart';
|
import 'package:appflowy/util/platform_extension.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import '../../application/cell/cell_service.dart';
|
import '../../application/cell/cell_service.dart';
|
||||||
import 'accessory/cell_accessory.dart';
|
import 'accessory/cell_accessory.dart';
|
||||||
import 'accessory/cell_shortcuts.dart';
|
import 'accessory/cell_shortcuts.dart';
|
||||||
@ -263,7 +264,9 @@ abstract class GridCellState<T extends GridCellWidget> extends State<T> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
widget.onAccessoryHover.dispose();
|
||||||
widget.requestFocus.removeAllListener();
|
widget.requestFocus.removeAllListener();
|
||||||
|
widget.requestFocus.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,6 +339,7 @@ class RequestFocusListener extends ChangeNotifier {
|
|||||||
void removeAllListener() {
|
void removeAllListener() {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
|
_listener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart';
|
import 'package:appflowy/plugins/database_view/widgets/row/cells/text_cell/text_cell_bloc.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.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 '../../../../grid/presentation/layout/sizes.dart';
|
import '../../../../grid/presentation/layout/sizes.dart';
|
||||||
import '../../cell_builder.dart';
|
import '../../cell_builder.dart';
|
||||||
|
|
||||||
@ -141,6 +143,7 @@ class _GridTextCellState extends GridEditableTextCell<GridTextCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
|
_controller.dispose();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
@ -166,6 +166,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
|
EditorStyleCustomizer get styleCustomizer => widget.styleCustomizer;
|
||||||
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
||||||
|
|
||||||
|
late final EditorScrollController editorScrollController;
|
||||||
|
|
||||||
Future<bool> showSlashMenu(editorState) async {
|
Future<bool> showSlashMenu(editorState) async {
|
||||||
final result = await customSlashCommand(
|
final result = await customSlashCommand(
|
||||||
slashMenuItems,
|
slashMenuItems,
|
||||||
@ -186,6 +188,12 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
slashMenuItems = _customSlashMenuItems();
|
slashMenuItems = _customSlashMenuItems();
|
||||||
effectiveScrollController = widget.scrollController ?? ScrollController();
|
effectiveScrollController = widget.scrollController ?? ScrollController();
|
||||||
|
|
||||||
|
editorScrollController = EditorScrollController(
|
||||||
|
editorState: widget.editorState,
|
||||||
|
shrinkWrap: widget.shrinkWrap,
|
||||||
|
scrollController: effectiveScrollController,
|
||||||
|
);
|
||||||
|
|
||||||
// keep the previous font style when typing new text.
|
// keep the previous font style when typing new text.
|
||||||
supportSlashMenuNodeWhiteList.addAll([
|
supportSlashMenuNodeWhiteList.addAll([
|
||||||
ToggleListBlockKeys.type,
|
ToggleListBlockKeys.type,
|
||||||
@ -207,7 +215,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
effectiveScrollController.dispose();
|
effectiveScrollController.dispose();
|
||||||
}
|
}
|
||||||
inlineActionsService.dispose();
|
inlineActionsService.dispose();
|
||||||
|
editorScrollController.dispose();
|
||||||
widget.editorState.dispose();
|
widget.editorState.dispose();
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
@ -225,12 +233,6 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
|||||||
|
|
||||||
_setRTLToolbarItems(isRTL);
|
_setRTLToolbarItems(isRTL);
|
||||||
|
|
||||||
final editorScrollController = EditorScrollController(
|
|
||||||
editorState: widget.editorState,
|
|
||||||
shrinkWrap: widget.shrinkWrap,
|
|
||||||
scrollController: effectiveScrollController,
|
|
||||||
);
|
|
||||||
|
|
||||||
final editor = Directionality(
|
final editor = Directionality(
|
||||||
textDirection: textDirection,
|
textDirection: textDirection,
|
||||||
child: AppFlowyEditor(
|
child: AppFlowyEditor(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:appflowy/startup/tasks/memory_leak_detector.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||||
import 'package:appflowy_backend/appflowy_backend.dart';
|
import 'package:appflowy_backend/appflowy_backend.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
@ -63,6 +64,9 @@ class FlowyRunner {
|
|||||||
// this task should be first task, for handling platform errors.
|
// this task should be first task, for handling platform errors.
|
||||||
// don't catch errors in test mode
|
// don't catch errors in test mode
|
||||||
if (!mode.isUnitTest) const PlatformErrorCatcherTask(),
|
if (!mode.isUnitTest) const PlatformErrorCatcherTask(),
|
||||||
|
// this task should be second task, for handling memory leak.
|
||||||
|
// there's a flag named _enable in memory_leak_detector.dart. If it's false, the task will be ignored.
|
||||||
|
MemoryLeakDetectorTask(),
|
||||||
// localization
|
// localization
|
||||||
const InitLocalizationTask(),
|
const InitLocalizationTask(),
|
||||||
// init the app window
|
// init the app window
|
||||||
|
@ -0,0 +1,97 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:leak_tracker/leak_tracker.dart';
|
||||||
|
|
||||||
|
import '../startup.dart';
|
||||||
|
|
||||||
|
bool _enable = false;
|
||||||
|
|
||||||
|
class MemoryLeakDetectorTask extends LaunchTask {
|
||||||
|
MemoryLeakDetectorTask();
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> initialize(LaunchContext context) async {
|
||||||
|
if (!kDebugMode || !_enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
LeakTracking.start();
|
||||||
|
LeakTracking.phase = const PhaseSettings(
|
||||||
|
leakDiagnosticConfig: LeakDiagnosticConfig(
|
||||||
|
collectRetainingPathForNotGCed: true,
|
||||||
|
collectStackTraceOnStart: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
MemoryAllocations.instance.addListener((p0) {
|
||||||
|
LeakTracking.dispatchObjectEvent(p0.toMap());
|
||||||
|
});
|
||||||
|
_timer = Timer.periodic(const Duration(seconds: 1), (_) async {
|
||||||
|
final summary = await LeakTracking.checkLeaks();
|
||||||
|
if (summary.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final details = await LeakTracking.collectLeaks();
|
||||||
|
dumpDetails(LeakType.notDisposed, details);
|
||||||
|
// dumpDetails(LeakType.notGCed, details);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {
|
||||||
|
if (!kDebugMode || !_enable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = null;
|
||||||
|
LeakTracking.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
final _dumpablePackages = [
|
||||||
|
'package:appflowy/',
|
||||||
|
];
|
||||||
|
void dumpDetails(LeakType type, Leaks leaks) {
|
||||||
|
final summary = '${type.desc}: ${switch (type) {
|
||||||
|
LeakType.notDisposed => '${leaks.notDisposed.length}',
|
||||||
|
LeakType.notGCed => '${leaks.notGCed.length}',
|
||||||
|
LeakType.gcedLate => '${leaks.gcedLate.length}'
|
||||||
|
}}';
|
||||||
|
debugPrint(summary);
|
||||||
|
final details = switch (type) {
|
||||||
|
LeakType.notDisposed => leaks.notDisposed,
|
||||||
|
LeakType.notGCed => leaks.notGCed,
|
||||||
|
LeakType.gcedLate => leaks.gcedLate
|
||||||
|
};
|
||||||
|
for (final value in details) {
|
||||||
|
final stack = value.context![ContextKeys.startCallstack]! as StackTrace;
|
||||||
|
final stackInAppFlowy = stack
|
||||||
|
.toString()
|
||||||
|
.split('\n')
|
||||||
|
.where(
|
||||||
|
(stack) =>
|
||||||
|
// ignore current file call stack
|
||||||
|
!stack.contains('memory_leak_detector') &&
|
||||||
|
_dumpablePackages.any((pkg) => stack.contains(pkg)),
|
||||||
|
)
|
||||||
|
.join('\n');
|
||||||
|
// ignore the untreatable leak
|
||||||
|
if (stackInAppFlowy.isEmpty) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
final object = value.type;
|
||||||
|
debugPrint('''
|
||||||
|
$object ${type.desc}
|
||||||
|
$stackInAppFlowy
|
||||||
|
''');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension on LeakType {
|
||||||
|
String get desc => switch (this) {
|
||||||
|
LeakType.notDisposed => 'not disposed',
|
||||||
|
LeakType.notGCed => 'not GCed',
|
||||||
|
LeakType.gcedLate => 'GCed late'
|
||||||
|
};
|
||||||
|
}
|
@ -2,8 +2,8 @@ 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/document/presentation/more/cubit/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
|
||||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
||||||
|
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -83,6 +83,12 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
|||||||
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
|
final List<String> availableFonts = GoogleFonts.asMap().keys.toList();
|
||||||
final ValueNotifier<String> query = ValueNotifier('');
|
final ValueNotifier<String> query = ValueNotifier('');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
query.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ThemeValueDropDown(
|
return ThemeValueDropDown(
|
||||||
|
@ -426,10 +426,16 @@ class _AIAccessKeyInputState extends State<_AIAccessKeyInput> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
textEditingController.text = widget.accessKey;
|
textEditingController.text = widget.accessKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
textEditingController.dispose();
|
||||||
|
debounce.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return TextField(
|
return TextField(
|
||||||
@ -470,12 +476,6 @@ class _AIAccessKeyInputState extends State<_AIAccessKeyInput> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
debounce.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
typedef SelectIconCallback = void Function(String iconUrl, bool isSelected);
|
typedef SelectIconCallback = void Function(String iconUrl, bool isSelected);
|
||||||
|
@ -979,6 +979,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.0.4"
|
version: "0.0.4"
|
||||||
|
leak_tracker:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: leak_tracker
|
||||||
|
sha256: b63ca5cc296c7509d71f6d4a8cb6085eec8461970c503f3ef3c5c541bc3f0a9a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.0.6"
|
||||||
linked_scroll_controller:
|
linked_scroll_controller:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -128,6 +128,7 @@ dependencies:
|
|||||||
image_picker: ^1.0.4
|
image_picker: ^1.0.4
|
||||||
image_gallery_saver: ^2.0.3
|
image_gallery_saver: ^2.0.3
|
||||||
cached_network_image: ^3.3.0
|
cached_network_image: ^3.3.0
|
||||||
|
leak_tracker: ^9.0.6
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
|
Reference in New Issue
Block a user