Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🥅 Add exception handling #457

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/src/constants/config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import 'enums.dart';

class AssetPickerConfig {
const AssetPickerConfig({
this.internalExceptionHandler,
this.selectedAssets,
this.maxAssets = defaultMaxAssetsCount,
this.pageSize = defaultAssetsPerPage,
Expand Down Expand Up @@ -61,6 +62,9 @@ class AssetPickerConfig {
'Custom item did not set properly.',
);

/// {@macro wechat_assets_picker.ExceptionHandler}
final ExceptionHandler? internalExceptionHandler;

/// Selected assets.
/// 已选中的资源
final List<AssetEntity>? selectedAssets;
Expand Down
6 changes: 6 additions & 0 deletions lib/src/constants/typedefs.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:photo_manager/photo_manager.dart' show PermissionState;

/// {@template wechat_assets_picker.ExceptionHandler}
/// Handles exceptions during internal calls.
/// 处理内部方法调用时的异常。
/// {@endtemplate}
typedef ExceptionHandler = void Function(Object e, StackTrace s);

/// {@template wechat_assets_picker.LoadingIndicatorBuilder}
/// Build the loading indicator with the given [isAssetsEmpty].
/// 根据给定的 [isAssetsEmpty] 构建加载指示器。
Expand Down
3 changes: 2 additions & 1 deletion lib/src/delegates/asset_picker_builder_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import '../constants/enums.dart';
import '../constants/extensions.dart';
import '../constants/typedefs.dart';
import '../delegates/asset_picker_text_delegate.dart';
import '../internal/methods.dart';
import '../internal/singleton.dart';
import '../models/path_wrapper.dart';
import '../provider/asset_picker_provider.dart';
Expand Down Expand Up @@ -1264,7 +1265,7 @@ class DefaultAssetPickerBuilderDelegate
if (p.hasMoreToLoad) {
if ((p.pageSize <= gridCount * 3 && index == length - 1) ||
index == length - gridCount * 3) {
p.loadMoreAssets();
p.loadMoreAssets().catchError(handleException);
}
}

Expand Down
16 changes: 12 additions & 4 deletions lib/src/delegates/asset_picker_delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:photo_manager/photo_manager.dart';

import '../constants/config.dart';
import '../constants/constants.dart';
import '../constants/typedefs.dart';
import '../internal/methods.dart';
import '../provider/asset_picker_provider.dart';
import '../widget/asset_picker.dart';
Expand Down Expand Up @@ -104,13 +105,17 @@ class AssetPickerDelegate {
locale: Localizations.maybeLocaleOf(context),
),
);
AssetPicker.setInternalExceptionHandler(
pickerConfig.internalExceptionHandler,
);
final List<AssetEntity>? result = await Navigator.of(
context,
rootNavigator: useRootNavigator,
).push<List<AssetEntity>>(
pageRouteBuilder?.call(picker) ??
AssetPickerPageRoute<List<AssetEntity>>(builder: (_) => picker),
);
AssetPicker.setInternalExceptionHandler(null);
return result;
}

Expand Down Expand Up @@ -138,19 +143,22 @@ class AssetPickerDelegate {
Key? key,
bool useRootNavigator = true,
AssetPickerPageRouteBuilder<List<Asset>>? pageRouteBuilder,
ExceptionHandler? internalExceptionHandler,
}) async {
await permissionCheck();
final Widget picker = AssetPicker<Asset, Path>(
key: key,
builder: delegate,
);
AssetPicker.setInternalExceptionHandler(internalExceptionHandler);
final List<Asset>? result = await Navigator.of(
context,
rootNavigator: useRootNavigator,
).push<List<Asset>>(
pageRouteBuilder?.call(picker) ??
AssetPickerPageRoute<List<Asset>>(builder: (_) => picker),
);
AssetPicker.setInternalExceptionHandler(null);
return result;
}

Expand All @@ -165,8 +173,8 @@ class AssetPickerDelegate {
try {
PhotoManager.addChangeCallback(callback);
PhotoManager.startChangeNotify();
} catch (e) {
realDebugPrint('Error when registering assets callback: $e');
} catch (e, s) {
handleException(e, s);
}
}

Expand All @@ -181,8 +189,8 @@ class AssetPickerDelegate {
try {
PhotoManager.removeChangeCallback(callback);
PhotoManager.stopChangeNotify();
} catch (e) {
realDebugPrint('Error when unregistering assets callback: $e');
} catch (e, s) {
handleException(e, s);
}
}

Expand Down
28 changes: 27 additions & 1 deletion lib/src/internal/methods.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,36 @@ import 'dart:developer';

import 'package:flutter/foundation.dart';

import '../constants/typedefs.dart';
import 'singleton.dart';

/// Log only when debugging.
/// 只在调试模式打印
void realDebugPrint(dynamic message) {
if (!kReleaseMode) {
log('$message');
log('[wechat_assets_picker] $message');
}
}

/// @nodoc
void handleException(Object e, StackTrace s) {
final ExceptionHandler? handler = Singleton.internalExceptionHandler;
if (handler == null) {
FlutterError.presentError(
FlutterErrorDetails(
exception: e,
stack: s,
library: 'wechat_assets_picker',
silent: true,
informationCollector: () => <DiagnosticsNode>[
ErrorHint(
'Note: Use `AssetPickerConfig.internalExceptionHandler` '
'to handle exceptions manually.',
),
],
),
);
} else {
handler(e, s);
}
}
4 changes: 3 additions & 1 deletion lib/src/internal/singleton.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,21 @@

import 'package:flutter/widgets.dart';

import '../constants/typedefs.dart';
import '../delegates/asset_picker_text_delegate.dart';
import '../delegates/sort_path_delegate.dart';

/// Define an inner static singleton for picker libraries.
class Singleton {
const Singleton._();

static ExceptionHandler? internalExceptionHandler;
static AssetPickerTextDelegate textDelegate = const AssetPickerTextDelegate();
static SortPathDelegate<dynamic> sortPathDelegate = SortPathDelegate.common;

/// The last scroll position where the picker scrolled.
///
/// See also:
/// * [AssetPickerBuilderDelegate.keepScrollOffset]
/// * [DefaultAssetPickerBuilderDelegate.keepScrollOffset]
static ScrollPosition? scrollPosition;
}
71 changes: 41 additions & 30 deletions lib/src/provider/asset_picker_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:provider/provider.dart';

import '../constants/constants.dart';
import '../delegates/sort_path_delegate.dart';
import '../internal/methods.dart';
import '../internal/singleton.dart';
import '../models/path_wrapper.dart';

Expand Down Expand Up @@ -245,7 +246,7 @@ class DefaultAssetPickerProvider
Future<void>.delayed(initializeDelayDuration, () async {
await getPaths();
await getAssetsFromCurrentPath();
});
}).catchError(handleException);
}

@visibleForTesting
Expand Down Expand Up @@ -414,40 +415,50 @@ class DefaultAssetPickerProvider
Future<Uint8List?> getThumbnailFromPath(
PathWrapper<AssetPathEntity> path,
) async {
if (requestType == RequestType.audio) {
return null;
}
final int assetCount = path.assetCount ?? await path.path.assetCountAsync;
if (assetCount == 0) {
return null;
}
final List<AssetEntity> assets = await path.path.getAssetListRange(
start: 0,
end: 1,
);
if (assets.isEmpty) {
return null;
}
final AssetEntity asset = assets.single;
// Obtain the thumbnail only when the asset is image or video.
if (asset.type != AssetType.image && asset.type != AssetType.video) {
try {
if (requestType == RequestType.audio) {
return null;
}
final int assetCount = path.assetCount ?? await path.path.assetCountAsync;
if (assetCount == 0) {
return null;
}
final List<AssetEntity> assets = await path.path.getAssetListRange(
start: 0,
end: 1,
);
if (assets.isEmpty) {
return null;
}
final AssetEntity asset = assets.single;
// Obtain the thumbnail only when the asset is image or video.
if (asset.type != AssetType.image && asset.type != AssetType.video) {
return null;
}
final Uint8List? data = await asset.thumbnailDataWithSize(
pathThumbnailSize,
);
final int index = _paths.indexWhere(
(PathWrapper<AssetPathEntity> p) => p.path == path.path,
);
if (index != -1) {
_paths[index] = _paths[index].copyWith(thumbnailData: data);
notifyListeners();
}
return data;
} catch (e, s) {
handleException(e, s);
return null;
}
final Uint8List? data = await asset.thumbnailDataWithSize(
pathThumbnailSize,
);
final int index = _paths.indexWhere(
(PathWrapper<AssetPathEntity> p) => p.path == path.path,
);
if (index != -1) {
_paths[index] = _paths[index].copyWith(thumbnailData: data);
notifyListeners();
}
return data;
}

Future<void> getAssetCountFromPath(PathWrapper<AssetPathEntity> path) async {
final int assetCount = await path.path.assetCountAsync;
final int assetCount = await path.path.assetCountAsync.catchError(
(Object e, StackTrace s) {
handleException(e, s);
return 0;
},
);
final int index = _paths.indexWhere(
(PathWrapper<AssetPathEntity> p) => p == path,
);
Expand Down
10 changes: 9 additions & 1 deletion lib/src/widget/asset_picker.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ import 'package:flutter/services.dart';
import 'package:photo_manager/photo_manager.dart';

import '../constants/config.dart';
import '../constants/typedefs.dart';
import '../delegates/asset_picker_builder_delegate.dart';
import '../delegates/asset_picker_delegate.dart';
import '../internal/methods.dart';
import '../internal/singleton.dart';
import '../provider/asset_picker_provider.dart';
import 'asset_picker_page_route.dart';

Expand All @@ -19,6 +22,11 @@ class AssetPicker<Asset, Path> extends StatefulWidget {

final AssetPickerBuilderDelegate<Asset, Path> builder;

/// Update the internal exception handler to the given [exceptionHandler].
static void setInternalExceptionHandler(ExceptionHandler? exceptionHandler) {
Singleton.internalExceptionHandler = exceptionHandler;
}

/// Provide another [AssetPickerDelegate] which override with
/// custom methods during handling the picking,
/// e.g. to verify if arguments are properly set during picking calls.
Expand Down Expand Up @@ -124,7 +132,7 @@ class AssetPickerState<Asset, Path> extends State<AssetPicker<Asset, Path>>
if (mounted) {
setState(() {});
}
});
}).catchError(handleException);
}

