Workaround memory leak in bloc

This commit is contained in:
Ming Ming 2024-06-18 00:04:53 +08:00
parent 8d75ab7967
commit 68b4273bfa
19 changed files with 133 additions and 46 deletions

View file

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/single_child_widget.dart';
@ -58,3 +60,42 @@ extension BlocExtension<E, S> on Bloc<E, S> {
}
}
}
class _BlocForEachObj {
const _BlocForEachObj(this.subscription, this.completer);
final StreamSubscription subscription;
final Completer completer;
}
mixin BlocForEachMixin<E, S> implements Bloc<E, S> {
@override
Future<void> close() async {
for (final e in _forEaches) {
unawaited(e.subscription.cancel());
e.completer.complete();
}
}
// The original emit.forEach is causing the internal eventController in Bloc
// to deadlock when closing, use this instead
Future<void> forEach<T>(
Emitter<S> emit,
Stream<T> stream, {
required S Function(T data) onData,
S Function(Object error, StackTrace stackTrace)? onError,
}) async {
final completer = Completer();
final subscription = stream.listen((event) {
emit(onData(event));
}, onError: (e, stackTrace) {
if (onError != null) {
emit(onError(e, stackTrace));
}
});
_forEaches.add(_BlocForEachObj(subscription, completer));
return completer.future;
}
final _forEaches = <_BlocForEachObj>[];
}

View file

