Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
d2110922
Unverified
Commit
d2110922
authored
Jul 18, 2023
by
Ian Hickson
Committed by
GitHub
Jul 18, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Catch errors in loadStructuredData (#130748)
Fixes
https://github.com/flutter/flutter/issues/42390
parent
2d753a62
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
78 additions
and
62 deletions
+78
-62
synchronous_future.dart
packages/flutter/lib/src/foundation/synchronous_future.dart
+2
-0
asset_bundle.dart
packages/flutter/lib/src/services/asset_bundle.dart
+56
-62
asset_bundle_test.dart
packages/flutter/test/services/asset_bundle_test.dart
+20
-0
No files found.
packages/flutter/lib/src/foundation/synchronous_future.dart
View file @
d2110922
...
@@ -14,6 +14,8 @@ import 'dart:async';
...
@@ -14,6 +14,8 @@ import 'dart:async';
/// rare occasions you want the ability to switch to an asynchronous model. **In
/// rare occasions you want the ability to switch to an asynchronous model. **In
/// general use of this class should be avoided as it is very difficult to debug
/// general use of this class should be avoided as it is very difficult to debug
/// such bimodal behavior.**
/// such bimodal behavior.**
///
/// A [SynchronousFuture] will never complete with an error.
class
SynchronousFuture
<
T
>
implements
Future
<
T
>
{
class
SynchronousFuture
<
T
>
implements
Future
<
T
>
{
/// Creates a synchronous future.
/// Creates a synchronous future.
///
///
...
...
packages/flutter/lib/src/services/asset_bundle.dart
View file @
d2110922
...
@@ -105,18 +105,21 @@ abstract class AssetBundle {
...
@@ -105,18 +105,21 @@ abstract class AssetBundle {
/// Retrieve a string from the asset bundle, parse it with the given function,
/// Retrieve a string from the asset bundle, parse it with the given function,
/// and return that function's result.
/// and return that function's result.
///
///
/// Implementations may cache the result, so a particular key should only be
/// The result is not cached by the default implementation; the parser is run
/// used with one parser for the lifetime of the asset bundle.
/// each time the resource is fetched. However, some subclasses may implement
Future
<
T
>
loadStructuredData
<
T
>(
String
key
,
Future
<
T
>
Function
(
String
value
)
parser
);
/// caching (notably, subclasses of [CachingAssetBundle]).
Future
<
T
>
loadStructuredData
<
T
>(
String
key
,
Future
<
T
>
Function
(
String
value
)
parser
)
async
{
return
parser
(
await
loadString
(
key
));
}
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
/// and return that function's result.
/// and return that function's result.
///
///
/// Implementations may cache the result, so a particular key should only be
/// The result is not cached by the default implementation; the parser is run
/// used with one parser for the lifetime of the asset bundle.
/// each time the resource is fetched. However, some subclasses may implement
/// caching (notably, subclasses of [CachingAssetBundle]).
Future
<
T
>
loadStructuredBinaryData
<
T
>(
String
key
,
FutureOr
<
T
>
Function
(
ByteData
data
)
parser
)
async
{
Future
<
T
>
loadStructuredBinaryData
<
T
>(
String
key
,
FutureOr
<
T
>
Function
(
ByteData
data
)
parser
)
async
{
final
ByteData
data
=
await
load
(
key
);
return
parser
(
await
load
(
key
));
return
parser
(
data
);
}
}
/// If this is a caching asset bundle, and the given key describes a cached
/// If this is a caching asset bundle, and the given key describes a cached
...
@@ -161,26 +164,6 @@ class NetworkAssetBundle extends AssetBundle {
...
@@ -161,26 +164,6 @@ class NetworkAssetBundle extends AssetBundle {
return
bytes
.
buffer
.
asByteData
();
return
bytes
.
buffer
.
asByteData
();
}
}
/// Retrieve a string from the asset bundle, parse it with the given function,
/// and return the function's result.
///
/// The result is not cached. The parser is run each time the resource is
/// fetched.
@override
Future
<
T
>
loadStructuredData
<
T
>(
String
key
,
Future
<
T
>
Function
(
String
value
)
parser
)
async
{
return
parser
(
await
loadString
(
key
));
}
/// Retrieve [ByteData] from the asset bundle, parse it with the given function,
/// and return the function's result.
///
/// The result is not cached. The parser is run each time the resource is
/// fetched.
@override
Future
<
T
>
loadStructuredBinaryData
<
T
>(
String
key
,
FutureOr
<
T
>
Function
(
ByteData
data
)
parser
)
async
{
return
parser
(
await
load
(
key
));
}
// TODO(ianh): Once the underlying network logic learns about caching, we
// TODO(ianh): Once the underlying network logic learns about caching, we
// should implement evict().
// should implement evict().
...
@@ -217,30 +200,40 @@ abstract class CachingAssetBundle extends AssetBundle {
...
@@ -217,30 +200,40 @@ abstract class CachingAssetBundle extends AssetBundle {
/// unless you also fetch it with [loadString]). For any given `key`, the
/// unless you also fetch it with [loadString]). For any given `key`, the
/// `parser` is only run the first time.
/// `parser` is only run the first time.
///
///
/// Once the value has been parsed, the future returned by this function for
/// Once the value has been successfully parsed, the future returned by this
/// subsequent calls will be a [SynchronousFuture], which resolves its
/// function for subsequent calls will be a [SynchronousFuture], which
/// callback synchronously.
/// resolves its callback synchronously.
///
/// Failures are not cached, and are returned as [Future]s with errors.
@override
@override
Future
<
T
>
loadStructuredData
<
T
>(
String
key
,
Future
<
T
>
Function
(
String
value
)
parser
)
{
Future
<
T
>
loadStructuredData
<
T
>(
String
key
,
Future
<
T
>
Function
(
String
value
)
parser
)
{
if
(
_structuredDataCache
.
containsKey
(
key
))
{
if
(
_structuredDataCache
.
containsKey
(
key
))
{
return
_structuredDataCache
[
key
]!
as
Future
<
T
>;
return
_structuredDataCache
[
key
]!
as
Future
<
T
>;
}
}
Completer
<
T
>?
completer
;
// loadString can return a SynchronousFuture in certain cases, like in the
Future
<
T
>?
result
;
// flutter_test framework. So, we need to support both async and sync flows.
Completer
<
T
>?
completer
;
// For async flow.
Future
<
T
>?
synchronousResult
;
// For sync flow.
loadString
(
key
,
cache:
false
).
then
<
T
>(
parser
).
then
<
void
>((
T
value
)
{
loadString
(
key
,
cache:
false
).
then
<
T
>(
parser
).
then
<
void
>((
T
value
)
{
r
esult
=
SynchronousFuture
<
T
>(
value
);
synchronousR
esult
=
SynchronousFuture
<
T
>(
value
);
_structuredDataCache
[
key
]
=
r
esult
!;
_structuredDataCache
[
key
]
=
synchronousR
esult
!;
if
(
completer
!=
null
)
{
if
(
completer
!=
null
)
{
// We already returned from the loadStructuredData function, which means
// We already returned from the loadStructuredData function, which means
// we are in the asynchronous mode. Pass the value to the completer. The
// we are in the asynchronous mode. Pass the value to the completer. The
// completer's future is what we returned.
// completer's future is what we returned.
completer
.
complete
(
value
);
completer
.
complete
(
value
);
}
}
},
onError:
(
Object
error
,
StackTrace
stack
)
{
assert
(
completer
!=
null
,
'unexpected synchronous failure'
);
// Either loading or parsing failed. We must report the error back to the
// caller and anyone waiting on this call. We clear the cache for this
// key, however, because we want future attempts to try again.
_structuredDataCache
.
remove
(
key
);
completer
!.
completeError
(
error
,
stack
);
});
});
if
(
result
!=
null
)
{
if
(
synchronousResult
!=
null
)
{
// The code above ran synchronously, and came up with an answer.
// The above code ran synchronously. We can synchronously return the result.
// Return the SynchronousFuture that we created above.
return
synchronousResult
!;
return
result
!;
}
}
// The code above hasn't yet run its "then" handler yet. Let's prepare a
// The code above hasn't yet run its "then" handler yet. Let's prepare a
// completer for it to use when it does run.
// completer for it to use when it does run.
...
@@ -255,40 +248,41 @@ abstract class CachingAssetBundle extends AssetBundle {
...
@@ -255,40 +248,41 @@ abstract class CachingAssetBundle extends AssetBundle {
/// The result of parsing the bytedata is cached (the bytedata itself is not).
/// The result of parsing the bytedata is cached (the bytedata itself is not).
/// For any given `key`, the `parser` is only run the first time.
/// For any given `key`, the `parser` is only run the first time.
///
///
/// Once the value has been parsed, the future returned by this function for
/// Once the value has been successfully parsed, the future returned by this
/// subsequent calls will be a [SynchronousFuture], which resolves its
/// function for subsequent calls will be a [SynchronousFuture], which
/// callback synchronously.
/// resolves its callback synchronously.
///
/// Failures are not cached, and are returned as [Future]s with errors.
@override
@override
Future
<
T
>
loadStructuredBinaryData
<
T
>(
String
key
,
FutureOr
<
T
>
Function
(
ByteData
data
)
parser
)
{
Future
<
T
>
loadStructuredBinaryData
<
T
>(
String
key
,
FutureOr
<
T
>
Function
(
ByteData
data
)
parser
)
{
if
(
_structuredBinaryDataCache
.
containsKey
(
key
))
{
if
(
_structuredBinaryDataCache
.
containsKey
(
key
))
{
return
_structuredBinaryDataCache
[
key
]!
as
Future
<
T
>;
return
_structuredBinaryDataCache
[
key
]!
as
Future
<
T
>;
}
}
// load can return a SynchronousFuture in certain cases, like in the
// load can return a SynchronousFuture in certain cases, like in the
// flutter_test framework. So, we need to support both async and sync flows.
// flutter_test framework. So, we need to support both async and sync flows.
Completer
<
T
>?
completer
;
// For async flow.
Completer
<
T
>?
completer
;
// For async flow.
SynchronousFuture
<
T
>?
result
;
// For sync flow.
Future
<
T
>?
synchronousResult
;
// For sync flow.
load
(
key
).
then
<
T
>(
parser
).
then
<
void
>((
T
value
)
{
load
(
key
)
synchronousResult
=
SynchronousFuture
<
T
>(
value
);
.
then
<
T
>(
parser
)
_structuredBinaryDataCache
[
key
]
=
synchronousResult
!;
.
then
<
void
>((
T
value
)
{
if
(
completer
!=
null
)
{
result
=
SynchronousFuture
<
T
>(
value
);
// The load and parse operation ran asynchronously. We already returned
_structuredBinaryDataCache
[
key
]
=
result
!;
// from the loadStructuredBinaryData function and therefore the caller
if
(
completer
!=
null
)
{
// was given the future of the completer.
// The load and parse operation ran asynchronously. We already returned
completer
.
complete
(
value
);
// from the loadStructuredBinaryData function and therefore the caller
}
// was given the future of the completer.
},
onError:
(
Object
error
,
StackTrace
stack
)
{
completer
.
complete
(
value
);
assert
(
completer
!=
null
,
'unexpected synchronous failure'
);
}
// Either loading or parsing failed. We must report the error back to the
},
onError:
(
Object
error
,
StackTrace
stack
)
{
// caller and anyone waiting on this call. We clear the cache for this
completer
!.
completeError
(
error
,
stack
);
// key, however, because we want future attempts to try again.
});
_structuredBinaryDataCache
.
remove
(
key
);
completer
!.
completeError
(
error
,
stack
);
if
(
result
!=
null
)
{
});
if
(
synchronousResult
!=
null
)
{
// The above code ran synchronously. We can synchronously return the result.
// The above code ran synchronously. We can synchronously return the result.
return
r
esult
!;
return
synchronousR
esult
!;
}
}
// Since the above code is being run asynchronously and thus hasn't run its
// Since the above code is being run asynchronously and thus hasn't run its
// `then` handler yet, we'll return a completer that will be completed
// `then` handler yet, we'll return a completer that will be completed
// when the handler does run.
// when the handler does run.
...
...
packages/flutter/test/services/asset_bundle_test.dart
View file @
d2110922
...
@@ -135,6 +135,26 @@ void main() {
...
@@ -135,6 +135,26 @@ void main() {
expect
(
data
,
isA
<
SynchronousFuture
<
int
>>());
expect
(
data
,
isA
<
SynchronousFuture
<
int
>>());
expect
(
await
data
,
1
);
expect
(
await
data
,
1
);
});
});
testWidgets
(
'loadStructuredData handles exceptions correctly'
,
(
WidgetTester
tester
)
async
{
final
TestAssetBundle
bundle
=
TestAssetBundle
();
try
{
await
bundle
.
loadStructuredData
(
'AssetManifest.json'
,
(
String
value
)
=>
Future
<
String
>.
error
(
'what do they say?'
));
fail
(
'expected exception did not happen'
);
}
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'what do they say?'
));
}
});
testWidgets
(
'loadStructuredBinaryData handles exceptions correctly'
,
(
WidgetTester
tester
)
async
{
final
TestAssetBundle
bundle
=
TestAssetBundle
();
try
{
await
bundle
.
loadStructuredBinaryData
(
'AssetManifest.bin'
,
(
ByteData
value
)
=>
Future
<
String
>.
error
(
'buy more crystals'
));
fail
(
'expected exception did not happen'
);
}
catch
(
e
)
{
expect
(
e
.
toString
(),
contains
(
'buy more crystals'
));
}
});
});
});
test
(
'AssetImage.obtainKey succeeds with ImageConfiguration.empty'
,
()
async
{
test
(
'AssetImage.obtainKey succeeds with ImageConfiguration.empty'
,
()
async
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment