From dff6292124df5b4c92bd017d0de8e25ab8855312 Mon Sep 17 00:00:00 2001 From: Seth Ladd Date: Tue, 20 Oct 2015 11:52:12 -0700 Subject: [PATCH 01/20] first commit --- packages/flutter_image/.gitignore | 9 ++++++++ packages/flutter_image/.test_config | 3 +++ packages/flutter_image/.travis.yml | 15 +++++++++++++ packages/flutter_image/AUTHORS | 6 ++++++ packages/flutter_image/CHANGELOG.md | 5 +++++ packages/flutter_image/LICENSE | 26 +++++++++++++++++++++++ packages/flutter_image/README.md | 9 ++++++++ packages/flutter_image/lib/sample.dart | 5 +++++ packages/flutter_image/pubspec.yaml | 14 ++++++++++++ packages/flutter_image/test/all_test.dart | 12 +++++++++++ packages/flutter_image/todo.txt | 11 ++++++++++ 11 files changed, 115 insertions(+) create mode 100644 packages/flutter_image/.gitignore create mode 100644 packages/flutter_image/.test_config create mode 100644 packages/flutter_image/.travis.yml create mode 100644 packages/flutter_image/AUTHORS create mode 100644 packages/flutter_image/CHANGELOG.md create mode 100644 packages/flutter_image/LICENSE create mode 100644 packages/flutter_image/README.md create mode 100644 packages/flutter_image/lib/sample.dart create mode 100644 packages/flutter_image/pubspec.yaml create mode 100644 packages/flutter_image/test/all_test.dart create mode 100644 packages/flutter_image/todo.txt diff --git a/packages/flutter_image/.gitignore b/packages/flutter_image/.gitignore new file mode 100644 index 0000000000..25a1df3326 --- /dev/null +++ b/packages/flutter_image/.gitignore @@ -0,0 +1,9 @@ +.buildlog +.DS_Store +.idea +.pub/ +.settings/ +build/ +packages +.packages +pubspec.lock diff --git a/packages/flutter_image/.test_config b/packages/flutter_image/.test_config new file mode 100644 index 0000000000..352d2feeec --- /dev/null +++ b/packages/flutter_image/.test_config @@ -0,0 +1,3 @@ +{ + "test_package": true +} diff --git a/packages/flutter_image/.travis.yml b/packages/flutter_image/.travis.yml new file mode 100644 index 0000000000..b35c9f67d8 --- /dev/null +++ b/packages/flutter_image/.travis.yml @@ -0,0 +1,15 @@ +language: dart + +script: ./tool/travis.sh + +# Speed up builds by using containerization. Disable this if you need to use +# sudo in your scripts. +sudo: false + +branches: + only: + - master + +cache: + directories: + - $HOME/.pub-cache diff --git a/packages/flutter_image/AUTHORS b/packages/flutter_image/AUTHORS new file mode 100644 index 0000000000..e8063a8cd6 --- /dev/null +++ b/packages/flutter_image/AUTHORS @@ -0,0 +1,6 @@ +# Below is a list of people and organizations that have contributed +# to the project. Names should be added to the list like so: +# +# Name/Organization + +Google Inc. diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md new file mode 100644 index 0000000000..2a2d63cf8e --- /dev/null +++ b/packages/flutter_image/CHANGELOG.md @@ -0,0 +1,5 @@ +# Changelog + +## 0.0.1 + +- Initial version diff --git a/packages/flutter_image/LICENSE b/packages/flutter_image/LICENSE new file mode 100644 index 0000000000..c9d23b9aa9 --- /dev/null +++ b/packages/flutter_image/LICENSE @@ -0,0 +1,26 @@ +Copyright 2015, the Flutter project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/packages/flutter_image/README.md b/packages/flutter_image/README.md new file mode 100644 index 0000000000..f18a24c5d0 --- /dev/null +++ b/packages/flutter_image/README.md @@ -0,0 +1,9 @@ +# REPLACE_ME + +A library for Flutter developers. It is awesome. + +## Features and bugs + +Please file feature requests and bugs at the [issue tracker][tracker]. + +[tracker]: https://github.com/flutter/REPLACE_ME/issues diff --git a/packages/flutter_image/lib/sample.dart b/packages/flutter_image/lib/sample.dart new file mode 100644 index 0000000000..edadc95e46 --- /dev/null +++ b/packages/flutter_image/lib/sample.dart @@ -0,0 +1,5 @@ +// Copyright (c) 2015, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +main() => print('foo'); diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml new file mode 100644 index 0000000000..6c169cb1b5 --- /dev/null +++ b/packages/flutter_image/pubspec.yaml @@ -0,0 +1,14 @@ +name: REPLACE_ME +version: 0.0.1 +description: A sample library. +author: Flutter Team +homepage: https://github.com/flutter/REPLACE_ME + +environment: + sdk: '>=1.0.0 <2.0.0' + +#dependencies: +# lib_name: any +dev_dependencies: +# change this to a specific version range when you create your project + test: REPLACE_ME diff --git a/packages/flutter_image/test/all_test.dart b/packages/flutter_image/test/all_test.dart new file mode 100644 index 0000000000..4183821a01 --- /dev/null +++ b/packages/flutter_image/test/all_test.dart @@ -0,0 +1,12 @@ +// Copyright (c) 2015, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:sample/sample.dart'; +import 'package:test/test.dart'; + +main() { + group('A group of tests', () { + test('First Test', () {}); + }); +} diff --git a/packages/flutter_image/todo.txt b/packages/flutter_image/todo.txt new file mode 100644 index 0000000000..1495a55c14 --- /dev/null +++ b/packages/flutter_image/todo.txt @@ -0,0 +1,11 @@ +- rename project in pubspec.yaml +- update description in pubspec.yaml +- update the homepage: field in pubspec.yaml + +- rename project in readme.md +- update description in readme.md +- update the [tracker] url in readme.md + +- (optionally) add a codereview.settings file + +- delete todo.txt :) From 5192740b8870c108bd0c0f19bfbdef602d673aa6 Mon Sep 17 00:00:00 2001 From: Devon Carew Date: Tue, 20 Oct 2015 12:23:23 -0700 Subject: [PATCH 02/20] Update todo.txt --- packages/flutter_image/todo.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/flutter_image/todo.txt b/packages/flutter_image/todo.txt index 1495a55c14..8a638e5b57 100644 --- a/packages/flutter_image/todo.txt +++ b/packages/flutter_image/todo.txt @@ -6,6 +6,4 @@ - update description in readme.md - update the [tracker] url in readme.md -- (optionally) add a codereview.settings file - - delete todo.txt :) From 68117fe3952d119cac99b3ac8c7423435661658c Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Thu, 4 Feb 2016 15:52:14 -0800 Subject: [PATCH 03/20] Update year --- packages/flutter_image/LICENSE | 2 +- packages/flutter_image/lib/sample.dart | 2 +- packages/flutter_image/test/all_test.dart | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/flutter_image/LICENSE b/packages/flutter_image/LICENSE index c9d23b9aa9..4da9688730 100644 --- a/packages/flutter_image/LICENSE +++ b/packages/flutter_image/LICENSE @@ -1,4 +1,4 @@ -Copyright 2015, the Flutter project authors. All rights reserved. +Copyright 2016, the Flutter project authors. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: diff --git a/packages/flutter_image/lib/sample.dart b/packages/flutter_image/lib/sample.dart index edadc95e46..d05946ab79 100644 --- a/packages/flutter_image/lib/sample.dart +++ b/packages/flutter_image/lib/sample.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2015, the Flutter project authors. Please see the AUTHORS file +// Copyright (c) 2016, the Flutter project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. diff --git a/packages/flutter_image/test/all_test.dart b/packages/flutter_image/test/all_test.dart index 4183821a01..625e576d75 100644 --- a/packages/flutter_image/test/all_test.dart +++ b/packages/flutter_image/test/all_test.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2015, the Flutter project authors. Please see the AUTHORS file +// Copyright (c) 2016, the Flutter project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. From 8074366d3e14edd7a3c905af6d7819d5d327a46b Mon Sep 17 00:00:00 2001 From: Collin Jackson Date: Thu, 18 Feb 2016 09:44:52 -0800 Subject: [PATCH 04/20] remove (c) --- packages/flutter_image/lib/sample.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_image/lib/sample.dart b/packages/flutter_image/lib/sample.dart index d05946ab79..943e4916fe 100644 --- a/packages/flutter_image/lib/sample.dart +++ b/packages/flutter_image/lib/sample.dart @@ -1,4 +1,4 @@ -// Copyright (c) 2016, the Flutter project authors. Please see the AUTHORS file +// Copyright 2016, the Flutter project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. From 304707216efe10fb16cea0e684282419fa61f156 Mon Sep 17 00:00:00 2001 From: Yegor Date: Thu, 13 Jul 2017 16:12:30 -0700 Subject: [PATCH 05/20] setup repo from the template w/Travis (#1) * setup repo from the template w/Travis * unhardcode path to flutter * split out pub get; analyze specific dirs; print commands * update .analysis_options header --- packages/flutter_image/.analysis_options | 132 ++++++++++++++++++ packages/flutter_image/.gitignore | 4 +- .../flutter_image/.idea/flutter_image.iml | 17 +++ packages/flutter_image/.idea/modules.xml | 8 ++ packages/flutter_image/.test_config | 3 - packages/flutter_image/.travis.yml | 28 ++-- packages/flutter_image/README.md | 8 +- packages/flutter_image/lib/network.dart | 16 +++ packages/flutter_image/lib/sample.dart | 5 - packages/flutter_image/pubspec.yaml | 18 ++- .../test/{all_test.dart => network_test.dart} | 5 +- packages/flutter_image/todo.txt | 9 -- 12 files changed, 210 insertions(+), 43 deletions(-) create mode 100644 packages/flutter_image/.analysis_options create mode 100644 packages/flutter_image/.idea/flutter_image.iml create mode 100644 packages/flutter_image/.idea/modules.xml delete mode 100644 packages/flutter_image/.test_config create mode 100644 packages/flutter_image/lib/network.dart delete mode 100644 packages/flutter_image/lib/sample.dart rename packages/flutter_image/test/{all_test.dart => network_test.dart} (70%) delete mode 100644 packages/flutter_image/todo.txt diff --git a/packages/flutter_image/.analysis_options b/packages/flutter_image/.analysis_options new file mode 100644 index 0000000000..754054c9ce --- /dev/null +++ b/packages/flutter_image/.analysis_options @@ -0,0 +1,132 @@ +# Specify Dart code analysis options. +# +# For a list of lints, see: http://dart-lang.github.io/linter/lints/ +# See the configuration guide for more +# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer +# +# This file contains the analysis options used by Flutter editors, such as Atom. + +analyzer: + language: + enableStrictCallChecks: true + enableSuperMixins: true + enableAssertInitializer: true + strong-mode: + implicit-dynamic: false + errors: + # treat missing required parameters as a warning (not a hint) + missing_required_param: warning + # treat missing returns as a warning (not a hint) + missing_return: warning + # allow having TODOs in the code + todo: ignore + exclude: + - 'bin/cache/**' + # the following two are relative to the stocks example and the flutter package respectively + # see https://github.com/dart-lang/sdk/issues/28463 + - 'lib/i18n/stock_messages_*.dart' + - 'lib/src/http/**' + +linter: + rules: + # these rules are documented on and in the same order as + # the Dart Lint rules page to make maintenance easier + # http://dart-lang.github.io/linter/lints/ + + # === error rules === + - avoid_empty_else + - avoid_slow_async_io + - cancel_subscriptions + # - close_sinks # https://github.com/flutter/flutter/issues/5789 + # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153 + - control_flow_in_finally + - empty_statements + - hash_and_equals + # - invariant_booleans # https://github.com/flutter/flutter/issues/5790 + - iterable_contains_unrelated_type + - list_remove_unrelated_type + # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791 + - no_adjacent_strings_in_list + - no_duplicate_case_values + - test_types_in_equals + - throw_in_finally + - unrelated_type_equality_checks + - valid_regexps + + # === style rules === + - always_declare_return_types + - always_put_control_body_on_new_line + - always_require_non_null_named_parameters + - always_specify_types + - annotate_overrides + # - avoid_annotating_with_dynamic # not yet tested + - avoid_as + # - avoid_catches_without_on_clauses # not yet tested + # - avoid_catching_errors # not yet tested + # - avoid_classes_with_only_static_members # not yet tested + # - avoid_function_literals_in_foreach_calls # not yet tested + - avoid_init_to_null + - avoid_null_checks_in_equality_operators + # - avoid_positional_boolean_parameters # not yet tested + - avoid_return_types_on_setters + # - avoid_returning_null # not yet tested + # - avoid_returning_this # not yet tested + # - avoid_setters_without_getters # not yet tested + # - avoid_types_on_closure_parameters # not yet tested + - await_only_futures + - camel_case_types + # - cascade_invocations # not yet tested + # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 + - directives_ordering + - empty_catches + - empty_constructor_bodies + - implementation_imports + # - join_return_with_assignment # not yet tested + - library_names + - library_prefixes + - non_constant_identifier_names + # - omit_local_variable_types # opposite of always_specify_types + # - one_member_abstracts # too many false positives + # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 + - overridden_fields + - package_api_docs + - package_prefixed_library_names + # - parameter_assignments # we do this commonly + - prefer_adjacent_string_concatenation + - prefer_collection_literals + # - prefer_conditional_assignment # not yet tested + - prefer_const_constructors + # - prefer_constructors_over_static_methods # not yet tested + - prefer_contains + # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods + # - prefer_final_fields # https://github.com/dart-lang/linter/issues/506 + - prefer_final_locals + # - prefer_foreach # not yet tested + # - prefer_function_declarations_over_variables # not yet tested + - prefer_initializing_formals + # - prefer_interpolation_to_compose_strings # not yet tested + - prefer_is_empty + - prefer_is_not_empty + # - public_member_api_docs # this is the only difference from .analysis_options_repo + # - recursive_getters # https://github.com/dart-lang/linter/issues/452 + - slash_for_doc_comments + - sort_constructors_first + - sort_unnamed_constructors_first + - super_goes_last + # - type_annotate_public_apis # subset of always_specify_types + - type_init_formals + # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 + - unnecessary_brace_in_string_interps + - unnecessary_getters_setters + # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498 + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + # - unnecessary_overrides # https://github.com/dart-lang/linter/issues/626 and https://github.com/dart-lang/linter/issues/627 + - unnecessary_this + - use_rethrow_when_possible + # - use_setters_to_change_properties # not yet tested + # - use_string_buffers # https://github.com/dart-lang/linter/pull/664 + # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review + + # === pub rules === + - package_names diff --git a/packages/flutter_image/.gitignore b/packages/flutter_image/.gitignore index 25a1df3326..b063423aad 100644 --- a/packages/flutter_image/.gitignore +++ b/packages/flutter_image/.gitignore @@ -1,6 +1,8 @@ .buildlog .DS_Store -.idea +.idea/libraries/* +.idea/vcs.xml +.idea/workspace.xml .pub/ .settings/ build/ diff --git a/packages/flutter_image/.idea/flutter_image.iml b/packages/flutter_image/.idea/flutter_image.iml new file mode 100644 index 0000000000..8e57b7f5a9 --- /dev/null +++ b/packages/flutter_image/.idea/flutter_image.iml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/flutter_image/.idea/modules.xml b/packages/flutter_image/.idea/modules.xml new file mode 100644 index 0000000000..8cab81cac9 --- /dev/null +++ b/packages/flutter_image/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/packages/flutter_image/.test_config b/packages/flutter_image/.test_config deleted file mode 100644 index 352d2feeec..0000000000 --- a/packages/flutter_image/.test_config +++ /dev/null @@ -1,3 +0,0 @@ -{ - "test_package": true -} diff --git a/packages/flutter_image/.travis.yml b/packages/flutter_image/.travis.yml index b35c9f67d8..851489c038 100644 --- a/packages/flutter_image/.travis.yml +++ b/packages/flutter_image/.travis.yml @@ -1,15 +1,23 @@ -language: dart +os: + - linux +sudo: false + +addons: + apt: + # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 + sources: + - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version + packages: + - libstdc++6 + - fonts-droid + +before_script: + - git clone https://github.com/flutter/flutter.git -b alpha --depth 1 + - export PATH=$PATH:$(pwd)/flutter/bin + - flutter doctor script: ./tool/travis.sh -# Speed up builds by using containerization. Disable this if you need to use -# sudo in your scripts. -sudo: false - -branches: - only: - - master - cache: directories: - - $HOME/.pub-cache + - $HOME/.pub-cache diff --git a/packages/flutter_image/README.md b/packages/flutter_image/README.md index f18a24c5d0..8678d71431 100644 --- a/packages/flutter_image/README.md +++ b/packages/flutter_image/README.md @@ -1,9 +1,7 @@ -# REPLACE_ME +# Image utilities for Flutter -A library for Flutter developers. It is awesome. +Nothing yet. Stay tuned! ## Features and bugs -Please file feature requests and bugs at the [issue tracker][tracker]. - -[tracker]: https://github.com/flutter/REPLACE_ME/issues +Please file feature requests and bugs at https://github.com/flutter/flutter/issues. diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart new file mode 100644 index 0000000000..0d79637cf3 --- /dev/null +++ b/packages/flutter_image/lib/network.dart @@ -0,0 +1,16 @@ +// Copyright 2017, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// Utilities for loading images from the network. +/// +/// This library expands the capabilities of the basic [Image.network] and +/// [NetworkImage] provided by Flutter core libraries, to include a retry +/// mechanism and connectivity detection. +library network; + +import 'package:flutter/widgets.dart'; + +void main() { + const NetworkImage(''); +} diff --git a/packages/flutter_image/lib/sample.dart b/packages/flutter_image/lib/sample.dart deleted file mode 100644 index 943e4916fe..0000000000 --- a/packages/flutter_image/lib/sample.dart +++ /dev/null @@ -1,5 +0,0 @@ -// Copyright 2016, the Flutter project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -main() => print('foo'); diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 6c169cb1b5..5ebaf5e1c4 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,14 +1,18 @@ -name: REPLACE_ME +name: flutter_image version: 0.0.1 -description: A sample library. +description: > + Image utilities for Flutter: providers, effects, etc author: Flutter Team -homepage: https://github.com/flutter/REPLACE_ME +homepage: https://github.com/flutter/flutter_image environment: sdk: '>=1.0.0 <2.0.0' -#dependencies: -# lib_name: any +dependencies: + flutter: + sdk: flutter + flutter_test: + sdk: flutter + dev_dependencies: -# change this to a specific version range when you create your project - test: REPLACE_ME + test: any diff --git a/packages/flutter_image/test/all_test.dart b/packages/flutter_image/test/network_test.dart similarity index 70% rename from packages/flutter_image/test/all_test.dart rename to packages/flutter_image/test/network_test.dart index 625e576d75..eb127db404 100644 --- a/packages/flutter_image/test/all_test.dart +++ b/packages/flutter_image/test/network_test.dart @@ -1,11 +1,10 @@ -// Copyright (c) 2016, the Flutter project authors. Please see the AUTHORS file +// Copyright (c) 2017, the Flutter project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:sample/sample.dart'; import 'package:test/test.dart'; -main() { +void main() { group('A group of tests', () { test('First Test', () {}); }); diff --git a/packages/flutter_image/todo.txt b/packages/flutter_image/todo.txt deleted file mode 100644 index 8a638e5b57..0000000000 --- a/packages/flutter_image/todo.txt +++ /dev/null @@ -1,9 +0,0 @@ -- rename project in pubspec.yaml -- update description in pubspec.yaml -- update the homepage: field in pubspec.yaml - -- rename project in readme.md -- update description in readme.md -- update the [tracker] url in readme.md - -- delete todo.txt :) From 305c45c899393309a665409195fd09da4842ec2c Mon Sep 17 00:00:00 2001 From: Yegor Date: Tue, 1 Aug 2017 13:53:10 -0700 Subject: [PATCH 06/20] NetworkImageWithRetry: a retrying network image provider (#3) * implement network image with retry * NetworkImageWithRetry: a retrying network image provider * add FLUTTER_HOME to travis * move default values into constructor --- packages/flutter_image/.travis.yml | 1 + packages/flutter_image/lib/network.dart | 415 +++++++++++++++++- packages/flutter_image/test/network_test.dart | 123 +++++- .../test/network_test_server.dart | 31 ++ 4 files changed, 565 insertions(+), 5 deletions(-) create mode 100644 packages/flutter_image/test/network_test_server.dart diff --git a/packages/flutter_image/.travis.yml b/packages/flutter_image/.travis.yml index 851489c038..bd3e1068f8 100644 --- a/packages/flutter_image/.travis.yml +++ b/packages/flutter_image/.travis.yml @@ -14,6 +14,7 @@ addons: before_script: - git clone https://github.com/flutter/flutter.git -b alpha --depth 1 - export PATH=$PATH:$(pwd)/flutter/bin + - export FLUTTER_HOME=$(pwd)/flutter - flutter doctor script: ./tool/travis.sh diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index 0d79637cf3..cd86d7eae2 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -9,8 +9,419 @@ /// mechanism and connectivity detection. library network; +import 'dart:async'; +import 'dart:io' as io; +import 'dart:math' as math; +import 'dart:typed_data'; +import 'dart:ui' as ui; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -void main() { - const NetworkImage(''); +/// Fetches the image from the given URL, associating it with the given scale. +/// +/// If [fetchStrategy] is specified, uses it instead of the +/// [defaultFetchStrategy] to obtain instructions for fetching the URL. +/// +/// The image will be cached regardless of cache headers from the server. +class NetworkImageWithRetry extends ImageProvider { + /// Creates an object that fetches the image at the given [url]. + /// + /// The arguments must not be null. + const NetworkImageWithRetry(this.url, { this.scale: 1.0, this.fetchStrategy: defaultFetchStrategy }) + : assert(url != null), + assert(scale != null), + assert(fetchStrategy != null); + + /// The HTTP client used to download images. + static final io.HttpClient _client = new io.HttpClient(); + + /// The URL from which the image will be fetched. + final String url; + + /// The scale to place in the [ImageInfo] object of the image. + final double scale; + + /// The strategy used to fetch the [url] and retry when the fetch fails. + /// + /// This function is called at least once and may be called multiple times. + /// The first time it is called, it is passed a null [FetchFailure], which + /// indicates that this is the first attempt to fetch the [url]. Subsequent + /// calls pass non-null [FetchFailure] values, which indicate that previous + /// fetch attempts failed. + final FetchStrategy fetchStrategy; + + /// Used by [defaultFetchStrategy]. + /// + /// This indirection is necessary because [defaultFetchStrategy] is used as + /// the default constructor argument value, which requires that it be a const + /// expression. + static final FetchStrategy _defaultFetchStrategyFunction = const FetchStrategyBuilder().build(); + + /// The [FetchStrategy] that [NetworkImageWithRetry] uses by default. + static Future defaultFetchStrategy(Uri uri, FetchFailure failure) { + return _defaultFetchStrategyFunction(uri, failure); + } + + @override + Future obtainKey(ImageConfiguration configuration) { + return new SynchronousFuture(this); + } + + @override + ImageStreamCompleter load(NetworkImageWithRetry key) { + return new OneFrameImageStreamCompleter( + _loadWithRetry(key), + informationCollector: (StringBuffer information) { + information.writeln('Image provider: $this'); + information.write('Image key: $key'); + } + ); + } + + void _debugCheckInstructions(FetchInstructions instructions) { + assert(() { + if (instructions == null) { + if (fetchStrategy == defaultFetchStrategy) { + throw new StateError( + 'The default FetchStrategy returned null FetchInstructions. This\n' + 'is likely a bug in $runtimeType. Please file a bug at\n' + 'https://github.com/flutter/flutter/issues.' + ); + } else { + throw new StateError( + 'The custom FetchStrategy used to fetch $url returned null\n' + 'FetchInstructions. FetchInstructions must never be null, but\n' + 'instead instruct to either make another fetch attempt or give up.' + ); + } + } + return true; + }); + } + + Future _loadWithRetry(NetworkImageWithRetry key) async { + assert(key == this); + + final Stopwatch stopwatch = new Stopwatch()..start(); + final Uri resolved = Uri.base.resolve(key.url); + FetchInstructions instructions = await fetchStrategy(resolved, null); + _debugCheckInstructions(instructions); + int attemptCount = 0; + FetchFailure lastFailure; + + while (!instructions.shouldGiveUp) { + attemptCount += 1; + io.HttpClientRequest request; + try { + request = await _client.getUrl(instructions.uri).timeout(instructions.timeout); + final io.HttpClientResponse response = await request.close().timeout(instructions.timeout); + + if (response == null || response.statusCode != 200) { + throw new FetchFailure._( + totalDuration: stopwatch.elapsed, + attemptCount: attemptCount, + httpStatusCode: response.statusCode, + ); + } + + final _Uint8ListBuilder builder = await response.fold( + new _Uint8ListBuilder(), + (_Uint8ListBuilder buffer, List bytes) => buffer..add(bytes), + ).timeout(instructions.timeout); + + final Uint8List bytes = builder.data; + + if (bytes.lengthInBytes == 0) + return null; + + final ui.Image image = await decodeImageFromList(bytes); + if (image == null) + return null; + + return new ImageInfo( + image: image, + scale: key.scale, + ); + } catch (error) { + request?.close(); + lastFailure = error is FetchFailure + ? error + : new FetchFailure._( + totalDuration: stopwatch.elapsed, + attemptCount: attemptCount, + originalException: error, + ); + instructions = await fetchStrategy(instructions.uri, lastFailure); + _debugCheckInstructions(instructions); + } + } + + if (instructions.alternativeImage != null) + return instructions.alternativeImage; + + assert(lastFailure != null); + + FlutterError.onError(new FlutterErrorDetails( + exception: lastFailure, + library: 'package:flutter_image', + context: '$runtimeType failed to load ${instructions.uri}', + )); + + return null; + } + + @override + bool operator ==(dynamic other) { + if (other.runtimeType != runtimeType) + return false; + final NetworkImageWithRetry typedOther = other; + return url == typedOther.url + && scale == typedOther.scale; + } + + @override + int get hashCode => hashValues(url, scale); + + @override + String toString() => '$runtimeType("$url", scale: $scale)'; +} + +/// This function is called to get [FetchInstructions] to fetch an image. +/// +/// The instructions are executed as soon as possible after the returned +/// [Future] resolves. If a delay in necessary between retries, use a delayed +/// [Future], such as [new Future.delayed]. This is useful for implementing +/// back-off strategies and for recovering from lack of connectivity. +/// +/// [uri] is the last requested image URI. A [FetchStrategy] may choose to use +/// a different URI (see [FetchInstructions.uri]). +/// +/// If [failure] is `null`, then this is the first attempt to fetch the image. +/// +/// If the [failure] is not `null`, it contains the information about the +/// previous attempt to fetch the image. A [FetchStrategy] may attempt to +/// recover from the failure by returning [FetchInstructions] that instruct +/// [NetworkImageWithRetry] to try again. +/// +/// See [NetworkImageWithRetry.defaultFetchStrategy] for an example. +typedef Future FetchStrategy(Uri uri, FetchFailure failure); + +/// Instructions [NetworkImageWithRetry] uses to fetch the image. +@immutable +class FetchInstructions { + /// Instructs [NetworkImageWithRetry] to give up trying to download the image. + const FetchInstructions.giveUp({ + @required this.uri, + this.alternativeImage, + }) + : shouldGiveUp = true, + timeout = null; + + /// Instructs [NetworkImageWithRetry] to attempt to download the image from + /// the given [uri] and [timeout] if it takes too long. + const FetchInstructions.attempt({ + @required this.uri, + @required this.timeout, + }) : shouldGiveUp = false, + alternativeImage = null; + + /// Instructs to give up trying. + /// + /// If [alternativeImage] is `null` reports the latest [FetchFailure] to + /// [FlutterError]. + final bool shouldGiveUp; + + /// Timeout for the next network call. + final Duration timeout; + + /// The URI to use on the next attempt. + final Uri uri; + + /// Instructs to give up and use this image instead. + final Future alternativeImage; + + @override + String toString() { + return '$runtimeType(\n' + ' shouldGiveUp: $shouldGiveUp\n' + ' timeout: $timeout\n' + ' uri: $uri\n' + ' alternativeImage?: ${alternativeImage != null ? 'yes' : 'no'}\n' + ')'; + } +} + +/// Contains information about a failed attempt to fetch an image. +@immutable +class FetchFailure implements Exception { + const FetchFailure._({ + @required this.totalDuration, + @required this.attemptCount, + this.httpStatusCode, + this.originalException, + }) : assert(totalDuration != null), + assert(attemptCount > 0); + + /// The total amount of time it has taken so far to download the image. + final Duration totalDuration; + + /// The number of times [NetworkImageWithRetry] attempted to fetch the image + /// so far. + /// + /// This value starts with 1 and grows by 1 with each attempt to fetch the + /// image. + final int attemptCount; + + /// HTTP status code, such as 500. + final int httpStatusCode; + + /// The exception that caused the fetch failure. + final dynamic originalException; + + @override + String toString() { + return '$runtimeType(\n' + ' attemptCount: $attemptCount\n' + ' httpStatusCode: $httpStatusCode\n' + ' totalDuration: $totalDuration\n' + ' originalException: $originalException\n' + ')'; + } +} + +/// An indefinitely growing builder of a [Uint8List]. +class _Uint8ListBuilder { + static const int _kInitialSize = 100000; // 100KB-ish + + int _usedLength = 0; + Uint8List _buffer = new Uint8List(_kInitialSize); + + Uint8List get data => new Uint8List.view(_buffer.buffer, 0, _usedLength); + + void add(List bytes) { + _ensureCanAdd(bytes.length); + _buffer.setAll(_usedLength, bytes); + _usedLength += bytes.length; + } + + void _ensureCanAdd(int byteCount) { + final int totalSpaceNeeded = _usedLength + byteCount; + + int newLength = _buffer.length; + while (totalSpaceNeeded > newLength) { + newLength *= 2; + } + + if (newLength != _buffer.length) { + final Uint8List newBuffer = new Uint8List(newLength); + newBuffer.setAll(0, _buffer); + newBuffer.setRange(0, _usedLength, _buffer); + _buffer = newBuffer; + } + } +} + +/// Determines whether the given HTTP [statusCode] is transient. +typedef bool TransientHttpStatusCodePredicate(int statusCode); + +/// Builds a [FetchStrategy] function that retries up to a certain amount of +/// times for up to a certain amount of time. +/// +/// Pauses between retries with pauses growing exponentially (known as +/// exponential backoff). Each attempt is subject to a [timeout]. Retries only +/// those HTTP status codes considered transient by a +/// [transientHttpStatusCodePredicate] function. +class FetchStrategyBuilder { + /// A list of HTTP status codes that can generally be retried. + /// + /// You may want to use a different list depending on the needs of your + /// application. + static const List defaultTransientHttpStatusCodes = const [ + 0, // Network error + 408, // Request timeout + 500, // Internal server error + 502, // Bad gateway + 503, // Service unavailable + 504 // Gateway timeout + ]; + + /// Creates a fetch strategy builder. + /// + /// All parameters must be non-null. + const FetchStrategyBuilder({ + this.timeout: const Duration(seconds: 30), + this.totalFetchTimeout: const Duration(minutes: 1), + this.maxAttempts: 5, + this.initialPauseBetweenRetries: const Duration(seconds: 1), + this.exponentialBackoffMultiplier: 2, + this.transientHttpStatusCodePredicate: defaultTransientHttpStatusCodePredicate, + }) : assert(timeout != null), + assert(totalFetchTimeout != null), + assert(maxAttempts != null), + assert(initialPauseBetweenRetries != null), + assert(exponentialBackoffMultiplier != null), + assert(transientHttpStatusCodePredicate != null); + + /// Maximum amount of time a single fetch attempt is allowed to take. + final Duration timeout; + + /// A strategy built by this builder will retry for up to this amount of time + /// before giving up. + final Duration totalFetchTimeout; + + /// Maximum number of attempts a strategy will make before giving up. + final int maxAttempts; + + /// Initial amount of time between retries. + final Duration initialPauseBetweenRetries; + + /// The pause between retries is multiplied by this number with each attempt, + /// causing it to grow exponentially. + final int exponentialBackoffMultiplier; + + /// A function that determines whether a given HTTP status code should be + /// retried. + final TransientHttpStatusCodePredicate transientHttpStatusCodePredicate; + + /// Uses [defaultTransientHttpStatusCodes] to determine if the [statusCode] is + /// transient. + static bool defaultTransientHttpStatusCodePredicate(int statusCode) { + return defaultTransientHttpStatusCodes.contains(statusCode); + } + + /// Builds a [FetchStrategy] that operates using the properties of this + /// builder. + FetchStrategy build() { + return (Uri uri, FetchFailure failure) async { + if (failure == null) { + // First attempt. Just load. + return new FetchInstructions.attempt( + uri: uri, + timeout: timeout, + ); + } + + final bool isRetriableFailure = transientHttpStatusCodePredicate(failure.httpStatusCode) || + failure.originalException is io.SocketException; + + // If cannot retry, give up. + if (!isRetriableFailure || // retrying will not help + failure.totalDuration > totalFetchTimeout || // taking too long + failure.attemptCount > maxAttempts) { // too many attempts + return new FetchInstructions.giveUp(uri: uri); + } + + // Exponential back-off. + final Duration pauseBetweenRetries = initialPauseBetweenRetries * math.pow(exponentialBackoffMultiplier, failure.attemptCount - 1); + await new Future.delayed(pauseBetweenRetries); + + // Retry. + return new FetchInstructions.attempt( + uri: uri, + timeout: timeout, + ); + }; + } } diff --git a/packages/flutter_image/test/network_test.dart b/packages/flutter_image/test/network_test.dart index eb127db404..a5d9b9bc82 100644 --- a/packages/flutter_image/test/network_test.dart +++ b/packages/flutter_image/test/network_test.dart @@ -2,10 +2,127 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:test/test.dart'; +import 'dart:async'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_image/network.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:quiver/testing/async.dart'; + +String _imageUrl(String fileName) { + return 'http://localhost:11111/$fileName'; +} void main() { - group('A group of tests', () { - test('First Test', () {}); + group('NetworkImageWithRetry', () { + setUp(() { + FlutterError.onError = (FlutterErrorDetails error) { + fail('$error'); + }; + }); + + tearDown(() { + FlutterError.onError = FlutterError.dumpErrorToConsole; + }); + + test('loads image from network', () async { + final NetworkImageWithRetry subject = new NetworkImageWithRetry( + _imageUrl('immediate_success.png'), + ); + + subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(image.image.height, 1); + expect(image.image.width, 1); + })); + }); + + test('retries 6 times then gives up', () async { + final List errorLog = []; + FlutterError.onError = errorLog.add; + + final FakeAsync fakeAsync = new FakeAsync(); + final dynamic maxAttemptCountReached = expectAsync0(() {}); + + int attemptCount = 0; + Future onAttempt() async { + expect(attemptCount, lessThan(7)); + if (attemptCount == 6) { + maxAttemptCountReached(); + } + await new Future.delayed(Duration.ZERO); + fakeAsync.elapse(const Duration(seconds: 60)); + attemptCount++; + } + + final NetworkImageWithRetry subject = new NetworkImageWithRetry( + _imageUrl('error.png'), + fetchStrategy: (Uri uri, FetchFailure failure) { + Timer.run(onAttempt); + return fakeAsync.run((FakeAsync fakeAsync) { + return NetworkImageWithRetry.defaultFetchStrategy(uri, failure); + }); + }, + ); + + subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(errorLog.single.exception, const isInstanceOf()); + expect(image, null); + })); + }); + + test('gives up immediately on non-retriable errors (HTTP 404)', () async { + final List errorLog = []; + FlutterError.onError = errorLog.add; + + final FakeAsync fakeAsync = new FakeAsync(); + + int attemptCount = 0; + Future onAttempt() async { + expect(attemptCount, lessThan(2)); + await new Future.delayed(Duration.ZERO); + fakeAsync.elapse(const Duration(seconds: 60)); + attemptCount++; + } + + final NetworkImageWithRetry subject = new NetworkImageWithRetry( + _imageUrl('does_not_exist.png'), + fetchStrategy: (Uri uri, FetchFailure failure) { + Timer.run(onAttempt); + return fakeAsync.run((FakeAsync fakeAsync) { + return NetworkImageWithRetry.defaultFetchStrategy(uri, failure); + }); + }, + ); + + subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(errorLog.single.exception, const isInstanceOf()); + expect(image, null); + })); + }); + + test('succeeds on successful retry', () async { + final NetworkImageWithRetry subject = new NetworkImageWithRetry( + _imageUrl('error.png'), + fetchStrategy: (Uri uri, FetchFailure failure) async { + if (failure == null) { + return new FetchInstructions.attempt( + uri: uri, + timeout: const Duration(minutes: 1), + ); + } else { + expect(failure.attemptCount, lessThan(2)); + return new FetchInstructions.attempt( + uri: Uri.parse(_imageUrl('immediate_success.png')), + timeout: const Duration(minutes: 1), + ); + } + }, + ); + + subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(image.image.height, 1); + expect(image.image.width, 1); + })); + }); }); } diff --git a/packages/flutter_image/test/network_test_server.dart b/packages/flutter_image/test/network_test_server.dart new file mode 100644 index 0000000000..aa5c212fcd --- /dev/null +++ b/packages/flutter_image/test/network_test_server.dart @@ -0,0 +1,31 @@ +// Copyright (c) 2017, the Flutter project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; +import 'dart:io'; + +const int _kTestServerPort = 11111; + +Future main() async { + final HttpServer testServer = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, _kTestServerPort); + await for (HttpRequest request in testServer) { + if (request.uri.path.endsWith('/immediate_success.png')) { + request.response.add(_kTransparentImage); + } else if (request.uri.path.endsWith('/error.png')) { + request.response.statusCode = 500; + } else { + request.response.statusCode = 404; + } + await request.response.flush(); + await request.response.close(); + } +} + +const List _kTransparentImage = const [ + 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, + 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, + 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, + 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D, + 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, +]; From 9f17b2d75e59f1f7b93194caf885c5cd7427fca2 Mon Sep 17 00:00:00 2001 From: Yegor Date: Wed, 9 Aug 2017 13:11:23 -0700 Subject: [PATCH 07/20] depend on quiver explicitly (#4) --- packages/flutter_image/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 5ebaf5e1c4..1fd0cab25b 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -15,4 +15,5 @@ dependencies: sdk: flutter dev_dependencies: + quiver: '>=0.24.0 <2.0.0' test: any From c3d901ddd0cc568ae5173d8bc44ea5d53a5a31a4 Mon Sep 17 00:00:00 2001 From: Yegor Date: Mon, 18 Sep 2017 12:48:18 -0700 Subject: [PATCH 08/20] Add NetworkImageWithRetry to README.md (#5) --- packages/flutter_image/README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/flutter_image/README.md b/packages/flutter_image/README.md index 8678d71431..f31adcd98f 100644 --- a/packages/flutter_image/README.md +++ b/packages/flutter_image/README.md @@ -1,6 +1,21 @@ # Image utilities for Flutter -Nothing yet. Stay tuned! +## NetworkImageWithRetry + +Use `NetworkImageWithRetry` instead of `Image.network` to load images from the +network with a retry mechanism. + +Example: + +```dart +var avatar = new Image( + image: new NetworkImageWithRetry('http://example.com/avatars/123.jpg'), +); +``` + +The retry mechanism may be customized by supplying a custom `FetchStrategy` +function. `FetchStrategyBuilder` is a utility class that helps building fetch +strategy functions. ## Features and bugs From 396d069a58858f4c396fe272987c28009cf9cb89 Mon Sep 17 00:00:00 2001 From: Yegor Date: Mon, 18 Sep 2017 12:55:36 -0700 Subject: [PATCH 09/20] version bump; changelog (#6) --- packages/flutter_image/CHANGELOG.md | 6 +++++- packages/flutter_image/pubspec.yaml | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 2a2d63cf8e..3ea3ef46e0 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 0.0.2 + +- Add `NetworkImageWithRetry`, an `ImageProvider` with a retry mechanism. + ## 0.0.1 -- Initial version +- Contains no useful code. diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 1fd0cab25b..3da846bb6c 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 0.0.1 +version: 0.0.2 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team From eec04acfcb4c40b8d98514796e48ca39bc5f40ce Mon Sep 17 00:00:00 2001 From: Yegor Date: Fri, 29 Sep 2017 22:12:15 -0700 Subject: [PATCH 10/20] allow using double for exponentialBackoffMultiplier (#7) --- packages/flutter_image/lib/network.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index cd86d7eae2..c3668f2ecf 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -379,7 +379,7 @@ class FetchStrategyBuilder { /// The pause between retries is multiplied by this number with each attempt, /// causing it to grow exponentially. - final int exponentialBackoffMultiplier; + final num exponentialBackoffMultiplier; /// A function that determines whether a given HTTP status code should be /// retried. From a4d88c7754648d1759f4d145076bebf26cd47c6a Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Thu, 18 Jan 2018 08:52:36 -0800 Subject: [PATCH 11/20] Move flutter_test to dev_dependencies (#8) --- packages/flutter_image/CHANGELOG.md | 5 +++++ packages/flutter_image/lib/network.dart | 1 - packages/flutter_image/pubspec.yaml | 6 +++--- packages/flutter_image/test/network_test.dart | 2 +- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 3ea3ef46e0..6314931b30 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 0.0.3 + +- Moved `flutter_test` to dev_dependencies in `pubspec.yaml`, and fixed issues +flagged by the analyzer. + ## 0.0.2 - Add `NetworkImageWithRetry`, an `ImageProvider` with a retry mechanism. diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index c3668f2ecf..d66ac5222d 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -16,7 +16,6 @@ import 'dart:typed_data'; import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; /// Fetches the image from the given URL, associating it with the given scale. diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 3da846bb6c..d4380cbc54 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 0.0.2 +version: 0.0.3 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team @@ -11,9 +11,9 @@ environment: dependencies: flutter: sdk: flutter - flutter_test: - sdk: flutter dev_dependencies: quiver: '>=0.24.0 <2.0.0' test: any + flutter_test: + sdk: flutter diff --git a/packages/flutter_image/test/network_test.dart b/packages/flutter_image/test/network_test.dart index a5d9b9bc82..8a734efc3c 100644 --- a/packages/flutter_image/test/network_test.dart +++ b/packages/flutter_image/test/network_test.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter/painting.dart'; import 'package:flutter_image/network.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:quiver/testing/async.dart'; From bf0ff135bb0bf45add07271fecc90ff53b9c6ffe Mon Sep 17 00:00:00 2001 From: pera Date: Fri, 9 Mar 2018 21:55:44 -0300 Subject: [PATCH 12/20] Fix `FetchInstructions` assertion (#9) --- packages/flutter_image/lib/network.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index d66ac5222d..08b9342ca3 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -97,7 +97,7 @@ class NetworkImageWithRetry extends ImageProvider { } } return true; - }); + }()); } Future _loadWithRetry(NetworkImageWithRetry key) async { From fbe88266958f9f26678952ff3c019bd1bc781d25 Mon Sep 17 00:00:00 2001 From: xster Date: Tue, 20 Mar 2018 21:24:43 -0700 Subject: [PATCH 13/20] Update package version and bump to dart2 (#10) * Update package version and bump to dart2 * Fix travis test --- packages/flutter_image/.travis.yml | 2 +- packages/flutter_image/CHANGELOG.md | 4 ++++ packages/flutter_image/pubspec.yaml | 9 +++++---- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/flutter_image/.travis.yml b/packages/flutter_image/.travis.yml index bd3e1068f8..9ac6cd490c 100644 --- a/packages/flutter_image/.travis.yml +++ b/packages/flutter_image/.travis.yml @@ -12,7 +12,7 @@ addons: - fonts-droid before_script: - - git clone https://github.com/flutter/flutter.git -b alpha --depth 1 + - git clone https://github.com/flutter/flutter.git -b beta --depth 1 - export PATH=$PATH:$(pwd)/flutter/bin - export FLUTTER_HOME=$(pwd)/flutter - flutter doctor diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 6314931b30..966b82eb07 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 1.0.0 + +* **Breaking change**. SDK constraints to support Flutter beta versions and Dart 2 only. + ## 0.0.3 - Moved `flutter_test` to dev_dependencies in `pubspec.yaml`, and fixed issues diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index d4380cbc54..56585ca9ed 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,13 +1,10 @@ name: flutter_image -version: 0.0.3 +version: 1.0.0 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team homepage: https://github.com/flutter/flutter_image -environment: - sdk: '>=1.0.0 <2.0.0' - dependencies: flutter: sdk: flutter @@ -17,3 +14,7 @@ dev_dependencies: test: any flutter_test: sdk: flutter + +environment: + sdk: ">=2.0.0-dev.28.0 <3.0.0" + flutter: ">=0.1.4 <2.0.0" \ No newline at end of file From 46273065f0de17abaa749e930470ce122f3149d9 Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Wed, 1 May 2019 15:37:42 -0700 Subject: [PATCH 14/20] Updates for FlutterError refactor (#13) --- packages/flutter_image/CHANGELOG.md | 4 ++++ packages/flutter_image/lib/network.dart | 8 ++++---- packages/flutter_image/pubspec.yaml | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 966b82eb07..65fab2f09b 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.0 + +* **Breaking change**. Updates for Flutter 1.5.9. + ## 1.0.0 * **Breaking change**. SDK constraints to support Flutter beta versions and Dart 2 only. diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index 08b9342ca3..09493161ef 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -72,9 +72,9 @@ class NetworkImageWithRetry extends ImageProvider { ImageStreamCompleter load(NetworkImageWithRetry key) { return new OneFrameImageStreamCompleter( _loadWithRetry(key), - informationCollector: (StringBuffer information) { - information.writeln('Image provider: $this'); - information.write('Image key: $key'); + informationCollector: () sync* { + yield ErrorDescription('Image provider: $this'); + yield ErrorDescription('Image key: $key'); } ); } @@ -165,7 +165,7 @@ class NetworkImageWithRetry extends ImageProvider { FlutterError.onError(new FlutterErrorDetails( exception: lastFailure, library: 'package:flutter_image', - context: '$runtimeType failed to load ${instructions.uri}', + context: ErrorDescription('$runtimeType failed to load ${instructions.uri}'), )); return null; diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 56585ca9ed..f7e1870775 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 1.0.0 +version: 2.0.0-dev description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team @@ -17,4 +17,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=0.1.4 <2.0.0" \ No newline at end of file + flutter: ">=1.5.9 <2.0.0" From f2ecb97abb1e871a57d07b1a14f593c0a277307c Mon Sep 17 00:00:00 2001 From: Zachary Anderson Date: Wed, 1 May 2019 22:08:40 -0700 Subject: [PATCH 15/20] Fix SDK constraint (#14) --- packages/flutter_image/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index f7e1870775..eb48176004 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -17,4 +17,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.9 <2.0.0" + flutter: ">=1.5.9-pre.94 <2.0.0" From 72730d3dd43d5b317086f067e6059c1fb7fd8fb5 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Tue, 9 Jul 2019 18:09:35 -0700 Subject: [PATCH 16/20] Update flutter_image per recent changes to framework (#15) * Update analysis options * Clean up analysis errors * Update/fix broken API calls https://github.com/flutter/flutter/issues/31962 --- packages/flutter_image/.analysis_options | 132 ------------------ packages/flutter_image/lib/network.dart | 81 +++++------ packages/flutter_image/pubspec.yaml | 4 +- packages/flutter_image/test/network_test.dart | 68 +++++---- .../test/network_test_server.dart | 6 +- 5 files changed, 86 insertions(+), 205 deletions(-) delete mode 100644 packages/flutter_image/.analysis_options diff --git a/packages/flutter_image/.analysis_options b/packages/flutter_image/.analysis_options deleted file mode 100644 index 754054c9ce..0000000000 --- a/packages/flutter_image/.analysis_options +++ /dev/null @@ -1,132 +0,0 @@ -# Specify Dart code analysis options. -# -# For a list of lints, see: http://dart-lang.github.io/linter/lints/ -# See the configuration guide for more -# https://github.com/dart-lang/sdk/tree/master/pkg/analyzer#configuring-the-analyzer -# -# This file contains the analysis options used by Flutter editors, such as Atom. - -analyzer: - language: - enableStrictCallChecks: true - enableSuperMixins: true - enableAssertInitializer: true - strong-mode: - implicit-dynamic: false - errors: - # treat missing required parameters as a warning (not a hint) - missing_required_param: warning - # treat missing returns as a warning (not a hint) - missing_return: warning - # allow having TODOs in the code - todo: ignore - exclude: - - 'bin/cache/**' - # the following two are relative to the stocks example and the flutter package respectively - # see https://github.com/dart-lang/sdk/issues/28463 - - 'lib/i18n/stock_messages_*.dart' - - 'lib/src/http/**' - -linter: - rules: - # these rules are documented on and in the same order as - # the Dart Lint rules page to make maintenance easier - # http://dart-lang.github.io/linter/lints/ - - # === error rules === - - avoid_empty_else - - avoid_slow_async_io - - cancel_subscriptions - # - close_sinks # https://github.com/flutter/flutter/issues/5789 - # - comment_references # blocked on https://github.com/dart-lang/dartdoc/issues/1153 - - control_flow_in_finally - - empty_statements - - hash_and_equals - # - invariant_booleans # https://github.com/flutter/flutter/issues/5790 - - iterable_contains_unrelated_type - - list_remove_unrelated_type - # - literal_only_boolean_expressions # https://github.com/flutter/flutter/issues/5791 - - no_adjacent_strings_in_list - - no_duplicate_case_values - - test_types_in_equals - - throw_in_finally - - unrelated_type_equality_checks - - valid_regexps - - # === style rules === - - always_declare_return_types - - always_put_control_body_on_new_line - - always_require_non_null_named_parameters - - always_specify_types - - annotate_overrides - # - avoid_annotating_with_dynamic # not yet tested - - avoid_as - # - avoid_catches_without_on_clauses # not yet tested - # - avoid_catching_errors # not yet tested - # - avoid_classes_with_only_static_members # not yet tested - # - avoid_function_literals_in_foreach_calls # not yet tested - - avoid_init_to_null - - avoid_null_checks_in_equality_operators - # - avoid_positional_boolean_parameters # not yet tested - - avoid_return_types_on_setters - # - avoid_returning_null # not yet tested - # - avoid_returning_this # not yet tested - # - avoid_setters_without_getters # not yet tested - # - avoid_types_on_closure_parameters # not yet tested - - await_only_futures - - camel_case_types - # - cascade_invocations # not yet tested - # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204 - - directives_ordering - - empty_catches - - empty_constructor_bodies - - implementation_imports - # - join_return_with_assignment # not yet tested - - library_names - - library_prefixes - - non_constant_identifier_names - # - omit_local_variable_types # opposite of always_specify_types - # - one_member_abstracts # too many false positives - # - only_throw_errors # https://github.com/flutter/flutter/issues/5792 - - overridden_fields - - package_api_docs - - package_prefixed_library_names - # - parameter_assignments # we do this commonly - - prefer_adjacent_string_concatenation - - prefer_collection_literals - # - prefer_conditional_assignment # not yet tested - - prefer_const_constructors - # - prefer_constructors_over_static_methods # not yet tested - - prefer_contains - # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - # - prefer_final_fields # https://github.com/dart-lang/linter/issues/506 - - prefer_final_locals - # - prefer_foreach # not yet tested - # - prefer_function_declarations_over_variables # not yet tested - - prefer_initializing_formals - # - prefer_interpolation_to_compose_strings # not yet tested - - prefer_is_empty - - prefer_is_not_empty - # - public_member_api_docs # this is the only difference from .analysis_options_repo - # - recursive_getters # https://github.com/dart-lang/linter/issues/452 - - slash_for_doc_comments - - sort_constructors_first - - sort_unnamed_constructors_first - - super_goes_last - # - type_annotate_public_apis # subset of always_specify_types - - type_init_formals - # - unawaited_futures # https://github.com/flutter/flutter/issues/5793 - - unnecessary_brace_in_string_interps - - unnecessary_getters_setters - # - unnecessary_lambdas # https://github.com/dart-lang/linter/issues/498 - - unnecessary_null_aware_assignments - - unnecessary_null_in_if_null_operators - # - unnecessary_overrides # https://github.com/dart-lang/linter/issues/626 and https://github.com/dart-lang/linter/issues/627 - - unnecessary_this - - use_rethrow_when_possible - # - use_setters_to_change_properties # not yet tested - # - use_string_buffers # https://github.com/dart-lang/linter/pull/664 - # - use_to_and_as_if_applicable # has false positives, so we prefer to catch this by code-review - - # === pub rules === - - package_names diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index 09493161ef..abffcdc3c6 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -24,17 +24,18 @@ import 'package:flutter/widgets.dart'; /// [defaultFetchStrategy] to obtain instructions for fetching the URL. /// /// The image will be cached regardless of cache headers from the server. +@immutable class NetworkImageWithRetry extends ImageProvider { /// Creates an object that fetches the image at the given [url]. /// /// The arguments must not be null. - const NetworkImageWithRetry(this.url, { this.scale: 1.0, this.fetchStrategy: defaultFetchStrategy }) + const NetworkImageWithRetry(this.url, { this.scale = 1.0, this.fetchStrategy = defaultFetchStrategy }) : assert(url != null), assert(scale != null), assert(fetchStrategy != null); /// The HTTP client used to download images. - static final io.HttpClient _client = new io.HttpClient(); + static final io.HttpClient _client = io.HttpClient(); /// The URL from which the image will be fetched. final String url; @@ -65,12 +66,12 @@ class NetworkImageWithRetry extends ImageProvider { @override Future obtainKey(ImageConfiguration configuration) { - return new SynchronousFuture(this); + return SynchronousFuture(this); } @override ImageStreamCompleter load(NetworkImageWithRetry key) { - return new OneFrameImageStreamCompleter( + return OneFrameImageStreamCompleter( _loadWithRetry(key), informationCollector: () sync* { yield ErrorDescription('Image provider: $this'); @@ -83,13 +84,13 @@ class NetworkImageWithRetry extends ImageProvider { assert(() { if (instructions == null) { if (fetchStrategy == defaultFetchStrategy) { - throw new StateError( + throw StateError( 'The default FetchStrategy returned null FetchInstructions. This\n' 'is likely a bug in $runtimeType. Please file a bug at\n' 'https://github.com/flutter/flutter/issues.' ); } else { - throw new StateError( + throw StateError( 'The custom FetchStrategy used to fetch $url returned null\n' 'FetchInstructions. FetchInstructions must never be null, but\n' 'instead instruct to either make another fetch attempt or give up.' @@ -103,7 +104,7 @@ class NetworkImageWithRetry extends ImageProvider { Future _loadWithRetry(NetworkImageWithRetry key) async { assert(key == this); - final Stopwatch stopwatch = new Stopwatch()..start(); + final Stopwatch stopwatch = Stopwatch()..start(); final Uri resolved = Uri.base.resolve(key.url); FetchInstructions instructions = await fetchStrategy(resolved, null); _debugCheckInstructions(instructions); @@ -118,7 +119,7 @@ class NetworkImageWithRetry extends ImageProvider { final io.HttpClientResponse response = await request.close().timeout(instructions.timeout); if (response == null || response.statusCode != 200) { - throw new FetchFailure._( + throw FetchFailure._( totalDuration: stopwatch.elapsed, attemptCount: attemptCount, httpStatusCode: response.statusCode, @@ -126,7 +127,7 @@ class NetworkImageWithRetry extends ImageProvider { } final _Uint8ListBuilder builder = await response.fold( - new _Uint8ListBuilder(), + _Uint8ListBuilder(), (_Uint8ListBuilder buffer, List bytes) => buffer..add(bytes), ).timeout(instructions.timeout); @@ -139,7 +140,7 @@ class NetworkImageWithRetry extends ImageProvider { if (image == null) return null; - return new ImageInfo( + return ImageInfo( image: image, scale: key.scale, ); @@ -147,7 +148,7 @@ class NetworkImageWithRetry extends ImageProvider { request?.close(); lastFailure = error is FetchFailure ? error - : new FetchFailure._( + : FetchFailure._( totalDuration: stopwatch.elapsed, attemptCount: attemptCount, originalException: error, @@ -162,7 +163,7 @@ class NetworkImageWithRetry extends ImageProvider { assert(lastFailure != null); - FlutterError.onError(new FlutterErrorDetails( + FlutterError.onError(FlutterErrorDetails( exception: lastFailure, library: 'package:flutter_image', context: ErrorDescription('$runtimeType failed to load ${instructions.uri}'), @@ -191,7 +192,7 @@ class NetworkImageWithRetry extends ImageProvider { /// /// The instructions are executed as soon as possible after the returned /// [Future] resolves. If a delay in necessary between retries, use a delayed -/// [Future], such as [new Future.delayed]. This is useful for implementing +/// [Future], such as [Future.delayed]. This is useful for implementing /// back-off strategies and for recovering from lack of connectivity. /// /// [uri] is the last requested image URI. A [FetchStrategy] may choose to use @@ -205,7 +206,7 @@ class NetworkImageWithRetry extends ImageProvider { /// [NetworkImageWithRetry] to try again. /// /// See [NetworkImageWithRetry.defaultFetchStrategy] for an example. -typedef Future FetchStrategy(Uri uri, FetchFailure failure); +typedef FetchStrategy = Future Function(Uri uri, FetchFailure failure); /// Instructions [NetworkImageWithRetry] uses to fetch the image. @immutable @@ -295,9 +296,9 @@ class _Uint8ListBuilder { static const int _kInitialSize = 100000; // 100KB-ish int _usedLength = 0; - Uint8List _buffer = new Uint8List(_kInitialSize); + Uint8List _buffer = Uint8List(_kInitialSize); - Uint8List get data => new Uint8List.view(_buffer.buffer, 0, _usedLength); + Uint8List get data => Uint8List.view(_buffer.buffer, 0, _usedLength); void add(List bytes) { _ensureCanAdd(bytes.length); @@ -314,7 +315,7 @@ class _Uint8ListBuilder { } if (newLength != _buffer.length) { - final Uint8List newBuffer = new Uint8List(newLength); + final Uint8List newBuffer = Uint8List(newLength); newBuffer.setAll(0, _buffer); newBuffer.setRange(0, _usedLength, _buffer); _buffer = newBuffer; @@ -323,7 +324,7 @@ class _Uint8ListBuilder { } /// Determines whether the given HTTP [statusCode] is transient. -typedef bool TransientHttpStatusCodePredicate(int statusCode); +typedef TransientHttpStatusCodePredicate = bool Function(int statusCode); /// Builds a [FetchStrategy] function that retries up to a certain amount of /// times for up to a certain amount of time. @@ -333,11 +334,28 @@ typedef bool TransientHttpStatusCodePredicate(int statusCode); /// those HTTP status codes considered transient by a /// [transientHttpStatusCodePredicate] function. class FetchStrategyBuilder { + /// Creates a fetch strategy builder. + /// + /// All parameters must be non-null. + const FetchStrategyBuilder({ + this.timeout = const Duration(seconds: 30), + this.totalFetchTimeout = const Duration(minutes: 1), + this.maxAttempts = 5, + this.initialPauseBetweenRetries = const Duration(seconds: 1), + this.exponentialBackoffMultiplier = 2, + this.transientHttpStatusCodePredicate = defaultTransientHttpStatusCodePredicate, + }) : assert(timeout != null), + assert(totalFetchTimeout != null), + assert(maxAttempts != null), + assert(initialPauseBetweenRetries != null), + assert(exponentialBackoffMultiplier != null), + assert(transientHttpStatusCodePredicate != null); + /// A list of HTTP status codes that can generally be retried. /// /// You may want to use a different list depending on the needs of your /// application. - static const List defaultTransientHttpStatusCodes = const [ + static const List defaultTransientHttpStatusCodes = [ 0, // Network error 408, // Request timeout 500, // Internal server error @@ -346,23 +364,6 @@ class FetchStrategyBuilder { 504 // Gateway timeout ]; - /// Creates a fetch strategy builder. - /// - /// All parameters must be non-null. - const FetchStrategyBuilder({ - this.timeout: const Duration(seconds: 30), - this.totalFetchTimeout: const Duration(minutes: 1), - this.maxAttempts: 5, - this.initialPauseBetweenRetries: const Duration(seconds: 1), - this.exponentialBackoffMultiplier: 2, - this.transientHttpStatusCodePredicate: defaultTransientHttpStatusCodePredicate, - }) : assert(timeout != null), - assert(totalFetchTimeout != null), - assert(maxAttempts != null), - assert(initialPauseBetweenRetries != null), - assert(exponentialBackoffMultiplier != null), - assert(transientHttpStatusCodePredicate != null); - /// Maximum amount of time a single fetch attempt is allowed to take. final Duration timeout; @@ -396,7 +397,7 @@ class FetchStrategyBuilder { return (Uri uri, FetchFailure failure) async { if (failure == null) { // First attempt. Just load. - return new FetchInstructions.attempt( + return FetchInstructions.attempt( uri: uri, timeout: timeout, ); @@ -409,15 +410,15 @@ class FetchStrategyBuilder { if (!isRetriableFailure || // retrying will not help failure.totalDuration > totalFetchTimeout || // taking too long failure.attemptCount > maxAttempts) { // too many attempts - return new FetchInstructions.giveUp(uri: uri); + return FetchInstructions.giveUp(uri: uri); } // Exponential back-off. final Duration pauseBetweenRetries = initialPauseBetweenRetries * math.pow(exponentialBackoffMultiplier, failure.attemptCount - 1); - await new Future.delayed(pauseBetweenRetries); + await Future.delayed(pauseBetweenRetries); // Retry. - return new FetchInstructions.attempt( + return FetchInstructions.attempt( uri: uri, timeout: timeout, ); diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index eb48176004..b7ffcdd66e 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 2.0.0-dev +version: 2.0.0 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team @@ -10,7 +10,7 @@ dependencies: sdk: flutter dev_dependencies: - quiver: '>=0.24.0 <2.0.0' + quiver: '>=0.24.0 <3.0.0' test: any flutter_test: sdk: flutter diff --git a/packages/flutter_image/test/network_test.dart b/packages/flutter_image/test/network_test.dart index 8a734efc3c..46147ae905 100644 --- a/packages/flutter_image/test/network_test.dart +++ b/packages/flutter_image/test/network_test.dart @@ -3,6 +3,7 @@ // BSD-style license that can be found in the LICENSE file. import 'dart:async'; +import 'dart:io' show HttpOverrides; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; import 'package:flutter_image/network.dart'; @@ -14,6 +15,9 @@ String _imageUrl(String fileName) { } void main() { + AutomatedTestWidgetsFlutterBinding(); + HttpOverrides.global = null; + group('NetworkImageWithRetry', () { setUp(() { FlutterError.onError = (FlutterErrorDetails error) { @@ -26,35 +30,37 @@ void main() { }); test('loads image from network', () async { - final NetworkImageWithRetry subject = new NetworkImageWithRetry( + final NetworkImageWithRetry subject = NetworkImageWithRetry( _imageUrl('immediate_success.png'), ); - subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(image.image.height, 1); - expect(image.image.width, 1); - })); + subject.load(subject).addListener( + ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(image.image.height, 1); + expect(image.image.width, 1); + })), + ); }); test('retries 6 times then gives up', () async { final List errorLog = []; FlutterError.onError = errorLog.add; - final FakeAsync fakeAsync = new FakeAsync(); + final FakeAsync fakeAsync = FakeAsync(); final dynamic maxAttemptCountReached = expectAsync0(() {}); int attemptCount = 0; - Future onAttempt() async { + Future onAttempt() async { expect(attemptCount, lessThan(7)); if (attemptCount == 6) { maxAttemptCountReached(); } - await new Future.delayed(Duration.ZERO); + await Future.delayed(Duration.zero); fakeAsync.elapse(const Duration(seconds: 60)); attemptCount++; } - final NetworkImageWithRetry subject = new NetworkImageWithRetry( + final NetworkImageWithRetry subject = NetworkImageWithRetry( _imageUrl('error.png'), fetchStrategy: (Uri uri, FetchFailure failure) { Timer.run(onAttempt); @@ -64,27 +70,29 @@ void main() { }, ); - subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(errorLog.single.exception, const isInstanceOf()); - expect(image, null); - })); + subject.load(subject).addListener( + ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(errorLog.single.exception, isInstanceOf()); + expect(image, null); + })), + ); }); test('gives up immediately on non-retriable errors (HTTP 404)', () async { final List errorLog = []; FlutterError.onError = errorLog.add; - final FakeAsync fakeAsync = new FakeAsync(); + final FakeAsync fakeAsync = FakeAsync(); int attemptCount = 0; - Future onAttempt() async { + Future onAttempt() async { expect(attemptCount, lessThan(2)); - await new Future.delayed(Duration.ZERO); + await Future.delayed(Duration.zero); fakeAsync.elapse(const Duration(seconds: 60)); attemptCount++; } - final NetworkImageWithRetry subject = new NetworkImageWithRetry( + final NetworkImageWithRetry subject = NetworkImageWithRetry( _imageUrl('does_not_exist.png'), fetchStrategy: (Uri uri, FetchFailure failure) { Timer.run(onAttempt); @@ -94,24 +102,26 @@ void main() { }, ); - subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(errorLog.single.exception, const isInstanceOf()); - expect(image, null); - })); + subject.load(subject).addListener( + ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(errorLog.single.exception, isInstanceOf()); + expect(image, null); + })), + ); }); test('succeeds on successful retry', () async { - final NetworkImageWithRetry subject = new NetworkImageWithRetry( + final NetworkImageWithRetry subject = NetworkImageWithRetry( _imageUrl('error.png'), fetchStrategy: (Uri uri, FetchFailure failure) async { if (failure == null) { - return new FetchInstructions.attempt( + return FetchInstructions.attempt( uri: uri, timeout: const Duration(minutes: 1), ); } else { expect(failure.attemptCount, lessThan(2)); - return new FetchInstructions.attempt( + return FetchInstructions.attempt( uri: Uri.parse(_imageUrl('immediate_success.png')), timeout: const Duration(minutes: 1), ); @@ -119,10 +129,12 @@ void main() { }, ); - subject.load(subject).addListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(image.image.height, 1); - expect(image.image.width, 1); - })); + subject.load(subject).addListener( + ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(image.image.height, 1); + expect(image.image.width, 1); + })), + ); }); }); } diff --git a/packages/flutter_image/test/network_test_server.dart b/packages/flutter_image/test/network_test_server.dart index aa5c212fcd..a6c6eaa87e 100644 --- a/packages/flutter_image/test/network_test_server.dart +++ b/packages/flutter_image/test/network_test_server.dart @@ -7,8 +7,8 @@ import 'dart:io'; const int _kTestServerPort = 11111; -Future main() async { - final HttpServer testServer = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, _kTestServerPort); +Future main() async { + final HttpServer testServer = await HttpServer.bind(InternetAddress.loopbackIPv4, _kTestServerPort); await for (HttpRequest request in testServer) { if (request.uri.path.endsWith('/immediate_success.png')) { request.response.add(_kTransparentImage); @@ -22,7 +22,7 @@ Future main() async { } } -const List _kTransparentImage = const [ +const List _kTransparentImage = [ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44, From 7ef8c6c2abae7a3589cbb5f729131cd0b41f12d9 Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 17 Oct 2019 13:39:24 -0700 Subject: [PATCH 17/20] Update Flutter SDK version constraint (#17) --- packages/flutter_image/.travis.yml | 2 +- packages/flutter_image/CHANGELOG.md | 4 ++++ packages/flutter_image/pubspec.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/packages/flutter_image/.travis.yml b/packages/flutter_image/.travis.yml index 9ac6cd490c..a918bd4d3e 100644 --- a/packages/flutter_image/.travis.yml +++ b/packages/flutter_image/.travis.yml @@ -9,7 +9,7 @@ addons: - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version packages: - libstdc++6 - - fonts-droid + - fonts-droid-fallback before_script: - git clone https://github.com/flutter/flutter.git -b beta --depth 1 diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 65fab2f09b..8085832beb 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 2.0.1 + +- Update Flutter SDK version constraint. + ## 2.0.0 * **Breaking change**. Updates for Flutter 1.5.9. diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index b7ffcdd66e..0a2416276e 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 2.0.0 +version: 2.0.1 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team @@ -17,4 +17,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.9-pre.94 <2.0.0" + flutter: ">=1.5.9-pre.94 <1.10.15-pre.144" From 021761221c36f64259e8c0e036bef780fe56d37b Mon Sep 17 00:00:00 2001 From: Jason Simmons Date: Thu, 17 Oct 2019 15:09:33 -0700 Subject: [PATCH 18/20] Update flutter_image for the decoder callback added to the image provider API (#16) --- packages/flutter_image/.travis.yml | 2 +- packages/flutter_image/CHANGELOG.md | 4 ++++ packages/flutter_image/lib/network.dart | 9 +++++---- packages/flutter_image/pubspec.yaml | 4 ++-- packages/flutter_image/test/network_test.dart | 8 ++++---- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/packages/flutter_image/.travis.yml b/packages/flutter_image/.travis.yml index a918bd4d3e..b63a1d627c 100644 --- a/packages/flutter_image/.travis.yml +++ b/packages/flutter_image/.travis.yml @@ -12,7 +12,7 @@ addons: - fonts-droid-fallback before_script: - - git clone https://github.com/flutter/flutter.git -b beta --depth 1 + - git clone https://github.com/flutter/flutter.git -b master - export PATH=$PATH:$(pwd)/flutter/bin - export FLUTTER_HOME=$(pwd)/flutter - flutter doctor diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index 8085832beb..cfb10e551e 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 3.0.0 + +* **Breaking change**. Updates for Flutter 1.10.15. + ## 2.0.1 - Update Flutter SDK version constraint. diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index abffcdc3c6..3f91fcba8c 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -70,9 +70,9 @@ class NetworkImageWithRetry extends ImageProvider { } @override - ImageStreamCompleter load(NetworkImageWithRetry key) { + ImageStreamCompleter load(NetworkImageWithRetry key, DecoderCallback decode) { return OneFrameImageStreamCompleter( - _loadWithRetry(key), + _loadWithRetry(key, decode), informationCollector: () sync* { yield ErrorDescription('Image provider: $this'); yield ErrorDescription('Image key: $key'); @@ -101,7 +101,7 @@ class NetworkImageWithRetry extends ImageProvider { }()); } - Future _loadWithRetry(NetworkImageWithRetry key) async { + Future _loadWithRetry(NetworkImageWithRetry key, DecoderCallback decode) async { assert(key == this); final Stopwatch stopwatch = Stopwatch()..start(); @@ -136,7 +136,8 @@ class NetworkImageWithRetry extends ImageProvider { if (bytes.lengthInBytes == 0) return null; - final ui.Image image = await decodeImageFromList(bytes); + final ui.Codec codec = await decode(bytes); + final ui.Image image = (await codec.getNextFrame()).image; if (image == null) return null; diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 0a2416276e..59d3cadc23 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 2.0.1 +version: 3.0.0 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team @@ -17,4 +17,4 @@ dev_dependencies: environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.5.9-pre.94 <1.10.15-pre.144" + flutter: ">=1.10.15-pre.144 <2.0.0" diff --git a/packages/flutter_image/test/network_test.dart b/packages/flutter_image/test/network_test.dart index 46147ae905..d9dd426973 100644 --- a/packages/flutter_image/test/network_test.dart +++ b/packages/flutter_image/test/network_test.dart @@ -34,7 +34,7 @@ void main() { _imageUrl('immediate_success.png'), ); - subject.load(subject).addListener( + subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { expect(image.image.height, 1); expect(image.image.width, 1); @@ -70,7 +70,7 @@ void main() { }, ); - subject.load(subject).addListener( + subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { expect(errorLog.single.exception, isInstanceOf()); expect(image, null); @@ -102,7 +102,7 @@ void main() { }, ); - subject.load(subject).addListener( + subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { expect(errorLog.single.exception, isInstanceOf()); expect(image, null); @@ -129,7 +129,7 @@ void main() { }, ); - subject.load(subject).addListener( + subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { expect(image.image.height, 1); expect(image.image.width, 1); From 0ec88d71f5c816e6ac3471b80822a99aaffd127a Mon Sep 17 00:00:00 2001 From: Michael Thomsen Date: Thu, 11 Feb 2021 07:37:11 +0100 Subject: [PATCH 19/20] Update pubspec to match templates (#19) --- packages/flutter_image/pubspec.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index 59d3cadc23..f930dee4b3 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -10,11 +10,11 @@ dependencies: sdk: flutter dev_dependencies: - quiver: '>=0.24.0 <3.0.0' - test: any flutter_test: sdk: flutter + quiver: '>=0.24.0 <3.0.0' + test: any environment: sdk: ">=2.0.0-dev.28.0 <3.0.0" - flutter: ">=1.10.15-pre.144 <2.0.0" + flutter: ">=1.10.15-pre.144" From d7e59a10cead45d6ccd53ee28f84a578ed046858 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asc=C3=AAnio?= Date: Wed, 12 May 2021 12:15:38 -0300 Subject: [PATCH 20/20] Migrates to null safety (#22) --- packages/flutter_image/CHANGELOG.md | 5 + packages/flutter_image/lib/network.dart | 171 ++++++------- packages/flutter_image/pubspec.yaml | 6 +- packages/flutter_image/test/network_test.dart | 226 +++++++++--------- 4 files changed, 217 insertions(+), 191 deletions(-) diff --git a/packages/flutter_image/CHANGELOG.md b/packages/flutter_image/CHANGELOG.md index cfb10e551e..6d9ac91919 100644 --- a/packages/flutter_image/CHANGELOG.md +++ b/packages/flutter_image/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 4.0.0 + +- Migrates to null safety +- **Breaking change**: `NetworkImageWithRetry.load` now throws a `FetchFailure` if the fetched image data is zero bytes. + ## 3.0.0 * **Breaking change**. Updates for Flutter 1.10.15. diff --git a/packages/flutter_image/lib/network.dart b/packages/flutter_image/lib/network.dart index 3f91fcba8c..41db7c3423 100644 --- a/packages/flutter_image/lib/network.dart +++ b/packages/flutter_image/lib/network.dart @@ -29,10 +29,11 @@ class NetworkImageWithRetry extends ImageProvider { /// Creates an object that fetches the image at the given [url]. /// /// The arguments must not be null. - const NetworkImageWithRetry(this.url, { this.scale = 1.0, this.fetchStrategy = defaultFetchStrategy }) - : assert(url != null), - assert(scale != null), - assert(fetchStrategy != null); + const NetworkImageWithRetry( + this.url, { + this.scale = 1.0, + this.fetchStrategy = defaultFetchStrategy, + }); /// The HTTP client used to download images. static final io.HttpClient _client = io.HttpClient(); @@ -57,10 +58,12 @@ class NetworkImageWithRetry extends ImageProvider { /// This indirection is necessary because [defaultFetchStrategy] is used as /// the default constructor argument value, which requires that it be a const /// expression. - static final FetchStrategy _defaultFetchStrategyFunction = const FetchStrategyBuilder().build(); + static final FetchStrategy _defaultFetchStrategyFunction = + const FetchStrategyBuilder().build(); /// The [FetchStrategy] that [NetworkImageWithRetry] uses by default. - static Future defaultFetchStrategy(Uri uri, FetchFailure failure) { + static Future defaultFetchStrategy( + Uri uri, FetchFailure? failure) { return _defaultFetchStrategyFunction(uri, failure); } @@ -71,37 +74,34 @@ class NetworkImageWithRetry extends ImageProvider { @override ImageStreamCompleter load(NetworkImageWithRetry key, DecoderCallback decode) { - return OneFrameImageStreamCompleter( - _loadWithRetry(key, decode), + return OneFrameImageStreamCompleter(_loadWithRetry(key, decode), informationCollector: () sync* { - yield ErrorDescription('Image provider: $this'); - yield ErrorDescription('Image key: $key'); - } - ); + yield ErrorDescription('Image provider: $this'); + yield ErrorDescription('Image key: $key'); + }); } - void _debugCheckInstructions(FetchInstructions instructions) { + void _debugCheckInstructions(FetchInstructions? instructions) { assert(() { if (instructions == null) { if (fetchStrategy == defaultFetchStrategy) { throw StateError( - 'The default FetchStrategy returned null FetchInstructions. This\n' - 'is likely a bug in $runtimeType. Please file a bug at\n' - 'https://github.com/flutter/flutter/issues.' - ); + 'The default FetchStrategy returned null FetchInstructions. This\n' + 'is likely a bug in $runtimeType. Please file a bug at\n' + 'https://github.com/flutter/flutter/issues.'); } else { throw StateError( - 'The custom FetchStrategy used to fetch $url returned null\n' - 'FetchInstructions. FetchInstructions must never be null, but\n' - 'instead instruct to either make another fetch attempt or give up.' - ); + 'The custom FetchStrategy used to fetch $url returned null\n' + 'FetchInstructions. FetchInstructions must never be null, but\n' + 'instead instruct to either make another fetch attempt or give up.'); } } return true; }()); } - Future _loadWithRetry(NetworkImageWithRetry key, DecoderCallback decode) async { + Future _loadWithRetry( + NetworkImageWithRetry key, DecoderCallback decode) async { assert(key == this); final Stopwatch stopwatch = Stopwatch()..start(); @@ -109,16 +109,19 @@ class NetworkImageWithRetry extends ImageProvider { FetchInstructions instructions = await fetchStrategy(resolved, null); _debugCheckInstructions(instructions); int attemptCount = 0; - FetchFailure lastFailure; + FetchFailure? lastFailure; while (!instructions.shouldGiveUp) { attemptCount += 1; - io.HttpClientRequest request; + io.HttpClientRequest? request; try { - request = await _client.getUrl(instructions.uri).timeout(instructions.timeout); - final io.HttpClientResponse response = await request.close().timeout(instructions.timeout); + request = await _client + .getUrl(instructions.uri) + .timeout(instructions.timeout); + final io.HttpClientResponse response = + await request.close().timeout(instructions.timeout); - if (response == null || response.statusCode != 200) { + if (response.statusCode != 200) { throw FetchFailure._( totalDuration: stopwatch.elapsed, attemptCount: attemptCount, @@ -126,21 +129,25 @@ class NetworkImageWithRetry extends ImageProvider { ); } - final _Uint8ListBuilder builder = await response.fold( - _Uint8ListBuilder(), + final _Uint8ListBuilder builder = await response + .fold( + _Uint8ListBuilder(), (_Uint8ListBuilder buffer, List bytes) => buffer..add(bytes), - ).timeout(instructions.timeout); + ) + .timeout(instructions.timeout); final Uint8List bytes = builder.data; - if (bytes.lengthInBytes == 0) - return null; + if (bytes.lengthInBytes == 0) { + throw FetchFailure._( + totalDuration: stopwatch.elapsed, + attemptCount: attemptCount, + httpStatusCode: response.statusCode, + ); + } final ui.Codec codec = await decode(bytes); final ui.Image image = (await codec.getNextFrame()).image; - if (image == null) - return null; - return ImageInfo( image: image, scale: key.scale, @@ -159,27 +166,31 @@ class NetworkImageWithRetry extends ImageProvider { } } - if (instructions.alternativeImage != null) - return instructions.alternativeImage; + if (instructions.alternativeImage != null) { + return instructions.alternativeImage!; + } assert(lastFailure != null); - FlutterError.onError(FlutterErrorDetails( - exception: lastFailure, - library: 'package:flutter_image', - context: ErrorDescription('$runtimeType failed to load ${instructions.uri}'), - )); + if (FlutterError.onError != null) { + FlutterError.onError!(FlutterErrorDetails( + exception: lastFailure!, + library: 'package:flutter_image', + context: + ErrorDescription('$runtimeType failed to load ${instructions.uri}'), + )); + } - return null; + throw lastFailure!; } @override bool operator ==(dynamic other) { - if (other.runtimeType != runtimeType) + if (other.runtimeType != runtimeType) { return false; + } final NetworkImageWithRetry typedOther = other; - return url == typedOther.url - && scale == typedOther.scale; + return url == typedOther.url && scale == typedOther.scale; } @override @@ -207,26 +218,26 @@ class NetworkImageWithRetry extends ImageProvider { /// [NetworkImageWithRetry] to try again. /// /// See [NetworkImageWithRetry.defaultFetchStrategy] for an example. -typedef FetchStrategy = Future Function(Uri uri, FetchFailure failure); +typedef FetchStrategy = Future Function( + Uri uri, FetchFailure? failure); /// Instructions [NetworkImageWithRetry] uses to fetch the image. @immutable class FetchInstructions { /// Instructs [NetworkImageWithRetry] to give up trying to download the image. const FetchInstructions.giveUp({ - @required this.uri, + required this.uri, this.alternativeImage, - }) - : shouldGiveUp = true, - timeout = null; + }) : shouldGiveUp = true, + timeout = Duration.zero; /// Instructs [NetworkImageWithRetry] to attempt to download the image from /// the given [uri] and [timeout] if it takes too long. const FetchInstructions.attempt({ - @required this.uri, - @required this.timeout, - }) : shouldGiveUp = false, - alternativeImage = null; + required this.uri, + required this.timeout, + }) : shouldGiveUp = false, + alternativeImage = null; /// Instructs to give up trying. /// @@ -241,16 +252,16 @@ class FetchInstructions { final Uri uri; /// Instructs to give up and use this image instead. - final Future alternativeImage; + final Future? alternativeImage; @override String toString() { return '$runtimeType(\n' - ' shouldGiveUp: $shouldGiveUp\n' - ' timeout: $timeout\n' - ' uri: $uri\n' - ' alternativeImage?: ${alternativeImage != null ? 'yes' : 'no'}\n' - ')'; + ' shouldGiveUp: $shouldGiveUp\n' + ' timeout: $timeout\n' + ' uri: $uri\n' + ' alternativeImage?: ${alternativeImage != null ? 'yes' : 'no'}\n' + ')'; } } @@ -258,12 +269,11 @@ class FetchInstructions { @immutable class FetchFailure implements Exception { const FetchFailure._({ - @required this.totalDuration, - @required this.attemptCount, + required this.totalDuration, + required this.attemptCount, this.httpStatusCode, this.originalException, - }) : assert(totalDuration != null), - assert(attemptCount > 0); + }) : assert(attemptCount > 0); /// The total amount of time it has taken so far to download the image. final Duration totalDuration; @@ -276,7 +286,7 @@ class FetchFailure implements Exception { final int attemptCount; /// HTTP status code, such as 500. - final int httpStatusCode; + final int? httpStatusCode; /// The exception that caused the fetch failure. final dynamic originalException; @@ -294,7 +304,7 @@ class FetchFailure implements Exception { /// An indefinitely growing builder of a [Uint8List]. class _Uint8ListBuilder { - static const int _kInitialSize = 100000; // 100KB-ish + static const int _kInitialSize = 100000; // 100KB-ish int _usedLength = 0; Uint8List _buffer = Uint8List(_kInitialSize); @@ -344,20 +354,16 @@ class FetchStrategyBuilder { this.maxAttempts = 5, this.initialPauseBetweenRetries = const Duration(seconds: 1), this.exponentialBackoffMultiplier = 2, - this.transientHttpStatusCodePredicate = defaultTransientHttpStatusCodePredicate, - }) : assert(timeout != null), - assert(totalFetchTimeout != null), - assert(maxAttempts != null), - assert(initialPauseBetweenRetries != null), - assert(exponentialBackoffMultiplier != null), - assert(transientHttpStatusCodePredicate != null); + this.transientHttpStatusCodePredicate = + defaultTransientHttpStatusCodePredicate, + }); /// A list of HTTP status codes that can generally be retried. /// /// You may want to use a different list depending on the needs of your /// application. static const List defaultTransientHttpStatusCodes = [ - 0, // Network error + 0, // Network error 408, // Request timeout 500, // Internal server error 502, // Bad gateway @@ -395,7 +401,7 @@ class FetchStrategyBuilder { /// Builds a [FetchStrategy] that operates using the properties of this /// builder. FetchStrategy build() { - return (Uri uri, FetchFailure failure) async { + return (Uri uri, FetchFailure? failure) async { if (failure == null) { // First attempt. Just load. return FetchInstructions.attempt( @@ -404,18 +410,21 @@ class FetchStrategyBuilder { ); } - final bool isRetriableFailure = transientHttpStatusCodePredicate(failure.httpStatusCode) || + final bool isRetriableFailure = (failure.httpStatusCode != null && + transientHttpStatusCodePredicate(failure.httpStatusCode!)) || failure.originalException is io.SocketException; // If cannot retry, give up. - if (!isRetriableFailure || // retrying will not help - failure.totalDuration > totalFetchTimeout || // taking too long - failure.attemptCount > maxAttempts) { // too many attempts + if (!isRetriableFailure || // retrying will not help + failure.totalDuration > totalFetchTimeout || // taking too long + failure.attemptCount > maxAttempts) { + // too many attempts return FetchInstructions.giveUp(uri: uri); } // Exponential back-off. - final Duration pauseBetweenRetries = initialPauseBetweenRetries * math.pow(exponentialBackoffMultiplier, failure.attemptCount - 1); + final Duration pauseBetweenRetries = initialPauseBetweenRetries * + math.pow(exponentialBackoffMultiplier, failure.attemptCount - 1); await Future.delayed(pauseBetweenRetries); // Retry. diff --git a/packages/flutter_image/pubspec.yaml b/packages/flutter_image/pubspec.yaml index f930dee4b3..399114cef0 100644 --- a/packages/flutter_image/pubspec.yaml +++ b/packages/flutter_image/pubspec.yaml @@ -1,5 +1,5 @@ name: flutter_image -version: 3.0.0 +version: 4.0.0 description: > Image utilities for Flutter: providers, effects, etc author: Flutter Team @@ -12,9 +12,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - quiver: '>=0.24.0 <3.0.0' + quiver: ^3.0.0 test: any environment: - sdk: ">=2.0.0-dev.28.0 <3.0.0" + sdk: ">=2.12.0 <3.0.0" flutter: ">=1.10.15-pre.144" diff --git a/packages/flutter_image/test/network_test.dart b/packages/flutter_image/test/network_test.dart index d9dd426973..4652ac307f 100644 --- a/packages/flutter_image/test/network_test.dart +++ b/packages/flutter_image/test/network_test.dart @@ -19,122 +19,134 @@ void main() { HttpOverrides.global = null; group('NetworkImageWithRetry', () { - setUp(() { - FlutterError.onError = (FlutterErrorDetails error) { - fail('$error'); - }; + group('succeeds', () { + setUp(() { + FlutterError.onError = (FlutterErrorDetails error) { + fail('$error'); + }; + }); + + tearDown(() { + FlutterError.onError = FlutterError.dumpErrorToConsole; + }); + + test('loads image from network', () async { + final NetworkImageWithRetry subject = NetworkImageWithRetry( + _imageUrl('immediate_success.png'), + ); + + assertThatImageLoadingSucceeds(subject); + }); + + test('succeeds on successful retry', () async { + final NetworkImageWithRetry subject = NetworkImageWithRetry( + _imageUrl('error.png'), + fetchStrategy: (Uri uri, FetchFailure? failure) async { + if (failure == null) { + return FetchInstructions.attempt( + uri: uri, + timeout: const Duration(minutes: 1), + ); + } else { + expect(failure.attemptCount, lessThan(2)); + return FetchInstructions.attempt( + uri: Uri.parse(_imageUrl('immediate_success.png')), + timeout: const Duration(minutes: 1), + ); + } + }, + ); + assertThatImageLoadingSucceeds(subject); + }); }); - tearDown(() { - FlutterError.onError = FlutterError.dumpErrorToConsole; - }); - - test('loads image from network', () async { - final NetworkImageWithRetry subject = NetworkImageWithRetry( - _imageUrl('immediate_success.png'), - ); - - subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( - ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(image.image.height, 1); - expect(image.image.width, 1); - })), - ); - }); - - test('retries 6 times then gives up', () async { + group('fails', () { final List errorLog = []; - FlutterError.onError = errorLog.add; + FakeAsync fakeAsync = FakeAsync(); - final FakeAsync fakeAsync = FakeAsync(); - final dynamic maxAttemptCountReached = expectAsync0(() {}); + setUp(() { + FlutterError.onError = errorLog.add; + }); - int attemptCount = 0; - Future onAttempt() async { - expect(attemptCount, lessThan(7)); - if (attemptCount == 6) { - maxAttemptCountReached(); - } - await Future.delayed(Duration.zero); - fakeAsync.elapse(const Duration(seconds: 60)); - attemptCount++; - } + tearDown(() { + fakeAsync = FakeAsync(); + errorLog.clear(); + FlutterError.onError = FlutterError.dumpErrorToConsole; + }); - final NetworkImageWithRetry subject = NetworkImageWithRetry( - _imageUrl('error.png'), - fetchStrategy: (Uri uri, FetchFailure failure) { - Timer.run(onAttempt); - return fakeAsync.run((FakeAsync fakeAsync) { - return NetworkImageWithRetry.defaultFetchStrategy(uri, failure); - }); - }, - ); + test('retries 6 times then gives up', () async { + final dynamic maxAttemptCountReached = expectAsync0(() {}); - subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( - ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(errorLog.single.exception, isInstanceOf()); - expect(image, null); - })), - ); - }); - - test('gives up immediately on non-retriable errors (HTTP 404)', () async { - final List errorLog = []; - FlutterError.onError = errorLog.add; - - final FakeAsync fakeAsync = FakeAsync(); - - int attemptCount = 0; - Future onAttempt() async { - expect(attemptCount, lessThan(2)); - await Future.delayed(Duration.zero); - fakeAsync.elapse(const Duration(seconds: 60)); - attemptCount++; - } - - final NetworkImageWithRetry subject = NetworkImageWithRetry( - _imageUrl('does_not_exist.png'), - fetchStrategy: (Uri uri, FetchFailure failure) { - Timer.run(onAttempt); - return fakeAsync.run((FakeAsync fakeAsync) { - return NetworkImageWithRetry.defaultFetchStrategy(uri, failure); - }); - }, - ); - - subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( - ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(errorLog.single.exception, isInstanceOf()); - expect(image, null); - })), - ); - }); - - test('succeeds on successful retry', () async { - final NetworkImageWithRetry subject = NetworkImageWithRetry( - _imageUrl('error.png'), - fetchStrategy: (Uri uri, FetchFailure failure) async { - if (failure == null) { - return FetchInstructions.attempt( - uri: uri, - timeout: const Duration(minutes: 1), - ); - } else { - expect(failure.attemptCount, lessThan(2)); - return FetchInstructions.attempt( - uri: Uri.parse(_imageUrl('immediate_success.png')), - timeout: const Duration(minutes: 1), - ); + int attemptCount = 0; + Future onAttempt() async { + expect(attemptCount, lessThan(7)); + if (attemptCount == 6) { + maxAttemptCountReached(); } - }, - ); + await Future.delayed(Duration.zero); + fakeAsync.elapse(const Duration(seconds: 60)); + attemptCount++; + } - subject.load(subject, PaintingBinding.instance.instantiateImageCodec).addListener( - ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { - expect(image.image.height, 1); - expect(image.image.width, 1); - })), - ); + final NetworkImageWithRetry subject = NetworkImageWithRetry( + _imageUrl('error.png'), + fetchStrategy: (Uri uri, FetchFailure? failure) { + Timer.run(onAttempt); + return fakeAsync.run((FakeAsync fakeAsync) { + return NetworkImageWithRetry.defaultFetchStrategy(uri, failure); + }); + }, + ); + + assertThatImageLoadingFails(subject, errorLog); + }); + + test('gives up immediately on non-retriable errors (HTTP 404)', () async { + int attemptCount = 0; + Future onAttempt() async { + expect(attemptCount, lessThan(2)); + await Future.delayed(Duration.zero); + fakeAsync.elapse(const Duration(seconds: 60)); + attemptCount++; + } + + final NetworkImageWithRetry subject = NetworkImageWithRetry( + _imageUrl('does_not_exist.png'), + fetchStrategy: (Uri uri, FetchFailure? failure) { + Timer.run(onAttempt); + return fakeAsync.run((FakeAsync fakeAsync) { + return NetworkImageWithRetry.defaultFetchStrategy(uri, failure); + }); + }, + ); + + assertThatImageLoadingFails(subject, errorLog); + }); }); }); } + +void assertThatImageLoadingFails( + NetworkImageWithRetry subject, List errorLog) { + subject + .load(subject, PaintingBinding.instance!.instantiateImageCodec) + .addListener(ImageStreamListener( + (ImageInfo image, bool synchronousCall) {}, + onError: expectAsync2((Object error, StackTrace? _) { + expect(errorLog.single.exception, isInstanceOf()); + expect(error, isInstanceOf()); + expect(error, equals(errorLog.single.exception)); + }), + )); +} + +void assertThatImageLoadingSucceeds(NetworkImageWithRetry subject) { + subject + .load(subject, PaintingBinding.instance!.instantiateImageCodec) + .addListener( + ImageStreamListener(expectAsync2((ImageInfo image, bool synchronousCall) { + expect(image.image.height, 1); + expect(image.image.width, 1); + })), + ); +}