@override
Expand Down
4 changes: 1 addition & 3 deletions lib/src/widget/builder/audio_page_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ class _AudioPageBuilderState extends State<AudioPageBuilder> {
@override
void initState() {
super.initState();
openAudioFile();
openAudioFile().catchError(handleException);
}

@override
Expand All @@ -76,8 +76,6 @@ class _AudioPageBuilderState extends State<AudioPageBuilder> {
_controller = VideoPlayerController.network(url!);
await _controller.initialize();
_controller.addListener(audioPlayerListener);
} catch (e) {
realDebugPrint('Error when opening audio file: $e');
} finally {
isLoaded = true;
if (mounted) {
Expand Down
9 changes: 5 additions & 4 deletions lib/src/widget/builder/image_page_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:photo_manager/photo_manager.dart';
import 'package:video_player/video_player.dart';

import '../../delegates/asset_picker_viewer_builder_delegate.dart';
import '../../internal/methods.dart';
import 'locally_available_builder.dart';

class ImagePageBuilder extends StatefulWidget {
Expand Down Expand Up @@ -57,12 +58,12 @@ class _ImagePageBuilderState extends State<ImagePageBuilder> {
if (!mounted || file == null) {
return;
}
final VideoPlayerController c = VideoPlayerController.file(
final VideoPlayerController controller = VideoPlayerController.file(
file,
videoPlayerOptions: VideoPlayerOptions(mixWithOthers: true),
);
setState(() => _controller = c);
c
setState(() => _controller = controller);
controller
..initialize()
..setVolume(0)
..addListener(() {
Expand Down Expand Up @@ -159,7 +160,7 @@ class _ImagePageBuilderState extends State<ImagePageBuilder> {
// Initialize the video controller when the asset is a Live photo
// and available for further use.
if (!_isLocallyAvailable && _isLivePhoto) {
_initializeLivePhoto();
_initializeLivePhoto().catchError(handleException);
}
_isLocallyAvailable = true;
// TODO(Alex): Wait until `extended_image` support synchronized zooming.
Expand Down
6 changes: 4 additions & 2 deletions lib/src/widget/builder/locally_available_builder.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class _LocallyAvailableBuilderState extends State<LocallyAvailableBuilder> {
@override
void initState() {
super.initState();
_checkLocallyAvailable();
_checkLocallyAvailable().catchError(handleException);
}

Future<void> _checkLocallyAvailable() async {
Expand All @@ -61,7 +61,7 @@ class _LocallyAvailableBuilderState extends State<LocallyAvailableBuilder> {
setState(() {});
}
}
});
}).catchError(handleException);
}
_progressHandler?.stream.listen((PMProgressState s) {
realDebugPrint('Handling progress: $s.');
Expand All @@ -70,6 +70,8 @@ class _LocallyAvailableBuilderState extends State<LocallyAvailableBuilder> {
if (mounted) {
setState(() {});
}
} else if (s.state == PMRequestState.failed) {
handleException(s, StackTrace.current);
}
});
}
Expand Down
Loading
Loading