memory leak fixes

This commit is contained in:
Thibault Deckers 2023-11-15 00:53:33 +01:00
parent 5b73c8630b
commit 3909b9223d
15 changed files with 127 additions and 47 deletions

View file

@ -4,6 +4,7 @@ import 'package:aves/theme/icons.dart';
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/identity/buttons/captioned_button.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves_video/aves_video.dart';
import 'package:flutter/material.dart';
@ -25,9 +26,10 @@ class MuteToggler extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: controller?.canMuteNotifier ?? ValueNotifier(false),
builder: (context, canDo, child) {
return NullableValueListenableBuilder<bool>(
valueListenable: controller?.canMuteNotifier,
builder: (context, value, child) {
final canDo = value ?? false;
return StreamBuilder<double>(
stream: controller?.volumeStream ?? Stream.value(1.0),
builder: (context, snapshot) {
@ -66,9 +68,10 @@ class MuteTogglerCaption extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>(
valueListenable: controller?.canMuteNotifier ?? ValueNotifier(false),
builder: (context, canDo, child) {
return NullableValueListenableBuilder<bool>(
valueListenable: controller?.canMuteNotifier,
builder: (context, value, child) {
final canDo = value ?? false;
return StreamBuilder<double>(
stream: controller?.volumeStream ?? Stream.value(1.0),
builder: (context, snapshot) {

View file

@ -6,6 +6,7 @@ import 'package:aves/widgets/common/extensions/build_context.dart';
import 'package:aves/widgets/common/extensions/media_query.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/tile_extent_controller.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
@ -151,9 +152,10 @@ class SafeCutoutArea extends StatelessWidget {
return ValueListenableBuilder<EdgeInsets>(
valueListenable: AvesApp.cutoutInsetsNotifier,
builder: (context, cutoutInsets, child) {
return ValueListenableBuilder<double>(
valueListenable: animation ?? ValueNotifier(1),
builder: (context, factor, child) {
return NullableValueListenableBuilder<double>(
valueListenable: animation,
builder: (context, value, child) {
final double factor = value ?? 1.0;
final effectiveInsets = cutoutInsets * factor;
return Padding(
padding: effectiveInsets,

View file

@ -7,6 +7,7 @@ import 'package:aves/widgets/common/map/leaflet/latlng_tween.dart' as llt;
import 'package:aves/widgets/common/map/leaflet/scale_layer.dart';
import 'package:aves/widgets/common/map/leaflet/tile_layers.dart';
import 'package:aves_map/aves_map.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:flutter/material.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
@ -172,8 +173,8 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
rotate: true,
alignment: Alignment.bottomCenter,
),
ValueListenableBuilder<LatLng?>(
valueListenable: widget.dotLocationNotifier ?? ValueNotifier(null),
NullableValueListenableBuilder<LatLng?>(
valueListenable: widget.dotLocationNotifier,
builder: (context, dotLocation, child) => MarkerLayer(
markers: [
if (dotLocation != null)
@ -214,9 +215,10 @@ class _EntryLeafletMapState<T> extends State<EntryLeafletMap<T>> with TickerProv
final corner2 = overlayEntry.bottomRight;
if (corner1 == null || corner2 == null) return const SizedBox();
return ValueListenableBuilder<double>(
valueListenable: widget.overlayOpacityNotifier ?? ValueNotifier(1),
builder: (context, overlayOpacity, child) {
return NullableValueListenableBuilder<double>(
valueListenable: widget.overlayOpacityNotifier,
builder: (context, value, child) {
final double overlayOpacity = value ?? 1.0;
return OverlayImageLayer(
overlayImages: [
OverlayImage(

View file

@ -24,7 +24,7 @@ class ViewerThumbnailPreview extends StatefulWidget {
}
class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {
final ValueNotifier<int> _entryIndexNotifier = ValueNotifier(0);
late final ValueNotifier<int> _entryIndexNotifier;
final Debouncer _debouncer = Debouncer(delay: ADurations.viewerThumbnailScrollDebounceDelay);
List<AvesEntry> get entries => widget.entries;
@ -34,7 +34,7 @@ class _ViewerThumbnailPreviewState extends State<ViewerThumbnailPreview> {
@override
void initState() {
super.initState();
_entryIndexNotifier.value = widget.displayedIndex;
_entryIndexNotifier = ValueNotifier(widget.displayedIndex);
_entryIndexNotifier.addListener(_onScrollerIndexChanged);
}

View file

@ -28,6 +28,7 @@ import 'package:aves/widgets/viewer/action/entry_action_delegate.dart';
import 'package:aves/widgets/viewer/controls/notifications.dart';
import 'package:aves/widgets/viewer/video/conductor.dart';
import 'package:aves_model/aves_model.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:aves_video/aves_video.dart';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
@ -438,15 +439,18 @@ class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
Widget? child;
void onPressed() => actionDelegate.onActionSelected(context, action);
ValueListenableBuilder<bool> _buildFromListenable(ValueListenable<bool>? enabledNotifier) {
return ValueListenableBuilder<bool>(
valueListenable: enabledNotifier ?? ValueNotifier(false),
builder: (context, canDo, child) => IconButton(
Widget _buildFromListenable(ValueListenable<bool>? enabledNotifier) {
return NullableValueListenableBuilder<bool>(
valueListenable: enabledNotifier,
builder: (context, value, child) {
final canDo = value ?? false;
return IconButton(
icon: child!,
onPressed: canDo ? onPressed : null,
focusNode: focusNode,
tooltip: action.getText(context),
),
);
},
child: action.getIcon(),
);
}

View file

@ -1,6 +1,7 @@
import 'package:aves/model/entry/entry.dart';
import 'package:aves/model/multipage.dart';
import 'package:aves/widgets/viewer/multipage/controller.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:flutter/widgets.dart';
class PageEntryBuilder extends StatelessWidget {
@ -20,8 +21,8 @@ class PageEntryBuilder extends StatelessWidget {
stream: controller != null ? controller.infoStream : Stream.value(null),
builder: (context, snapshot) {
final multiPageInfo = controller?.info;
return ValueListenableBuilder<int?>(
valueListenable: controller?.pageNotifier ?? ValueNotifier(null),
return NullableValueListenableBuilder<int?>(
valueListenable: controller?.pageNotifier,
builder: (context, page, child) {
final pageEntry = multiPageInfo?.getPageEntryByIndex(page);
return builder(pageEntry);

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:aves_map/aves_map.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:latlong2/latlong.dart' as ll;
@ -172,12 +173,13 @@ class _EntryGoogleMapState<T> extends State<EntryGoogleMap<T>> with WidgetsBindi
final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
final overlayEntry = widget.overlayEntry;
return ValueListenableBuilder<ll.LatLng?>(
valueListenable: widget.dotLocationNotifier ?? ValueNotifier(null),
return NullableValueListenableBuilder<ll.LatLng?>(
valueListenable: widget.dotLocationNotifier,
builder: (context, dotLocation, child) {
return ValueListenableBuilder<double>(
valueListenable: widget.overlayOpacityNotifier ?? ValueNotifier(1),
builder: (context, overlayOpacity, child) {
return NullableValueListenableBuilder<double>(
valueListenable: widget.overlayOpacityNotifier,
builder: (context, value, child) {
final double overlayOpacity = value ?? 1.0;
return LayoutBuilder(
builder: (context, constraints) {
_sizeNotifier.value = constraints.biggest;

View file

@ -30,6 +30,13 @@ packages:
relative: true
source: path
version: "0.0.1"
aves_utils:
dependency: "direct main"
description:
path: "../aves_utils"
relative: true
source: path
version: "0.0.1"
characters:
dependency: transitive
description:

View file

@ -12,6 +12,8 @@ dependencies:
path: ../aves_map
aves_services:
path: ../aves_services
aves_utils:
path: ../aves_utils
device_info_plus:
google_api_availability:
google_maps_flutter:

View file

@ -2,6 +2,7 @@ import 'dart:async';
import 'dart:typed_data';
import 'package:aves_map/aves_map.dart';
import 'package:aves_utils/aves_utils.dart';
import 'package:flutter/material.dart';
import 'package:huawei_map/huawei_map.dart';
import 'package:latlong2/latlong.dart' as ll;
@ -146,12 +147,13 @@ class _EntryHmsMapState<T> extends State<EntryHmsMap<T>> {
final interactive = context.select<MapThemeData, bool>((v) => v.interactive);
// final overlayEntry = widget.overlayEntry;
return ValueListenableBuilder<ll.LatLng?>(
valueListenable: widget.dotLocationNotifier ?? ValueNotifier(null),
return NullableValueListenableBuilder<ll.LatLng?>(
valueListenable: widget.dotLocationNotifier,
builder: (context, dotLocation, child) {
return ValueListenableBuilder<double>(
valueListenable: widget.overlayOpacityNotifier ?? ValueNotifier(1),
builder: (context, overlayOpacity, child) {
return NullableValueListenableBuilder<double>(
valueListenable: widget.overlayOpacityNotifier,
builder: (context, value, child) {
// final double overlayOpacity = value ?? 1.0;
return HuaweiMap(
initialCameraPosition: CameraPosition(
bearing: bounds.rotation,

View file

@ -37,6 +37,13 @@ packages:
relative: true
source: path
version: "0.0.1"
aves_utils:
dependency: "direct main"
description:
path: "../aves_utils"
relative: true
source: path
version: "0.0.1"
characters:
dependency: transitive
description:

View file

@ -14,6 +14,8 @@ dependencies:
path: ../aves_platform_meta
aves_services:
path: ../aves_services
aves_utils:
path: ../aves_utils
# cf https://github.com/HMS-Core/hms-flutter-plugin/pull/296
huawei_hmsavailability:
git:

View file

@ -1,6 +1,6 @@
library aves_utils;
export 'src/change_notifier.dart';
export 'src/colors.dart';
export 'src/listenable.dart';
export 'src/optional_event_channel.dart';
export 'src/vector_utils.dart';

View file

@ -1,9 +0,0 @@
import 'package:flutter/foundation.dart';
// `ChangeNotifier` wrapper to call `notify` without constraint
class AChangeNotifier extends ChangeNotifier {
void notify() {
// why is this protected?
super.notifyListeners();
}
}

View file

@ -0,0 +1,55 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
// `ChangeNotifier` wrapper to call `notify` without constraint
class AChangeNotifier extends ChangeNotifier {
void notify() {
// why is this protected?
super.notifyListeners();
}
}
// contrary to standard `ValueListenableBuilder`, this widget allows providing a null listenable
class NullableValueListenableBuilder<T> extends StatefulWidget {
final ValueListenable<T?>? valueListenable;
final ValueWidgetBuilder<T?> builder;
final Widget? child;
const NullableValueListenableBuilder({
super.key,
required this.valueListenable,
required this.builder,
this.child,
});
@override
State<NullableValueListenableBuilder> createState() => _NullableValueListenableBuilderState<T>();
}
class _NullableValueListenableBuilderState<T> extends State<NullableValueListenableBuilder<T>> {
ValueNotifier<T?>? _internalValueListenable;
ValueListenable<T?> get _valueListenable {
var listenable = widget.valueListenable;
if (listenable == null) {
_internalValueListenable ??= ValueNotifier(null);
listenable = _internalValueListenable;
}
return listenable!;
}
@override
void dispose() {
_internalValueListenable?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<T?>(
valueListenable: _valueListenable,
builder: widget.builder,
child: widget.child,
);
}
}