@ -1,7 +1,8 @@
part of '../archive_browser.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.filesController,
@ -58,7 +59,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) {
_log.info(ev);
unawaited(filesController.queryByArchived());
return emit.forEach<FilesStreamEvent>(
return forEach(
emit,
filesController.stream,
onData: (data) => state.copyWith(
files: data.data,

View file

@ -1,7 +1,8 @@
part of '../collection_browser.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required DiContainer container,
required this.account,
@ -121,7 +122,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<CollectionItemStreamData>(
forEach(
emit,
itemsController.stream,
onData: (data) => state.copyWith(
items: _filterItems(data.items, state.itemsWhitelist),
@ -136,7 +138,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
);
},
),
emit.forEach<FilesStreamEvent>(
forEach(
emit,
filesController.stream,
onData: (data) {
final whitelist = HashSet.of(data.dataMap.keys);

View file

@ -1,7 +1,8 @@
part of '../collection_picker.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.controller,
@ -31,7 +32,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadCollections ev, Emitter<_State> emit) async {
_log.info(ev);
return emit.forEach<CollectionStreamEvent>(
return forEach(
emit,
controller.stream,
onData: (data) => state.copyWith(
collections: data.data.map((e) => e.collection).toList(),

View file

@ -1,7 +1,8 @@
part of '../home_collections.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.controller,
@ -69,7 +70,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadCollections ev, Emitter<_State> emit) async {
_log.info(ev);
return emit.forEach<CollectionStreamEvent>(
return forEach(
emit,
controller.stream,
onData: (data) => state.copyWith(
collections: data.data.map((d) => d.collection).toList(),

View file

@ -1,7 +1,8 @@
part of '../home_photos2.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc(
this._c, {
required this.account,
@ -138,7 +139,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadItems ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<FilesSummaryStreamEvent>(
forEach(
emit,
filesController.summaryStream,
onData: (data) {
if (data.summary.items.isEmpty && _isInitialLoad) {
@ -157,7 +159,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
);
},
),
emit.forEach<TimelineStreamEvent>(
forEach(
emit,
filesController.timelineStream,
onData: (data) {
if (!data.isDummy && _isInitialLoad) {

View file

@ -1,7 +1,8 @@
part of '../people_browser.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.personsController,
@ -16,7 +17,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadPersons ev, Emitter<_State> emit) {
_log.info(ev);
return emit.forEach<PersonStreamEvent>(
return forEach(
emit,
personsController.stream,
onData: (data) => state.copyWith(
persons: data.data,

View file

@ -1,7 +1,8 @@
part of '../places_browser.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.placesController,
@ -16,7 +17,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoad(_LoadPlaces ev, Emitter<_State> emit) {
_log.info(ev);
return emit.forEach<PlaceStreamEvent>(
return forEach(
emit,
placesController.stream,
onData: (data) => state.copyWith(
places: data.data,

View file

@ -1,7 +1,8 @@
part of '../search_landing.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.personsController,
@ -18,7 +19,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoadPersons(_LoadPersons ev, Emitter<_State> emit) {
_log.info(ev);
return emit.forEach<PersonStreamEvent>(
return forEach(
emit,
personsController.stream,
onData: (data) => state.copyWith(
persons: data.data,
@ -54,7 +56,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onLoadPlaces(_LoadPlaces ev, Emitter<_State> emit) {
_log.info(ev);
return emit.forEach<PlaceStreamEvent>(
return forEach(
emit,
placesController.stream,
onData: (data) => state.copyWith(
places: data.data,

View file

@ -1,7 +1,8 @@
part of '../collection_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
}) : super(_State(
@ -16,7 +17,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
return emit.forEach<bool>(
return forEach(
emit,
prefController.isAlbumBrowserShowDateChange,
onData: (data) => state.copyWith(isBrowserShowDate: data),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../enhancement_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
}) : super(_State(
@ -20,7 +21,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<bool>(
forEach(
emit,
prefController.isSaveEditResultToServerChange,
onData: (data) => state.copyWith(isSaveEditResultToServer: data),
onError: (e, stackTrace) {
@ -28,7 +30,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<SizeInt>(
forEach(
emit,
prefController.enhanceMaxSizeChange,
onData: (data) => state.copyWith(maxSize: data),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../language_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
}) : super(_State.init(
@ -30,7 +31,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) {
_log.info(ev);
return emit.forEach<language_util.AppLanguage>(
return forEach(
emit,
prefController.languageChange,
onData: (data) => state.copyWith(selected: data),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../metadata_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
}) : super(_State(
@ -19,7 +20,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<bool>(
forEach(
emit,
prefController.isEnableExifChange,
onData: (data) => state.copyWith(isEnable: data),
onError: (e, stackTrace) {
@ -27,7 +29,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<bool>(
forEach(
emit,
prefController.shouldProcessExifWifiOnlyChange,
onData: (data) => state.copyWith(isWifiOnly: data),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../misc_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
required this.securePrefController,
@ -19,7 +20,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<bool>(
forEach(
emit,
prefController.isDoubleTapExitChange,
onData: (data) => state.copyWith(isDoubleTapExit: data),
onError: (e, stackTrace) {
@ -27,7 +29,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<ProtectedPageAuthType?>(
forEach(
emit,
securePrefController.protectedPageAuthTypeChange,
onData: (data) => state.copyWith(appLockType: data),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../photos_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
required this.accountPrefController,
@ -20,7 +21,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<bool>(
forEach(
emit,
accountPrefController.isEnableMemoryAlbumChange,
onData: (data) => state.copyWith(isEnableMemories: data),
onError: (e, stackTrace) {
@ -28,7 +30,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<int>(
forEach(
emit,
prefController.memoriesRangeChange,
onData: (data) => state.copyWith(memoriesRange: data),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../theme_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
}) : super(_State(
@ -22,7 +23,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<bool>(
forEach(
emit,
prefController.isFollowSystemThemeChange,
onData: (data) => state.copyWith(isFollowSystemTheme: data),
onError: (e, stackTrace) {
@ -30,7 +32,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<bool>(
forEach(
emit,
prefController.isUseBlackInDarkThemeChange,
onData: (data) => state.copyWith(isUseBlackInDarkTheme: data),
onError: (e, stackTrace) {
@ -38,7 +41,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<Color?>(
forEach(
emit,
prefController.seedColorChange,
onData: (data) => state.copyWith(seedColor: data?.value),
onError: (e, stackTrace) {
@ -46,7 +50,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<Color?>(
forEach(
emit,
prefController.secondarySeedColorChange,
onData: (data) => state.copyWith(secondarySeedColor: data?.value),
onError: (e, stackTrace) {

View file

@ -1,7 +1,8 @@
part of '../viewer_settings.dart';
@npLog
class _Bloc extends Bloc<_Event, _State> with BlocLogger {
class _Bloc extends Bloc<_Event, _State>
with BlocLogger, BlocForEachMixin<_Event, _State> {
_Bloc({
required this.prefController,
}) : super(_State(
@ -21,7 +22,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
Future<void> _onInit(_Init ev, Emitter<_State> emit) async {
_log.info(ev);
await Future.wait([
emit.forEach<int>(
forEach(
emit,
prefController.viewerScreenBrightnessChange,
onData: (data) => state.copyWith(screenBrightness: data),
onError: (e, stackTrace) {
@ -29,7 +31,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<bool>(
forEach(
emit,
prefController.isViewerForceRotationChange,
onData: (data) => state.copyWith(isForceRotation: data),
onError: (e, stackTrace) {
@ -37,7 +40,8 @@ class _Bloc extends Bloc<_Event, _State> with BlocLogger {
return state.copyWith(error: ExceptionEvent(e, stackTrace));
},
),
emit.forEach<GpsMapProvider>(
forEach(
emit,
prefController.gpsMapProviderChange,
onData: (data) => state.copyWith(gpsMapProvider: data),
onError: (e, stackTrace) {

View file

@ -9,6 +9,7 @@ import 'package:kiwi/kiwi.dart';
import 'package:logging/logging.dart';
import 'package:nc_photos/account.dart';
import 'package:nc_photos/app_localizations.dart';
import 'package:nc_photos/bloc_util.dart';
import 'package:nc_photos/controller/account_controller.dart';
import 'package:nc_photos/controller/account_pref_controller.dart';
import 'package:nc_photos/controller/sharings_controller.dart';

View file

@ -2,7 +2,7 @@ part of '../sharing_browser.dart';
/// List shares to be shown in [SharingBrowser]
@npLog
class _Bloc extends Bloc<_Event, _State> {
class _Bloc extends Bloc<_Event, _State> with BlocForEachMixin<_Event, _State> {
_Bloc({
required this.account,
required this.accountPrefController,
@ -21,7 +21,8 @@ class _Bloc extends Bloc<_Event, _State> {
"[_onInit] Failed while _importPotentialSharedAlbum", e, stackTrace);
}
unawaited(sharingsController.reload());
return emit.forEach<SharingStreamEvent>(
return forEach(
emit,
sharingsController.stream,
onData: (data) => state.copyWith(
items: data.data,