├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_zh.md ├── android └── app │ └── src │ └── main │ └── java │ └── io │ └── flutter │ └── plugins │ └── GeneratedPluginRegistrant.java ├── example ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ ├── Flutter.podspec │ │ ├── Release.xcconfig │ │ └── flutter_export_environment.sh │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── dio_helper.dart │ ├── main.dart │ ├── panels │ │ ├── cache_manage.dart │ │ ├── helper.dart │ │ ├── panel_204.dart │ │ ├── panel_get.dart │ │ ├── panel_get_bytes.dart │ │ ├── panel_get_from_service.dart │ │ └── panel_post.dart │ └── tuple.dart ├── pubspec.yaml └── test │ └── widget_test.dart ├── ios └── Runner │ ├── GeneratedPluginRegistrant.h │ └── GeneratedPluginRegistrant.m ├── lib ├── dio_http_cache.dart └── src │ ├── builder_dio.dart │ ├── core │ ├── config.dart │ ├── manager.dart │ ├── obj.dart │ └── obj.g.dart │ ├── manager_dio.dart │ └── store │ ├── store_disk.dart │ ├── store_impl.dart │ └── store_memory.dart ├── pubspec.yaml ├── shells ├── cleanpub.sh └── json.sh └── test └── dio_cache_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | .pub/ 7 | build/ 8 | # If you're building an application, you may want to check-in your pubspec.lock 9 | pubspec.lock 10 | 11 | # Directory created by dartdoc 12 | # If you don't generate documentation locally you can remove this line. 13 | doc/api/ 14 | 15 | .idea/ 16 | *.iml 17 | pubspec.lock 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.3.0] - 2021-03-23 2 | 3 | * Migrated to nullsatefy with Dart 2.12 4 | * Updated compatibility woth Dio 4 interceptors 5 | 6 | ## [0.2.11] - 2020-09-24 7 | * Support for custom disk storage. 8 | 9 | ## [0.2.9] - 2020-09-15 10 | * Support database path custom config. 11 | * Add flag in Response headers to distinguish whether the data is coming from the cache. 12 | 13 | ## [0.2.8] - 2020-07-18 14 | 15 | * Fix issue [#37](https://github.com/hurshi/dio-http-cache/issues/41), fix request method in primaryKey, and add request method in delete caches. 16 | * WARNING: request method is needed when you delete one cache, or set defaultRequestMethod in CacheConfig. 17 | 18 | ## [0.2.7] - 2020-06-04 19 | 20 | * Fix issue [#37](https://github.com/hurshi/dio-http-cache/issues/30), add request method to primaryKey. 21 | 22 | ## [0.2.6] - 2020-02-05 23 | 24 | * Fix issue [#30](https://github.com/hurshi/dio-http-cache/issues/30), default maxAge ignored. 25 | * Fix issue [#24](https://github.com/hurshi/dio-http-cache/issues/24), Resolve the crash parsing of the Response head. 26 | 27 | ## [0.2.5] - 2019-11-26 28 | 29 | * Support ResponseType.bytes. 30 | * Cache all headers. 31 | * Change cache data type from TEXT to BLOB. 32 | * WARNING: Because of the change in the database data type, when upgrading to this version, the data cached by the previous version will be erased. 33 | 34 | ## [0.2.4] - 2019-11-16 35 | 36 | * Support for get maxAge and maxStale from response headers. 37 | * Improve example codes. 38 | 39 | ## [0.2.3] - 2019-11-14 40 | 41 | * Store cache only when response statusCode in 200 ~ 300. 42 | * Support for store statusCode. 43 | * Improve example codes. 44 | 45 | ## [0.2.2] - 2019-11-14 46 | 47 | * Store cache only when response statusCode equals 200. 48 | 49 | ## [0.2.1] - 2019-10-29 50 | 51 | * Fix crash for null value in headers 52 | 53 | ## [0.2.0] - 2019-09-20 54 | 55 | * Support for dio 3.0 56 | 57 | ## [0.1.4] - 2019-09-17 58 | 59 | * Fix bug for remove memory cache by primary key 60 | 61 | 62 | ## [0.1.3] - 2019-08-21 63 | 64 | * Change primaryKey to "host + path", and automatically use queryParams as the subKey. 65 | * Support for delete caches by primaryKey. (Parsing primaryKey from path). 66 | * Support for delete one cache by primaryKey and subKey. (Parsing primaryKey and subKey from path). 67 | 68 | 69 | ## [0.1.2] - 2019-08-06 70 | 71 | * Return Future for function delete, expire and so on. 72 | * Returns statusCode=200 if data was retrieved from the cache successfully. 73 | * Support for forced data refresh from the network. 74 | 75 | 76 | ## [0.1.1] - 2019-07-24 77 | 78 | * Support delete all cache. 79 | 80 | 81 | ## [0.1.0] - 2019-07-14 82 | 83 | * This is a pre-release version. 84 | * Support disk cache. 85 | * Support memory cache. 86 | * Support key and subKey. 87 | * Support maxAge and maxStale. 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2019 Hurshi 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dio-http-cache 2 | 3 | [![Pub](https://img.shields.io/pub/v/dio_http_cache.svg?style=flat)](https://pub.dev/packages/dio_http_cache) 4 | 5 | [中文介绍](./README_zh.md) 6 | 7 | Dio-http-cache is a cache library for [Dio ( http client for flutter )](https://github.com/flutterchina/dio), like [Rxcache](https://github.com/VictorAlbertos/RxCache) in Android. 8 | 9 | Dio-http-cache uses [sqflite](https://github.com/tekartik/sqflite) as disk cache, and [LRU](https://github.com/google/quiver-dart) strategy as memory cache. 10 | 11 | Inspired by [flutter_cache_manager](https://github.com/renefloor/flutter_cache_manager). 12 | 13 | ### Add Dependency 14 | 15 | ```yaml 16 | dependencies: 17 | dio_http_cache: ^0.3.x #latest version 18 | ``` 19 | 20 | ### QuickStart 21 | 22 | 1. Add a dio-http-cache interceptor in Dio : 23 | 24 | ```dart 25 | dio.interceptors.add(DioCacheManager(CacheConfig(baseUrl: "http://www.google.com")).interceptor); 26 | ``` 27 | 28 | 2. Set maxAge for a request : 29 | 30 | ```dart 31 | Dio().get( 32 | "http://www.google.com", 33 | options: buildCacheOptions(Duration(days: 7)), 34 | ); 35 | ``` 36 | 37 | ### The advanced 38 | 39 | 1. **Custom your config by buildCacheOptions :** 40 | 41 | 1. **primaryKey:** By default, `host + path` is used as the primaryKey, and you can also customize it. 42 | 43 | 2. **subKey:** By default, query ( data or queryParameters) is used as subKey, and you can specify the subKey when it's necessary, for example: 44 | 45 | ```dart 46 | buildCacheOptions(Duration(days: 7), subKey: "page=1") 47 | ``` 48 | 49 | 3. **maxAge:** set the cache time. If the value is null or not setted, it will try to get maxAge and maxStale from response headers. 50 | 51 | 4. **maxStale:** set stale time. When an error (like 500,404) occurs before maxStale, try to return cache. 52 | 53 | ```dart 54 | buildCacheOptions(Duration(days: 7), maxStale: Duration(days: 10)) 55 | ``` 56 | 57 | 5. **forceRefresh**: false default. 58 | 59 | ```dart 60 | buildCacheOptions(Duration(days: 7), forceRefresh: true) 61 | ``` 62 | 63 | 1. Get data from network first. 64 | 2. If getting data from network succeeds, store or refresh cache. 65 | 3. If getting data from network fails or no network avaliable, **try** get data from cache instead of an error. 66 | 67 | 2. **Use "CacheConfig" to config default params** 68 | 69 | 1. **baseUrl:** it’s optional; If you don't have set baseUrl in CacheConfig, when you call `deleteCache`, you need provide full path like `"https://www.google.com/search?q=hello"`, but not just `"search?q=hello"`. 70 | 2. **encrypt / decrypt:** these two must be used together to encrypt the disk cache data, you can also zip data here. 71 | 3. **defaultMaxAge:** use `Duration(day:7)` as default. 72 | 4. **defaultaMaxStale:** similar with DefaultMaxAge. 73 | 5. **databasePath:** database path. 74 | 6. **databaseName:** database name. 75 | 7. **skipMemoryCache:** false defalut. 76 | 8. **skipDiskCache:** false default. 77 | 9. **maxMemoryCacheCount:** 100 defalut. 78 | 10. **defaultRequestMethod**: use "POST" as default, it will be used in `delete caches`. 79 | 11. **diskStore**: custom disk storage. 80 | 81 | 3. **How to clear expired cache** 82 | 83 | * Just ignore it, that is automatic. 84 | 85 | * But if you insist : `DioCacheManager.clearExpired();` 86 | 87 | 4. **How to delete caches** 88 | 89 | 1. No matter what subKey is, delete local cache if primary matched. 90 | 91 | ```dart 92 | // Automatically parses primarykey from path 93 | _dioCacheManager.deleteByPrimaryKey(path, requestMethod: "POST"); 94 | ``` 95 | 96 | 2. Delete local cache when both primaryKey and subKey matched. 97 | 98 | ```dart 99 | // delete local cache when both primaryKey and subKey matched. 100 | _dioCacheManager.deleteByPrimaryKeyAndSubKey(path, requestMethod: "GET"); 101 | ``` 102 | 103 | **INPORTANT:** If you have additional parameters when requesting the http interface, you must take them with it, for example: 104 | 105 | ```dart 106 | _dio.get(_url, queryParameters: {'k': keyword}, 107 | options: buildCacheOptions(Duration(hours: 1))) 108 | //delete the cache: 109 | _dioCacheManager.deleteByPrimaryKeyAndSubKey(_url, requestMethod: "GET", queryParameters:{'k': keyword}); 110 | ``` 111 | 112 | ```dart 113 | _dio.post(_url, data: {'k': keyword}, 114 | options: buildCacheOptions(Duration(hours: 1))) 115 | //delete the cache: 116 | _dioCacheManager.deleteByPrimaryKeyAndSubKey(_url, requestMethod: "POST", data:{'k': keyword}); 117 | ``` 118 | 119 | 3. Delete local cache by primaryKey and optional subKey if you know your primarykey and subkey exactly. 120 | 121 | ```dart 122 | // delete local cache by primaryKey and optional subKey 123 | _dioCacheManager.delete(primaryKey,{subKey,requestMethod}); 124 | ``` 125 | 126 | 5. **How to clear All caches** (expired or not) 127 | 128 | ```dart 129 | _dioCacheManager.clearAll(); 130 | ``` 131 | 132 | 6. **How to know if the data come from Cache** 133 | 134 | ```dart 135 | if (null != response.headers.value(DIO_CACHE_HEADER_KEY_DATA_SOURCE)) { 136 | // data come from cache 137 | } else { 138 | // data come from net 139 | } 140 | ``` 141 | 142 | 143 | ### Example for maxAge and maxStale 144 | 145 | ```dart 146 | _dio.post( 147 | "https://www.exmaple.com", 148 | data: {'k': "keyword"}, 149 | options:buildCacheOptions( 150 | Duration(days: 3), 151 | maxStale: Duration(days: 7), 152 | ) 153 | ) 154 | ``` 155 | 156 | 1. 0 ~ 3 days : Return data from cache directly (irrelevant with network). 157 | 2. 3 ~ 7 days: 158 | 1. Get data from network first. 159 | 2. If getting data from network succeeds, refresh cache. 160 | 3. If getting data from network fails or no network avaliable, **try** get data from cache instead of an error. 161 | 3. 7 ~ ∞ days: It won't use cache anymore, and the cache will be deleted at the right time. 162 | 163 | ### License 164 | 165 | ``` 166 | Copyright 2019 Hurshi 167 | 168 | Licensed under the Apache License, Version 2.0 (the "License"); 169 | you may not use this file except in compliance with the License. 170 | You may obtain a copy of the License at 171 | 172 | http://www.apache.org/licenses/LICENSE-2.0 173 | 174 | Unless required by applicable law or agreed to in writing, software 175 | distributed under the License is distributed on an "AS IS" BASIS, 176 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 177 | See the License for the specific language governing permissions and 178 | limitations under the License. 179 | ``` 180 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | [![Pub](https://img.shields.io/pub/v/dio_http_cache.svg?style=flat)](https://pub.dev/packages/dio_http_cache) 2 | 3 | ### 简介 4 | 5 | * Dio-http-cache 是 Flutter 的 http 缓存库,为 [Dio](https://github.com/flutterchina/dio) 设计,就像 Android 中的 [RxCache](https://github.com/VictorAlbertos/RxCache) 一样。 6 | * Dio-http-cache 使用 [sqflite](https://github.com/tekartik/sqflite) 作为磁盘缓存,使用 [Google/quiver-dart 的LRU算法](https://github.com/google/quiver-dart) 作为内存缓存策略。 7 | * 有参考 [flutter_cache_manager](https://github.com/renefloor/flutter_cache_manager) 开发,为此感谢。 8 | * **更详细**且**最新**的文档还请参阅 [README](https://github.com/hurshi/dio-http-cache)。 9 | 10 | ### 添加依赖 11 | 12 | ```yaml 13 | dependencies: 14 | dio_http_cache: ^0.3.x #latest version 15 | ``` 16 | 17 | ### 简单使用 18 | 19 | 1. 为 dio 添加拦截器: 20 | 21 | ```dart 22 | dio.interceptors.add(DioCacheManager(CacheConfig(baseUrl: "http://www.google.com")).interceptor); 23 | ``` 24 | 25 | 2. 为需要缓存的请求添加 options: 26 | 27 | ```dart 28 | Dio().get( 29 | "http://www.google.com", 30 | options: buildCacheOptions(Duration(days: 7)), 31 | ); 32 | ``` 33 | 34 | ### 进阶使用 35 | 36 | 1. **buildCacheOptions可以配置多种参数满足不同的缓存需求:** 37 | 1. ***MaxAge:*** 只有这个是必须的参数,设置缓存的时间; 38 | 39 | 2. ***MaxStale:*** 设置过期时常;在maxAge过期,而请求网络**失败**的时候,如果maxStale没有过期,则会使用这个缓存数据。 40 | 41 | ```dart 42 | buildCacheOptions(Duration(days: 7), maxStale: Duration(days: 10)) 43 | ``` 44 | 45 | 3. ***subKey:*** dio-http-cache 默认使用 url 作为缓存 key ,但当 url 不够用的时候,比如 post 请求分页数据的时候,就需要配合subKey使用。 46 | 47 | ```dart 48 | buildCacheOptions(Duration(days: 7), subKey: "page=1") 49 | ``` 50 | 51 | 2. **CacheConfig 可以配置一些默认参数:** 52 | 1. ***encrypt / dectrypt:*** 这2个必须组合使用,实现磁盘缓存数据的加密。也可以在这里实现数据的压缩。 53 | 2. ***DefaultMaxAge:*** 默认值为 Duration( day: 7 ), 在上面 buildCacheOption 中如果没有配置 MaxAge 有错误,或者自己实现了 option 而没有配置 MaxAge, 会使用这个默认值; 54 | 3. ***DefalutMaxStale:*** 和 DefaultMaxAge 类似; 55 | 4. ***DatabaseName:*** 配置数据库名; 56 | 5. ***SkipMemoryCache:*** 默认 false; 57 | 6. ***SkipDiskCache:*** 默认 false; 58 | 7. ***MaxMemoryCacheCount:*** 最大的内存缓存数量,默认100; 59 | 60 | 3. **如何清理已过期缓存** 61 | 62 | * 会自动清理不用管 63 | * 如果非要清理,可以调用:`DioCacheManager.clearExpired();` 64 | 65 | 4. **如何删除一条记录** 66 | 67 | ``` 68 | _dioCacheManager.delete(url);//会删除所有 url 的缓存。 69 | _dioCacheManager.delete(url,subKey); 70 | ``` 71 | 72 | 5. **如何清理所有缓存**(不管有没有过期) 73 | 74 | ``` 75 | _dioCacheManager.clearAll(); 76 | ``` 77 | 78 | ### License 79 | 80 | ``` 81 | Copyright 2019 Hurshi 82 | 83 | Licensed under the Apache License, Version 2.0 (the "License"); 84 | you may not use this file except in compliance with the License. 85 | You may obtain a copy of the License at 86 | 87 | http://www.apache.org/licenses/LICENSE-2.0 88 | 89 | Unless required by applicable law or agreed to in writing, software 90 | distributed under the License is distributed on an "AS IS" BASIS, 91 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 92 | See the License for the specific language governing permissions and 93 | limitations under the License. 94 | ``` 95 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/flutter/plugins/GeneratedPluginRegistrant.java: -------------------------------------------------------------------------------- 1 | package io.flutter.plugins; 2 | 3 | import io.flutter.plugin.common.PluginRegistry; 4 | 5 | /** 6 | * Generated file. Do not edit. 7 | */ 8 | public final class GeneratedPluginRegistrant { 9 | public static void registerWith(PluginRegistry registry) { 10 | if (alreadyRegisteredWith(registry)) { 11 | return; 12 | } 13 | } 14 | 15 | private static boolean alreadyRegisteredWith(PluginRegistry registry) { 16 | final String key = GeneratedPluginRegistrant.class.getCanonicalName(); 17 | if (registry.hasPlugin(key)) { 18 | return true; 19 | } 20 | registry.registrarFor(key); 21 | return false; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"sqflite","path":"/Users/tomas/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.0+3/","dependencies":[]}],"android":[{"name":"sqflite","path":"/Users/tomas/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.0+3/","dependencies":[]}],"macos":[{"name":"sqflite","path":"/Users/tomas/.pub-cache/hosted/pub.dartlang.org/sqflite-2.0.0+3/","dependencies":[]}],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"sqflite","dependencies":[]}],"date_created":"2021-07-23 14:48:33.231474","version":"2.2.3"} -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # Visual Studio Code related 19 | .vscode/ 20 | 21 | # Flutter/Dart/Pub related 22 | **/doc/api/ 23 | .dart_tool/ 24 | .flutter-plugins 25 | .packages 26 | .pub-cache/ 27 | .pub/ 28 | /build/ 29 | 30 | # Android related 31 | **/android/**/gradle-wrapper.jar 32 | **/android/.gradle 33 | **/android/captures/ 34 | **/android/gradlew 35 | **/android/gradlew.bat 36 | **/android/local.properties 37 | **/android/**/GeneratedPluginRegistrant.java 38 | 39 | # iOS/XCode related 40 | **/ios/**/*.mode1v3 41 | **/ios/**/*.mode2v3 42 | **/ios/**/*.moved-aside 43 | **/ios/**/*.pbxuser 44 | **/ios/**/*.perspectivev3 45 | **/ios/**/*sync/ 46 | **/ios/**/.sconsign.dblite 47 | **/ios/**/.tags* 48 | **/ios/**/.vagrant/ 49 | **/ios/**/DerivedData/ 50 | **/ios/**/Icon? 51 | **/ios/**/Pods/ 52 | **/ios/**/.symlinks/ 53 | **/ios/**/profile 54 | **/ios/**/xcuserdata 55 | **/ios/.generated/ 56 | **/ios/Flutter/App.framework 57 | **/ios/Flutter/Flutter.framework 58 | **/ios/Flutter/Generated.xcconfig 59 | **/ios/Flutter/app.flx 60 | **/ios/Flutter/app.zip 61 | **/ios/Flutter/flutter_assets/ 62 | **/ios/ServiceDefinitions.json 63 | **/ios/Runner/GeneratedPluginRegistrant.* 64 | 65 | # Exceptions to above rules. 66 | !**/ios/**/default.mode1v3 67 | !**/ios/**/default.mode2v3 68 | !**/ios/**/default.pbxuser 69 | !**/ios/**/default.perspectivev3 70 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 71 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 7a4c33425ddd78c54aba07d86f3f9a4a0051769b 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | [Access the details on Github](https://github.com/hurshi/dio-http-cache/) 2 | 3 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 26 | 27 | android { 28 | compileSdkVersion 28 29 | 30 | lintOptions { 31 | disable 'InvalidPackage' 32 | } 33 | 34 | defaultConfig { 35 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 36 | applicationId "com.example.example" 37 | minSdkVersion 16 38 | targetSdkVersion 28 39 | versionCode flutterVersionCode.toInteger() 40 | versionName flutterVersionName 41 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 42 | } 43 | 44 | buildTypes { 45 | release { 46 | // TODO: Add your own signing config for the release build. 47 | // Signing with the debug keys for now, so `flutter run --release` works. 48 | signingConfig signingConfigs.debug 49 | } 50 | } 51 | } 52 | 53 | flutter { 54 | source '../..' 55 | } 56 | 57 | dependencies { 58 | testImplementation 'junit:junit:4.12' 59 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 60 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 61 | } 62 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 10 | 11 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:4.0.0' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.enableR8=true 3 | android.useAndroidX=true 4 | android.enableJetifier=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Flutter.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # NOTE: This podspec is NOT to be published. It is only used as a local source! 3 | # This is a generated file; do not edit or check into version control. 4 | # 5 | 6 | Pod::Spec.new do |s| 7 | s.name = 'Flutter' 8 | s.version = '1.0.0' 9 | s.summary = 'High-performance, high-fidelity mobile apps.' 10 | s.homepage = 'https://flutter.io' 11 | s.license = { :type => 'MIT' } 12 | s.author = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' } 13 | s.source = { :git => 'https://github.com/flutter/engine', :tag => s.version.to_s } 14 | s.ios.deployment_target = '8.0' 15 | # Framework linking is handled by Flutter tooling, not CocoaPods. 16 | # Add a placeholder to satisfy `s.dependency 'Flutter'` plugin podspecs. 17 | s.vendored_frameworks = 'path/to/nothing' 18 | end 19 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/flutter_export_environment.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # This is a generated file; do not edit or check into version control. 3 | export "FLUTTER_ROOT=/Users/tomas/Documents/dev/flutter" 4 | export "FLUTTER_APPLICATION_PATH=/Users/tomas/Documents/code/dio-http-cache/example" 5 | export "COCOAPODS_PARALLEL_CODE_SIGN=true" 6 | export "FLUTTER_TARGET=lib/main.dart" 7 | export "FLUTTER_BUILD_DIR=build" 8 | export "SYMROOT=${SOURCE_ROOT}/../build/ios" 9 | export "FLUTTER_BUILD_NAME=1.0.0" 10 | export "FLUTTER_BUILD_NUMBER=1" 11 | export "DART_OBFUSCATION=false" 12 | export "TRACK_WIDGET_CREATION=false" 13 | export "TREE_SHAKE_ICONS=false" 14 | export "PACKAGE_CONFIG=.packages" 15 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '9.0' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_ios_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_ios_build_settings(target) 39 | # Do not build arm64 when building for iOS Simulator, which xcodebuild will attempt from XCode 12 onwards 40 | # https://stackoverflow.com/q/63607158/243165 41 | target.build_configurations.each do |config| 42 | config.build_settings["EXCLUDED_ARCHS[sdk=iphonesimulator*]"] = "arm64" 43 | config.build_settings.delete 'IPHONEOS_DEPLOYMENT_TARGET' 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - FMDB (2.7.5): 4 | - FMDB/standard (= 2.7.5) 5 | - FMDB/standard (2.7.5) 6 | - sqflite (0.0.2): 7 | - Flutter 8 | - FMDB (>= 2.7.5) 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - sqflite (from `.symlinks/plugins/sqflite/ios`) 13 | 14 | SPEC REPOS: 15 | trunk: 16 | - FMDB 17 | 18 | EXTERNAL SOURCES: 19 | Flutter: 20 | :path: Flutter 21 | sqflite: 22 | :path: ".symlinks/plugins/sqflite/ios" 23 | 24 | SPEC CHECKSUMS: 25 | Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c 26 | FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a 27 | sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 28 | 29 | PODFILE CHECKSUM: 4f075935f8d51cabd6114ffcdb57f90115d955bd 30 | 31 | COCOAPODS: 1.10.1 32 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 13 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 14 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 15 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 16 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 17 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 18 | CA4C2C696631918E536392D1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AD1639E663229C8892201C31 /* Pods_Runner.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXCopyFilesBuildPhase section */ 22 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 23 | isa = PBXCopyFilesBuildPhase; 24 | buildActionMask = 2147483647; 25 | dstPath = ""; 26 | dstSubfolderSpec = 10; 27 | files = ( 28 | ); 29 | name = "Embed Frameworks"; 30 | runOnlyForDeploymentPostprocessing = 0; 31 | }; 32 | /* End PBXCopyFilesBuildPhase section */ 33 | 34 | /* Begin PBXFileReference section */ 35 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 36 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 37 | 36BA2DDDDB4578813647A2E5 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 38 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 39 | 4EBC5C03CEC34C3508869607 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 40 | 4FD47B8D06E5A66BA1345F59 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 41 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 42 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 43 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 44 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 45 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 46 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 47 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 48 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 49 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 50 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 51 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 52 | AD1639E663229C8892201C31 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 53 | /* End PBXFileReference section */ 54 | 55 | /* Begin PBXFrameworksBuildPhase section */ 56 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 57 | isa = PBXFrameworksBuildPhase; 58 | buildActionMask = 2147483647; 59 | files = ( 60 | CA4C2C696631918E536392D1 /* Pods_Runner.framework in Frameworks */, 61 | ); 62 | runOnlyForDeploymentPostprocessing = 0; 63 | }; 64 | /* End PBXFrameworksBuildPhase section */ 65 | 66 | /* Begin PBXGroup section */ 67 | 897AC2EF7496BBE14CACB1FC /* Pods */ = { 68 | isa = PBXGroup; 69 | children = ( 70 | 36BA2DDDDB4578813647A2E5 /* Pods-Runner.debug.xcconfig */, 71 | 4EBC5C03CEC34C3508869607 /* Pods-Runner.release.xcconfig */, 72 | 4FD47B8D06E5A66BA1345F59 /* Pods-Runner.profile.xcconfig */, 73 | ); 74 | name = Pods; 75 | path = Pods; 76 | sourceTree = ""; 77 | }; 78 | 9740EEB11CF90186004384FC /* Flutter */ = { 79 | isa = PBXGroup; 80 | children = ( 81 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 82 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 83 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 84 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 85 | ); 86 | name = Flutter; 87 | sourceTree = ""; 88 | }; 89 | 97C146E51CF9000F007C117D = { 90 | isa = PBXGroup; 91 | children = ( 92 | 9740EEB11CF90186004384FC /* Flutter */, 93 | 97C146F01CF9000F007C117D /* Runner */, 94 | 97C146EF1CF9000F007C117D /* Products */, 95 | 897AC2EF7496BBE14CACB1FC /* Pods */, 96 | BA668DB416733BED75D9CC3B /* Frameworks */, 97 | ); 98 | sourceTree = ""; 99 | }; 100 | 97C146EF1CF9000F007C117D /* Products */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 97C146EE1CF9000F007C117D /* Runner.app */, 104 | ); 105 | name = Products; 106 | sourceTree = ""; 107 | }; 108 | 97C146F01CF9000F007C117D /* Runner */ = { 109 | isa = PBXGroup; 110 | children = ( 111 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 112 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 113 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 114 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 115 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 116 | 97C147021CF9000F007C117D /* Info.plist */, 117 | 97C146F11CF9000F007C117D /* Supporting Files */, 118 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 119 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 120 | ); 121 | path = Runner; 122 | sourceTree = ""; 123 | }; 124 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 97C146F21CF9000F007C117D /* main.m */, 128 | ); 129 | name = "Supporting Files"; 130 | sourceTree = ""; 131 | }; 132 | BA668DB416733BED75D9CC3B /* Frameworks */ = { 133 | isa = PBXGroup; 134 | children = ( 135 | AD1639E663229C8892201C31 /* Pods_Runner.framework */, 136 | ); 137 | name = Frameworks; 138 | sourceTree = ""; 139 | }; 140 | /* End PBXGroup section */ 141 | 142 | /* Begin PBXNativeTarget section */ 143 | 97C146ED1CF9000F007C117D /* Runner */ = { 144 | isa = PBXNativeTarget; 145 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 146 | buildPhases = ( 147 | 3D99DA4A42EA2C2488C3D9C8 /* [CP] Check Pods Manifest.lock */, 148 | 9740EEB61CF901F6004384FC /* Run Script */, 149 | 97C146EA1CF9000F007C117D /* Sources */, 150 | 97C146EB1CF9000F007C117D /* Frameworks */, 151 | 97C146EC1CF9000F007C117D /* Resources */, 152 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 153 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 154 | 071B788AA00F0838DA756471 /* [CP] Embed Pods Frameworks */, 155 | ); 156 | buildRules = ( 157 | ); 158 | dependencies = ( 159 | ); 160 | name = Runner; 161 | productName = Runner; 162 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 163 | productType = "com.apple.product-type.application"; 164 | }; 165 | /* End PBXNativeTarget section */ 166 | 167 | /* Begin PBXProject section */ 168 | 97C146E61CF9000F007C117D /* Project object */ = { 169 | isa = PBXProject; 170 | attributes = { 171 | LastUpgradeCheck = 0910; 172 | ORGANIZATIONNAME = "The Chromium Authors"; 173 | TargetAttributes = { 174 | 97C146ED1CF9000F007C117D = { 175 | CreatedOnToolsVersion = 7.3.1; 176 | }; 177 | }; 178 | }; 179 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 180 | compatibilityVersion = "Xcode 3.2"; 181 | developmentRegion = English; 182 | hasScannedForEncodings = 0; 183 | knownRegions = ( 184 | en, 185 | Base, 186 | ); 187 | mainGroup = 97C146E51CF9000F007C117D; 188 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 189 | projectDirPath = ""; 190 | projectRoot = ""; 191 | targets = ( 192 | 97C146ED1CF9000F007C117D /* Runner */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | 97C146EC1CF9000F007C117D /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 203 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 204 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 205 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 206 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 207 | ); 208 | runOnlyForDeploymentPostprocessing = 0; 209 | }; 210 | /* End PBXResourcesBuildPhase section */ 211 | 212 | /* Begin PBXShellScriptBuildPhase section */ 213 | 071B788AA00F0838DA756471 /* [CP] Embed Pods Frameworks */ = { 214 | isa = PBXShellScriptBuildPhase; 215 | buildActionMask = 2147483647; 216 | files = ( 217 | ); 218 | inputPaths = ( 219 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 220 | "${BUILT_PRODUCTS_DIR}/FMDB/FMDB.framework", 221 | "${BUILT_PRODUCTS_DIR}/sqflite/sqflite.framework", 222 | ); 223 | name = "[CP] Embed Pods Frameworks"; 224 | outputPaths = ( 225 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FMDB.framework", 226 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sqflite.framework", 227 | ); 228 | runOnlyForDeploymentPostprocessing = 0; 229 | shellPath = /bin/sh; 230 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 231 | showEnvVarsInLog = 0; 232 | }; 233 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 234 | isa = PBXShellScriptBuildPhase; 235 | buildActionMask = 2147483647; 236 | files = ( 237 | ); 238 | inputPaths = ( 239 | ); 240 | name = "Thin Binary"; 241 | outputPaths = ( 242 | ); 243 | runOnlyForDeploymentPostprocessing = 0; 244 | shellPath = /bin/sh; 245 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 246 | }; 247 | 3D99DA4A42EA2C2488C3D9C8 /* [CP] Check Pods Manifest.lock */ = { 248 | isa = PBXShellScriptBuildPhase; 249 | buildActionMask = 2147483647; 250 | files = ( 251 | ); 252 | inputFileListPaths = ( 253 | ); 254 | inputPaths = ( 255 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 256 | "${PODS_ROOT}/Manifest.lock", 257 | ); 258 | name = "[CP] Check Pods Manifest.lock"; 259 | outputFileListPaths = ( 260 | ); 261 | outputPaths = ( 262 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | shellPath = /bin/sh; 266 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 267 | showEnvVarsInLog = 0; 268 | }; 269 | 9740EEB61CF901F6004384FC /* Run Script */ = { 270 | isa = PBXShellScriptBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | ); 274 | inputPaths = ( 275 | ); 276 | name = "Run Script"; 277 | outputPaths = ( 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | shellPath = /bin/sh; 281 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 282 | }; 283 | /* End PBXShellScriptBuildPhase section */ 284 | 285 | /* Begin PBXSourcesBuildPhase section */ 286 | 97C146EA1CF9000F007C117D /* Sources */ = { 287 | isa = PBXSourcesBuildPhase; 288 | buildActionMask = 2147483647; 289 | files = ( 290 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 291 | 97C146F31CF9000F007C117D /* main.m in Sources */, 292 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | /* End PBXSourcesBuildPhase section */ 297 | 298 | /* Begin PBXVariantGroup section */ 299 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 300 | isa = PBXVariantGroup; 301 | children = ( 302 | 97C146FB1CF9000F007C117D /* Base */, 303 | ); 304 | name = Main.storyboard; 305 | sourceTree = ""; 306 | }; 307 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 308 | isa = PBXVariantGroup; 309 | children = ( 310 | 97C147001CF9000F007C117D /* Base */, 311 | ); 312 | name = LaunchScreen.storyboard; 313 | sourceTree = ""; 314 | }; 315 | /* End PBXVariantGroup section */ 316 | 317 | /* Begin XCBuildConfiguration section */ 318 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 319 | isa = XCBuildConfiguration; 320 | buildSettings = { 321 | ALWAYS_SEARCH_USER_PATHS = NO; 322 | CLANG_ANALYZER_NONNULL = YES; 323 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 324 | CLANG_CXX_LIBRARY = "libc++"; 325 | CLANG_ENABLE_MODULES = YES; 326 | CLANG_ENABLE_OBJC_ARC = YES; 327 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 328 | CLANG_WARN_BOOL_CONVERSION = YES; 329 | CLANG_WARN_COMMA = YES; 330 | CLANG_WARN_CONSTANT_CONVERSION = YES; 331 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 332 | CLANG_WARN_EMPTY_BODY = YES; 333 | CLANG_WARN_ENUM_CONVERSION = YES; 334 | CLANG_WARN_INFINITE_RECURSION = YES; 335 | CLANG_WARN_INT_CONVERSION = YES; 336 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 337 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 338 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 339 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 340 | CLANG_WARN_STRICT_PROTOTYPES = YES; 341 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 342 | CLANG_WARN_UNREACHABLE_CODE = YES; 343 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 344 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 345 | COPY_PHASE_STRIP = NO; 346 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 347 | ENABLE_NS_ASSERTIONS = NO; 348 | ENABLE_STRICT_OBJC_MSGSEND = YES; 349 | GCC_C_LANGUAGE_STANDARD = gnu99; 350 | GCC_NO_COMMON_BLOCKS = YES; 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 358 | MTL_ENABLE_DEBUG_INFO = NO; 359 | SDKROOT = iphoneos; 360 | TARGETED_DEVICE_FAMILY = "1,2"; 361 | VALIDATE_PRODUCT = YES; 362 | }; 363 | name = Profile; 364 | }; 365 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 366 | isa = XCBuildConfiguration; 367 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 368 | buildSettings = { 369 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 370 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 371 | DEVELOPMENT_TEAM = S8QB4VV633; 372 | ENABLE_BITCODE = NO; 373 | FRAMEWORK_SEARCH_PATHS = ( 374 | "$(inherited)", 375 | "$(PROJECT_DIR)/Flutter", 376 | ); 377 | INFOPLIST_FILE = Runner/Info.plist; 378 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 379 | LIBRARY_SEARCH_PATHS = ( 380 | "$(inherited)", 381 | "$(PROJECT_DIR)/Flutter", 382 | ); 383 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 384 | PRODUCT_NAME = "$(TARGET_NAME)"; 385 | VERSIONING_SYSTEM = "apple-generic"; 386 | }; 387 | name = Profile; 388 | }; 389 | 97C147031CF9000F007C117D /* Debug */ = { 390 | isa = XCBuildConfiguration; 391 | buildSettings = { 392 | ALWAYS_SEARCH_USER_PATHS = NO; 393 | CLANG_ANALYZER_NONNULL = YES; 394 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 395 | CLANG_CXX_LIBRARY = "libc++"; 396 | CLANG_ENABLE_MODULES = YES; 397 | CLANG_ENABLE_OBJC_ARC = YES; 398 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 399 | CLANG_WARN_BOOL_CONVERSION = YES; 400 | CLANG_WARN_COMMA = YES; 401 | CLANG_WARN_CONSTANT_CONVERSION = YES; 402 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 403 | CLANG_WARN_EMPTY_BODY = YES; 404 | CLANG_WARN_ENUM_CONVERSION = YES; 405 | CLANG_WARN_INFINITE_RECURSION = YES; 406 | CLANG_WARN_INT_CONVERSION = YES; 407 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 408 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 409 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 410 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 411 | CLANG_WARN_STRICT_PROTOTYPES = YES; 412 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 413 | CLANG_WARN_UNREACHABLE_CODE = YES; 414 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 415 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 416 | COPY_PHASE_STRIP = NO; 417 | DEBUG_INFORMATION_FORMAT = dwarf; 418 | ENABLE_STRICT_OBJC_MSGSEND = YES; 419 | ENABLE_TESTABILITY = YES; 420 | GCC_C_LANGUAGE_STANDARD = gnu99; 421 | GCC_DYNAMIC_NO_PIC = NO; 422 | GCC_NO_COMMON_BLOCKS = YES; 423 | GCC_OPTIMIZATION_LEVEL = 0; 424 | GCC_PREPROCESSOR_DEFINITIONS = ( 425 | "DEBUG=1", 426 | "$(inherited)", 427 | ); 428 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 429 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 430 | GCC_WARN_UNDECLARED_SELECTOR = YES; 431 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 432 | GCC_WARN_UNUSED_FUNCTION = YES; 433 | GCC_WARN_UNUSED_VARIABLE = YES; 434 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 435 | MTL_ENABLE_DEBUG_INFO = YES; 436 | ONLY_ACTIVE_ARCH = YES; 437 | SDKROOT = iphoneos; 438 | TARGETED_DEVICE_FAMILY = "1,2"; 439 | }; 440 | name = Debug; 441 | }; 442 | 97C147041CF9000F007C117D /* Release */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | ALWAYS_SEARCH_USER_PATHS = NO; 446 | CLANG_ANALYZER_NONNULL = YES; 447 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 448 | CLANG_CXX_LIBRARY = "libc++"; 449 | CLANG_ENABLE_MODULES = YES; 450 | CLANG_ENABLE_OBJC_ARC = YES; 451 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 452 | CLANG_WARN_BOOL_CONVERSION = YES; 453 | CLANG_WARN_COMMA = YES; 454 | CLANG_WARN_CONSTANT_CONVERSION = YES; 455 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 456 | CLANG_WARN_EMPTY_BODY = YES; 457 | CLANG_WARN_ENUM_CONVERSION = YES; 458 | CLANG_WARN_INFINITE_RECURSION = YES; 459 | CLANG_WARN_INT_CONVERSION = YES; 460 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 461 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 462 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 463 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 464 | CLANG_WARN_STRICT_PROTOTYPES = YES; 465 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 466 | CLANG_WARN_UNREACHABLE_CODE = YES; 467 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 468 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 469 | COPY_PHASE_STRIP = NO; 470 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 471 | ENABLE_NS_ASSERTIONS = NO; 472 | ENABLE_STRICT_OBJC_MSGSEND = YES; 473 | GCC_C_LANGUAGE_STANDARD = gnu99; 474 | GCC_NO_COMMON_BLOCKS = YES; 475 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 476 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 477 | GCC_WARN_UNDECLARED_SELECTOR = YES; 478 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 479 | GCC_WARN_UNUSED_FUNCTION = YES; 480 | GCC_WARN_UNUSED_VARIABLE = YES; 481 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 482 | MTL_ENABLE_DEBUG_INFO = NO; 483 | SDKROOT = iphoneos; 484 | TARGETED_DEVICE_FAMILY = "1,2"; 485 | VALIDATE_PRODUCT = YES; 486 | }; 487 | name = Release; 488 | }; 489 | 97C147061CF9000F007C117D /* Debug */ = { 490 | isa = XCBuildConfiguration; 491 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 492 | buildSettings = { 493 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 494 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 495 | ENABLE_BITCODE = NO; 496 | FRAMEWORK_SEARCH_PATHS = ( 497 | "$(inherited)", 498 | "$(PROJECT_DIR)/Flutter", 499 | ); 500 | INFOPLIST_FILE = Runner/Info.plist; 501 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 502 | LIBRARY_SEARCH_PATHS = ( 503 | "$(inherited)", 504 | "$(PROJECT_DIR)/Flutter", 505 | ); 506 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 507 | PRODUCT_NAME = "$(TARGET_NAME)"; 508 | VERSIONING_SYSTEM = "apple-generic"; 509 | }; 510 | name = Debug; 511 | }; 512 | 97C147071CF9000F007C117D /* Release */ = { 513 | isa = XCBuildConfiguration; 514 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 515 | buildSettings = { 516 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 517 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 518 | ENABLE_BITCODE = NO; 519 | FRAMEWORK_SEARCH_PATHS = ( 520 | "$(inherited)", 521 | "$(PROJECT_DIR)/Flutter", 522 | ); 523 | INFOPLIST_FILE = Runner/Info.plist; 524 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 525 | LIBRARY_SEARCH_PATHS = ( 526 | "$(inherited)", 527 | "$(PROJECT_DIR)/Flutter", 528 | ); 529 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example; 530 | PRODUCT_NAME = "$(TARGET_NAME)"; 531 | VERSIONING_SYSTEM = "apple-generic"; 532 | }; 533 | name = Release; 534 | }; 535 | /* End XCBuildConfiguration section */ 536 | 537 | /* Begin XCConfigurationList section */ 538 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 539 | isa = XCConfigurationList; 540 | buildConfigurations = ( 541 | 97C147031CF9000F007C117D /* Debug */, 542 | 97C147041CF9000F007C117D /* Release */, 543 | 249021D3217E4FDB00AE95B9 /* Profile */, 544 | ); 545 | defaultConfigurationIsVisible = 0; 546 | defaultConfigurationName = Release; 547 | }; 548 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 549 | isa = XCConfigurationList; 550 | buildConfigurations = ( 551 | 97C147061CF9000F007C117D /* Debug */, 552 | 97C147071CF9000F007C117D /* Release */, 553 | 249021D4217E4FDB00AE95B9 /* Profile */, 554 | ); 555 | defaultConfigurationIsVisible = 0; 556 | defaultConfigurationName = Release; 557 | }; 558 | /* End XCConfigurationList section */ 559 | }; 560 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 561 | } 562 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hurshi/dio-http-cache/2ddd64b7e982aec2bb6cc251d8e33254230cc664/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/dio_helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio/adapter.dart'; 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_http_cache/dio_http_cache.dart'; 6 | 7 | class DioHelper { 8 | static Dio? _dio; 9 | static DioCacheManager? _manager; 10 | static final baseUrl = "https://www.wanandroid.com/"; 11 | 12 | static Dio getDio() { 13 | if (null == _dio) { 14 | _dio = Dio(BaseOptions( 15 | baseUrl: baseUrl, 16 | contentType: "application/x-www-form-urlencoded; charset=utf-8")) 17 | // ..httpClientAdapter = _getHttpClientAdapter() 18 | ..interceptors.add(getCacheManager().interceptor) 19 | ..interceptors.add(LogInterceptor(responseBody: true)); 20 | } 21 | return _dio!; 22 | } 23 | 24 | static DioCacheManager getCacheManager() { 25 | if (null == _manager) { 26 | _manager = 27 | DioCacheManager(CacheConfig(baseUrl: "https://www.wanandroid.com/")); 28 | } 29 | return _manager!; 30 | } 31 | 32 | // set proxy 33 | static DefaultHttpClientAdapter _getHttpClientAdapter() { 34 | DefaultHttpClientAdapter httpClientAdapter; 35 | httpClientAdapter = DefaultHttpClientAdapter(); 36 | httpClientAdapter.onHttpClientCreate = (HttpClient client) { 37 | client.findProxy = (uri) { 38 | return 'PROXY 10.0.0.103:6152'; 39 | }; 40 | client.badCertificateCallback = 41 | (X509Certificate cert, String host, int port) { 42 | return true; 43 | }; 44 | }; 45 | return httpClientAdapter; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'panels/cache_manage.dart'; 4 | import 'panels/panel_204.dart'; 5 | import 'panels/panel_get.dart'; 6 | import 'panels/panel_get_bytes.dart'; 7 | import 'panels/panel_get_from_service.dart'; 8 | import 'panels/panel_post.dart'; 9 | import 'tuple.dart'; 10 | 11 | void main() => runApp(MyApp()); 12 | 13 | class MyApp extends StatelessWidget { 14 | @override 15 | Widget build(BuildContext context) { 16 | final title = "DioHttpCache Example"; 17 | return MaterialApp( 18 | title: title, 19 | theme: ThemeData(primarySwatch: Colors.blue), 20 | home: MyHomePage(title: title)); 21 | } 22 | } 23 | 24 | class MyHomePage extends StatefulWidget { 25 | MyHomePage({Key? key, this.title}) : super(key: key); 26 | final String? title; 27 | 28 | @override 29 | _MyHomePageState createState() => _MyHomePageState(); 30 | } 31 | 32 | enum Panel { CACHE_MANAGER, POST, POST_204, GET_FROM_SERVICE, GET, GET_BYTES } 33 | 34 | class _MyHomePageState extends State { 35 | Panel panel = Panel.POST; 36 | 37 | @override 38 | Widget build(BuildContext context) { 39 | return Scaffold( 40 | appBar: AppBar( 41 | title: Text(widget.title!), 42 | actions: [_buildHomePageActionButtons(context)]), 43 | body: getPanel()); 44 | } 45 | 46 | Widget? getPanel() { 47 | switch (panel) { 48 | case Panel.CACHE_MANAGER: 49 | return CacheManagerPanel(); 50 | case Panel.GET: 51 | return GetPanel(); 52 | case Panel.POST: 53 | return PostPanel(); 54 | case Panel.POST_204: 55 | return Post204Panel(); 56 | case Panel.GET_FROM_SERVICE: 57 | return PostGetLetServicePanel(); 58 | case Panel.GET_BYTES: 59 | return GetBytesPanel(); 60 | } 61 | } 62 | 63 | Widget _buildHomePageActionButtons(BuildContext context) { 64 | List> choices = [ 65 | Pair("Cache Manager", () => setState(() => panel = Panel.CACHE_MANAGER)), 66 | Pair("POST", () => setState(() => panel = Panel.POST)), 67 | Pair("POST 204", () => setState(() => panel = Panel.POST_204)), 68 | Pair("GET", () => setState(() => panel = Panel.GET)), 69 | Pair("GET from service", 70 | () => setState(() => panel = Panel.GET_FROM_SERVICE)), 71 | Pair("GET byte array", () => setState(() => panel = Panel.GET_BYTES)), 72 | ]; 73 | return PopupMenuButton>( 74 | onSelected: (p) => p.i1!(), 75 | child: Padding( 76 | padding: EdgeInsets.all(10), 77 | child: Icon(Icons.menu, color: Colors.white)), 78 | itemBuilder: (BuildContext context) => choices 79 | .map((choice) => PopupMenuItem>( 80 | value: choice, child: Text(choice.i0!))) 81 | .toList()); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/lib/panels/cache_manage.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/dio_http_cache.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../dio_helper.dart'; 5 | 6 | class CacheManagerPanel extends StatefulWidget { 7 | @override 8 | State createState() => _CacheManagerPanelState(); 9 | } 10 | 11 | enum _Mode { clearByKey, clearByKeyAndSubKey, clearAll } 12 | 13 | class MyDiskStore implements ICacheStore { 14 | @override 15 | Future clearAll() { 16 | // TODO: implement clearAll 17 | throw UnimplementedError(); 18 | } 19 | 20 | @override 21 | Future clearExpired() { 22 | // TODO: implement clearExpired 23 | throw UnimplementedError(); 24 | } 25 | 26 | @override 27 | Future delete(String key, {String? subKey}) { 28 | // TODO: implement delete 29 | throw UnimplementedError(); 30 | } 31 | 32 | @override 33 | Future getCacheObj(String key, {String? subKey}) { 34 | // TODO: implement getCacheObj 35 | throw UnimplementedError(); 36 | } 37 | 38 | @override 39 | Future setCacheObj(CacheObj obj) { 40 | // TODO: implement setCacheObj 41 | throw UnimplementedError(); 42 | } 43 | } 44 | 45 | class _CacheManagerPanelState extends State { 46 | _Mode? _mode = _Mode.clearAll; 47 | var _url = "article/query/0/json"; 48 | var _keyController = TextEditingController(); 49 | var _requestMethodController = TextEditingController(); 50 | var _subKeyController = TextEditingController(); 51 | 52 | @override 53 | Widget build(BuildContext context) { 54 | return Padding( 55 | padding: EdgeInsets.all(10), 56 | child: Column( 57 | crossAxisAlignment: CrossAxisAlignment.start, 58 | children: [ 59 | Text("Cache Manager", 60 | style: Theme.of(context) 61 | .textTheme 62 | .headline6! 63 | .copyWith(color: Theme.of(context).accentColor)), 64 | Container(height: 50), 65 | Text("1. Choose mode:", 66 | style: Theme.of(context) 67 | .textTheme 68 | .subtitle2! 69 | .copyWith(color: Theme.of(context).accentColor)), 70 | DropdownButton<_Mode>( 71 | value: _mode, 72 | onChanged: (value) => setState(() => _mode = value), 73 | items: <_Mode>[ 74 | _Mode.clearByKey, 75 | _Mode.clearByKeyAndSubKey, 76 | _Mode.clearAll 77 | ] 78 | .map>((value) => 79 | DropdownMenuItem<_Mode>( 80 | value: value, child: Text(getTxtByMode(value)))) 81 | .toList()), 82 | Container(height: 20), 83 | for (var w in getRequestMethodViews(context)) w, 84 | Container(height: 20), 85 | for (var w in getKeyViews(context)) w, 86 | Container(height: 20), 87 | for (var w in getSubKeyViews(context)) w, 88 | Container(height: 20), 89 | Text("${getLabel()}. to clear", 90 | style: Theme.of(context) 91 | .textTheme 92 | .subtitle2! 93 | .copyWith(color: Theme.of(context).accentColor)), 94 | Padding( 95 | padding: EdgeInsets.all(10), 96 | child: FloatingActionButton( 97 | child: Text("Clear", 98 | style: Theme.of(context) 99 | .textTheme 100 | .subtitle2! 101 | .copyWith(color: Colors.white)), 102 | onPressed: () => _clear())) 103 | ])); 104 | } 105 | 106 | void _clear() { 107 | var resultPrinter = (result) => showSnackBar("缓存清理${result ? '成功' : '失败'}"); 108 | if (_mode == _Mode.clearAll) { 109 | DioHelper.getCacheManager().clearAll().then(resultPrinter); 110 | } else if (_mode == _Mode.clearByKey) { 111 | DioHelper.getCacheManager() 112 | .deleteByPrimaryKey(_keyController.text, 113 | requestMethod: _requestMethodController.text) 114 | .then(resultPrinter); 115 | } else if (_mode == _Mode.clearByKeyAndSubKey) { 116 | DioHelper.getCacheManager() 117 | .deleteByPrimaryKeyAndSubKey(_keyController.text, 118 | requestMethod: _requestMethodController.text, 119 | subKey: _subKeyController.text) 120 | .then(resultPrinter); 121 | } 122 | } 123 | 124 | void showSnackBar(String msg) { 125 | ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg))); 126 | } 127 | 128 | List getRequestMethodViews(BuildContext context) { 129 | if (_mode == _Mode.clearAll) return []; 130 | _requestMethodController.text = "POST"; 131 | return [ 132 | Text("2. RequestMethod:", 133 | style: Theme.of(context) 134 | .textTheme 135 | .subtitle2! 136 | .copyWith(color: Theme.of(context).accentColor)), 137 | TextField( 138 | controller: _requestMethodController, 139 | style: Theme.of(context).textTheme.bodyText1), 140 | Container(height: 20), 141 | ]; 142 | } 143 | 144 | List getKeyViews(BuildContext context) { 145 | if (_mode == _Mode.clearAll) return []; 146 | _keyController.text = "${DioHelper.baseUrl}$_url"; 147 | return [ 148 | Text("3. Key:", 149 | style: Theme.of(context) 150 | .textTheme 151 | .subtitle2! 152 | .copyWith(color: Theme.of(context).accentColor)), 153 | TextField( 154 | controller: _keyController, 155 | style: Theme.of(context).textTheme.bodyText1), 156 | Container(height: 20), 157 | ]; 158 | } 159 | 160 | List getSubKeyViews(BuildContext context) { 161 | if (_mode == _Mode.clearAll || _mode == _Mode.clearByKey) return []; 162 | _subKeyController.text = "k=flutter"; 163 | return [ 164 | Text("4. Subkey:", 165 | style: Theme.of(context) 166 | .textTheme 167 | .subtitle2! 168 | .copyWith(color: Theme.of(context).accentColor)), 169 | TextField( 170 | controller: _subKeyController, 171 | style: Theme.of(context).textTheme.bodyText1), 172 | Container(height: 20), 173 | ]; 174 | } 175 | 176 | String getLabel() { 177 | if (_mode == _Mode.clearAll) 178 | return "2"; 179 | else if (_mode == _Mode.clearByKey) 180 | return "4"; 181 | else 182 | return "5"; 183 | } 184 | 185 | String getTxtByMode(_Mode mode) { 186 | if (mode == _Mode.clearAll) 187 | return "Clear All"; 188 | else if (mode == _Mode.clearByKey) 189 | return "Clear by Key"; 190 | else 191 | return "Clear By Key and SubKey"; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /example/lib/panels/helper.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:dio/dio.dart'; 4 | import 'package:dio_http_cache/dio_http_cache.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | import '../dio_helper.dart'; 8 | 9 | class PanelHelper { 10 | static Map getPrintContent( 11 | String key, String? subKey, Response response) { 12 | var result = { 13 | "Cached": "", 14 | "Key": 15 | "${key.startsWith(RegExp(r'https?:')) ? '' : DioHelper.baseUrl}$key" 16 | }; 17 | if (null != subKey && subKey.length > 0) { 18 | result.addAll({"subKey": subKey}); 19 | } 20 | if (null != response.headers.value(DIO_CACHE_HEADER_KEY_DATA_SOURCE)) { 21 | result.addAll({"data source": "data come from cache"}); 22 | } else { 23 | result.addAll({"data source": "data come from net"}); 24 | } 25 | result.addAll({ 26 | "statusCode": "${response.statusCode}", 27 | "Header": response.headers.toString(), 28 | "Content": jsonEncode(response.data) 29 | }); 30 | return result; 31 | } 32 | 33 | static Map getPrintError( 34 | dynamic onError, dynamic stackTrace) { 35 | return { 36 | "Error occur": "", 37 | "onError": onError.toString(), 38 | "stackTrace": stackTrace.toString() 39 | }; 40 | } 41 | 42 | static Widget buildNormalPanel( 43 | String title, 44 | TextEditingController? _urlController, 45 | TextEditingController? _paramsController, 46 | Map txt, 47 | Function() request) { 48 | return Builder( 49 | builder: (context) => Padding( 50 | padding: EdgeInsets.all(10), 51 | child: Column( 52 | crossAxisAlignment: CrossAxisAlignment.start, 53 | children: [ 54 | Text("NOTE: $title", 55 | style: Theme.of(context) 56 | .textTheme 57 | .subtitle2! 58 | .copyWith(color: Theme.of(context).primaryColor)), 59 | Container(height: 20), 60 | Text("Base url:", 61 | style: Theme.of(context) 62 | .textTheme 63 | .subtitle2! 64 | .copyWith(color: Theme.of(context).accentColor)), 65 | Text(DioHelper.baseUrl, 66 | style: Theme.of(context) 67 | .textTheme 68 | .bodyText2! 69 | .copyWith(color: Colors.grey)), 70 | for (var w in _buildInput( 71 | context, 72 | _urlController, 73 | "Request url", 74 | (null == _paramsController) ? request : null)) 75 | w, 76 | for (var w in _buildInput( 77 | context, _paramsController, "Request params", request)) 78 | w, 79 | Expanded( 80 | child: SingleChildScrollView( 81 | child: RichText( 82 | text: TextSpan(children: [ 83 | for (var w in _buildConsoleOutput(context, txt)) w, 84 | ])))) 85 | ]))); 86 | } 87 | 88 | static List _buildConsoleOutput( 89 | BuildContext context, Map map) { 90 | List widgets = []; 91 | map.forEach((k, v) { 92 | widgets.add(TextSpan( 93 | text: "$k: ", 94 | style: Theme.of(context) 95 | .textTheme 96 | .subtitle2! 97 | .copyWith(color: Colors.teal))); 98 | widgets.add(TextSpan( 99 | text: "$v\n\n", 100 | style: Theme.of(context) 101 | .textTheme 102 | .bodyText2! 103 | .copyWith(color: Theme.of(context).disabledColor))); 104 | }); 105 | return widgets; 106 | } 107 | 108 | static List _buildInput(BuildContext context, 109 | TextEditingController? controller, String title, Function()? request) { 110 | if (null == controller) return []; 111 | return [ 112 | Container(height: 20), 113 | Text("$title:", 114 | style: Theme.of(context) 115 | .textTheme 116 | .subtitle2! 117 | .copyWith(color: Theme.of(context).accentColor)), 118 | Row(children: [ 119 | Expanded(child: TextField(controller: controller)), 120 | for (var w in _buildRequestButton(context, request)) w, 121 | ]), 122 | ]; 123 | } 124 | 125 | static List _buildRequestButton( 126 | BuildContext context, Function()? request) { 127 | if (null != request) 128 | return [ 129 | Container(width: 10), 130 | FloatingActionButton( 131 | child: Text("GO", 132 | style: Theme.of(context) 133 | .textTheme 134 | .subtitle2! 135 | .copyWith(color: Colors.white)), 136 | onPressed: () => request()) 137 | ]; 138 | return []; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /example/lib/panels/panel_204.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/dio_http_cache.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../dio_helper.dart'; 5 | import 'helper.dart'; 6 | 7 | class Post204Panel extends StatefulWidget { 8 | @override 9 | State createState() => _Post204PanelState(); 10 | } 11 | 12 | class _Post204PanelState extends State { 13 | Map _content = {"Hello ~": ""}; 14 | var _url = "https://www.v2ex.com/generate_204"; 15 | var _urlController; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _urlController = TextEditingController(text: _url); 21 | } 22 | 23 | void _doRequest() { 24 | setState(() => _content = {"Requesting": _url}); 25 | DioHelper.getDio() 26 | .post(_urlController.text, 27 | options: buildCacheOptions(Duration(hours: 1))) 28 | .then((response) => setState( 29 | () => _content = PanelHelper.getPrintContent(_url, null, response))) 30 | .catchError((onError, stackTrace) => setState( 31 | () => _content = PanelHelper.getPrintError(onError, stackTrace))); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return PanelHelper.buildNormalPanel( 37 | "Cache 204 request", _urlController, null, _content, _doRequest); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/lib/panels/panel_get.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/dio_http_cache.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../dio_helper.dart'; 5 | import 'helper.dart'; 6 | 7 | class GetPanel extends StatefulWidget { 8 | @override 9 | State createState() => _GetPanelState(); 10 | } 11 | 12 | class _GetPanelState extends State { 13 | Map _content = {"Hello ~": ""}; 14 | var _url = "https://www.baidu.com"; 15 | var _urlController; 16 | 17 | @override 18 | void initState() { 19 | super.initState(); 20 | _urlController = TextEditingController(text: _url); 21 | } 22 | 23 | void _doRequest() { 24 | setState(() => _content = {"Requesting": _url}); 25 | DioHelper.getDio() 26 | .get(_urlController.text, 27 | options: buildCacheOptions(Duration(hours: 1), forceRefresh: false)) 28 | .then((response) => setState( 29 | () => _content = PanelHelper.getPrintContent(_url, null, response))) 30 | .catchError((onError, stackTrace) => setState( 31 | () => _content = PanelHelper.getPrintError(onError, stackTrace))); 32 | } 33 | 34 | @override 35 | Widget build(BuildContext context) { 36 | return PanelHelper.buildNormalPanel( 37 | "Normal GET example", _urlController, null, _content, _doRequest); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /example/lib/panels/panel_get_bytes.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:dio_http_cache/dio_http_cache.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | import '../dio_helper.dart'; 6 | import 'helper.dart'; 7 | 8 | class GetBytesPanel extends StatefulWidget { 9 | @override 10 | State createState() => _GetBytesPanelState(); 11 | } 12 | 13 | class _GetBytesPanelState extends State { 14 | Map _content = {"Hello ~": ""}; 15 | var _url = 16 | "https://ss0.baidu.com/6ONWsjip0QIZ8tyhnq/it/u=1731248121,277041329&fm=58&s=0DE6CD13D1A06D015651B0D6000080B1&bpow=121&bpoh=75"; 17 | var _urlController; 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _urlController = TextEditingController(text: _url); 23 | } 24 | 25 | void _doRequest() { 26 | setState(() => _content = {"Requesting": _url}); 27 | DioHelper.getDio() 28 | .get(_urlController.text, 29 | options: buildCacheOptions(Duration(hours: 1), 30 | forceRefresh: false, 31 | options: Options(responseType: ResponseType.bytes))) 32 | .then((response) => setState( 33 | () => _content = PanelHelper.getPrintContent(_url, null, response))) 34 | .catchError((onError, stackTrace) => setState( 35 | () => _content = PanelHelper.getPrintError(onError, stackTrace))); 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | return PanelHelper.buildNormalPanel( 41 | "GET bytes example", _urlController, null, _content, _doRequest); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /example/lib/panels/panel_get_from_service.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/dio_http_cache.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../dio_helper.dart'; 5 | import 'helper.dart'; 6 | 7 | class PostGetLetServicePanel extends StatefulWidget { 8 | @override 9 | State createState() => _PostGetLetServicePanelState(); 10 | } 11 | 12 | class _PostGetLetServicePanelState extends State { 13 | Map _content = {"Hello ~": ""}; 14 | var _url = 15 | "https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control"; 16 | var _urlController; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _urlController = TextEditingController(text: _url); 22 | } 23 | 24 | void _doRequest() { 25 | setState(() => _content = {"Requesting": _url}); 26 | DioHelper.getDio() 27 | .get(_urlController.text, 28 | options: buildServiceCacheOptions(forceRefresh: false)) 29 | .then((response) => setState( 30 | () => _content = PanelHelper.getPrintContent(_url, null, response))) 31 | .catchError((onError, stackTrace) => setState( 32 | () => _content = PanelHelper.getPrintError(onError, stackTrace))); 33 | } 34 | 35 | @override 36 | Widget build(BuildContext context) { 37 | return PanelHelper.buildNormalPanel( 38 | "Cache strategy by service, try to read maxAge and maxStale from response http head.", 39 | _urlController, 40 | null, 41 | _content, 42 | _doRequest); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/lib/panels/panel_post.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/dio_http_cache.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | import '../dio_helper.dart'; 5 | import 'helper.dart'; 6 | 7 | class PostPanel extends StatefulWidget { 8 | @override 9 | State createState() => _PostPanelState(); 10 | } 11 | 12 | class _PostPanelState extends State { 13 | Map _content = {"Hello ~": ""}; 14 | var _url = "article/query/0/json"; 15 | var _paramsController; 16 | var _urlController; 17 | 18 | @override 19 | void initState() { 20 | super.initState(); 21 | _paramsController = TextEditingController(text: "flutter"); 22 | _urlController = TextEditingController(text: _url); 23 | } 24 | 25 | void _doRequest() { 26 | var params = _paramsController.text; 27 | var paramsAvailable = null != params && params.length > 0; 28 | setState(() => _content = {"Requesting": params}); 29 | DioHelper.getDio() 30 | .post(_urlController.text, 31 | data: paramsAvailable ? {'k': params} : {}, 32 | options: buildCacheOptions(Duration(hours: 1), 33 | subKey: paramsAvailable ? "k=$params" : null, 34 | forceRefresh: false)) 35 | .then((response) => setState(() => 36 | _content = PanelHelper.getPrintContent(_url, params, response))) 37 | .catchError((onError, stackTrace) => setState( 38 | () => _content = PanelHelper.getPrintError(onError, stackTrace))); 39 | } 40 | 41 | @override 42 | Widget build(BuildContext context) { 43 | return PanelHelper.buildNormalPanel("Normal POST example", _urlController, 44 | _paramsController, _content, _doRequest); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/lib/tuple.dart: -------------------------------------------------------------------------------- 1 | /// Represents a 2-tuple or pair. 2 | class Pair { 3 | /// First item of the tuple 4 | T0? i0; 5 | 6 | /// Second item of the tuple 7 | T1? i1; 8 | 9 | /// Create a new tuple value with the specified items. 10 | Pair([this.i0, this.i1]); 11 | 12 | @override 13 | String toString() => '[$i0, $i1]'; 14 | 15 | @override 16 | bool operator ==(Object other) => 17 | other is Pair && other.i0 == i0 && other.i1 == i1; 18 | 19 | @override 20 | int get hashCode => hash([i0.hashCode, i1.hashCode]); 21 | } 22 | 23 | /// Represents a 3-tuple or pair. 24 | class Triple { 25 | T0? i0; 26 | T1? i1; 27 | T2? i2; 28 | 29 | Triple([this.i0, this.i1, this.i2]); 30 | 31 | @override 32 | String toString() => '[$i0, $i1, $i2]'; 33 | 34 | @override 35 | bool operator ==(Object other) => 36 | other is Triple && other.i0 == i0 && other.i1 == i1 && other.i2 == i2; 37 | 38 | @override 39 | int get hashCode => hash([i0.hashCode, i1.hashCode, i2.hashCode]); 40 | } 41 | 42 | int hash(Iterable values) { 43 | int hash = 0; 44 | 45 | /// combine 46 | for (int value in values) { 47 | hash = 0x1fffffff & (hash + value); 48 | hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); 49 | hash = hash ^ (hash >> 6); 50 | } 51 | 52 | /// finish 53 | hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); 54 | hash = hash ^ (hash >> 11); 55 | return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); 56 | } 57 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dio_http_cache_example 2 | description: A demo of the dio_http_cache package. 3 | version: 1.0.0+1 4 | publish_to: none 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | dio_http_cache: 13 | path: ../ 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | flutter: 20 | uses-material-design: true 21 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | //// This is a basic Flutter widget test. 2 | //// 3 | //// To perform an interaction with a widget in your test, use the WidgetTester 4 | //// utility that Flutter provides. For example, you can send tap and scroll 5 | //// gestures. You can also use WidgetTester to find child widgets in the widget 6 | //// tree, read text, and verify that the values of widget properties are correct. 7 | // 8 | //import 'package:flutter/material.dart'; 9 | //import 'package:flutter_test/flutter_test.dart'; 10 | // 11 | //import 'package:example/main.dart'; 12 | // 13 | //void main() { 14 | // testWidgets('Counter increments smoke test', (WidgetTester tester) async { 15 | // // Build our app and trigger a frame. 16 | // await tester.pumpWidget(MyApp()); 17 | // 18 | // // Verify that our counter starts at 0. 19 | // expect(find.text('0'), findsOneWidget); 20 | // expect(find.text('1'), findsNothing); 21 | // 22 | // // Tap the '+' icon and trigger a frame. 23 | // await tester.tap(find.byIcon(Icons.add)); 24 | // await tester.pump(); 25 | // 26 | // // Verify that our counter has incremented. 27 | // expect(find.text('0'), findsNothing); 28 | // expect(find.text('1'), findsOneWidget); 29 | // }); 30 | //} 31 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #ifndef GeneratedPluginRegistrant_h 6 | #define GeneratedPluginRegistrant_h 7 | 8 | #import 9 | 10 | @interface GeneratedPluginRegistrant : NSObject 11 | + (void)registerWithRegistry:(NSObject*)registry; 12 | @end 13 | 14 | #endif /* GeneratedPluginRegistrant_h */ 15 | -------------------------------------------------------------------------------- /ios/Runner/GeneratedPluginRegistrant.m: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | #import "GeneratedPluginRegistrant.h" 6 | 7 | @implementation GeneratedPluginRegistrant 8 | 9 | + (void)registerWithRegistry:(NSObject*)registry { 10 | } 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /lib/dio_http_cache.dart: -------------------------------------------------------------------------------- 1 | library dio_http_cache; 2 | 3 | export 'src/builder_dio.dart'; 4 | export 'src/core/config.dart'; 5 | export 'src/core/obj.dart'; 6 | export 'src/manager_dio.dart'; 7 | export 'src/store/store_impl.dart'; 8 | -------------------------------------------------------------------------------- /lib/src/builder_dio.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio/dio.dart'; 2 | import 'package:dio_http_cache/src/manager_dio.dart'; 3 | 4 | /// try to get maxAge and maxStale from response headers. 5 | /// local settings will always overview the value get from service. 6 | Options buildServiceCacheOptions( 7 | {Options? options, 8 | Duration? maxStale, 9 | String? primaryKey, 10 | String? subKey, 11 | bool? forceRefresh}) => 12 | buildConfigurableCacheOptions( 13 | options: options, 14 | maxStale: maxStale, 15 | primaryKey: primaryKey, 16 | subKey: subKey, 17 | forceRefresh: forceRefresh); 18 | 19 | /// build a normal cache options 20 | Options buildCacheOptions(Duration maxAge, 21 | {Duration? maxStale, 22 | String? primaryKey, 23 | String? subKey, 24 | Options? options, 25 | bool? forceRefresh}) => 26 | buildConfigurableCacheOptions( 27 | maxAge: maxAge, 28 | options: options, 29 | primaryKey: primaryKey, 30 | subKey: subKey, 31 | maxStale: maxStale, 32 | forceRefresh: forceRefresh); 33 | 34 | /// if null==maxAge, will try to get maxAge and maxStale from response headers. 35 | /// local settings will always overview the value get from service. 36 | Options buildConfigurableCacheOptions( 37 | {Options? options, 38 | Duration? maxAge, 39 | Duration? maxStale, 40 | String? primaryKey, 41 | String? subKey, 42 | bool? forceRefresh}) { 43 | if (null == options) { 44 | options = Options(); 45 | options.extra = {}; 46 | } else if (options.responseType == ResponseType.stream) { 47 | throw Exception("ResponseType.stream is not supported"); 48 | } else if (options.extra == null) { 49 | options.extra = {}; 50 | } 51 | options.extra!.addAll({DIO_CACHE_KEY_TRY_CACHE: true}); 52 | if (null != maxAge) { 53 | options.extra!.addAll({DIO_CACHE_KEY_MAX_AGE: maxAge}); 54 | } 55 | if (null != maxStale) { 56 | options.extra!.addAll({DIO_CACHE_KEY_MAX_STALE: maxStale}); 57 | } 58 | if (null != primaryKey) { 59 | options.extra!.addAll({DIO_CACHE_KEY_PRIMARY_KEY: primaryKey}); 60 | } 61 | if (null != subKey) { 62 | options.extra!.addAll({DIO_CACHE_KEY_SUB_KEY: subKey}); 63 | } 64 | if (null != forceRefresh) { 65 | options.extra!.addAll({DIO_CACHE_KEY_FORCE_REFRESH: forceRefresh}); 66 | } 67 | return options; 68 | } 69 | -------------------------------------------------------------------------------- /lib/src/core/config.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/src/store/store_impl.dart'; 2 | 3 | typedef Future> Encrypt(List str); 4 | typedef Future> Decrypt(List str); 5 | 6 | class CacheConfig { 7 | final Duration defaultMaxAge; 8 | final Duration? defaultMaxStale; 9 | final String? databasePath; 10 | final String databaseName; 11 | final String? baseUrl; 12 | final String defaultRequestMethod; 13 | 14 | final bool skipMemoryCache; 15 | final bool skipDiskCache; 16 | 17 | final int maxMemoryCacheCount; 18 | 19 | final Encrypt? encrypt; 20 | final Decrypt? decrypt; 21 | final ICacheStore? diskStore; 22 | 23 | CacheConfig( 24 | {this.defaultMaxAge = const Duration(days: 7), 25 | this.defaultMaxStale, 26 | this.defaultRequestMethod = "POST", 27 | this.databasePath, 28 | this.databaseName = "DioCache", 29 | this.baseUrl, 30 | this.skipDiskCache = false, 31 | this.skipMemoryCache = false, 32 | this.maxMemoryCacheCount = 100, 33 | this.encrypt, 34 | this.decrypt, 35 | this.diskStore}); 36 | } 37 | -------------------------------------------------------------------------------- /lib/src/core/manager.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:crypto/crypto.dart'; 4 | import 'package:dio_http_cache/src/core/config.dart'; 5 | import 'package:dio_http_cache/src/core/obj.dart'; 6 | import 'package:dio_http_cache/src/store/store_disk.dart'; 7 | import 'package:dio_http_cache/src/store/store_impl.dart'; 8 | import 'package:dio_http_cache/src/store/store_memory.dart'; 9 | import 'package:sqflite/utils/utils.dart'; 10 | 11 | class CacheManager { 12 | CacheConfig _config; 13 | ICacheStore? _diskCacheStore; 14 | ICacheStore? _memoryCacheStore; 15 | late Utf8Encoder _utf8encoder; 16 | 17 | CacheManager(this._config) { 18 | _utf8encoder = const Utf8Encoder(); 19 | if (!_config.skipDiskCache) 20 | _diskCacheStore = _config.diskStore ?? 21 | DiskCacheStore(_config.databasePath, _config.databaseName, 22 | _config.encrypt, _config.decrypt); 23 | if (!_config.skipMemoryCache) 24 | _memoryCacheStore = MemoryCacheStore(_config.maxMemoryCacheCount); 25 | } 26 | 27 | Future _pullFromCache(String key, {String? subKey}) async { 28 | key = _convertMd5(key); 29 | if (null != subKey) subKey = _convertMd5(subKey); 30 | var obj = await _memoryCacheStore?.getCacheObj(key, subKey: subKey); 31 | if (null == obj) { 32 | obj = await _diskCacheStore?.getCacheObj(key, subKey: subKey); 33 | if (null != obj) await _memoryCacheStore?.setCacheObj(obj); 34 | } 35 | if (null != obj) { 36 | var now = DateTime.now().millisecondsSinceEpoch; 37 | if (null != obj.maxStaleDate && obj.maxStaleDate! > 0) { 38 | //if maxStaleDate exist, Remove it if maxStaleDate expired. 39 | if (obj.maxStaleDate! < now) { 40 | await delete(key, subKey: subKey); 41 | return null; 42 | } 43 | } else { 44 | //if maxStaleDate NOT exist, Remove it if maxAgeDate expired. 45 | if (obj.maxAgeDate! < now) { 46 | await delete(key, subKey: subKey); 47 | return null; 48 | } 49 | } 50 | } 51 | return obj; 52 | } 53 | 54 | Future pullFromCacheBeforeMaxAge(String key, 55 | {String? subKey}) async { 56 | var obj = await _pullFromCache(key, subKey: subKey); 57 | if (null != obj && 58 | null != obj.maxAgeDate && 59 | obj.maxAgeDate! < DateTime.now().millisecondsSinceEpoch) { 60 | return null; 61 | } 62 | return obj; 63 | } 64 | 65 | Future pullFromCacheBeforeMaxStale(String key, 66 | {String? subKey}) async { 67 | return await _pullFromCache(key, subKey: subKey); 68 | } 69 | 70 | Future pushToCache(CacheObj obj) { 71 | obj.key = _convertMd5(obj.key); 72 | if (null != obj.subKey) obj.subKey = _convertMd5(obj.subKey!); 73 | 74 | if (null == obj.maxAgeDate || obj.maxAgeDate! <= 0) { 75 | obj.maxAge = _config.defaultMaxAge; 76 | } 77 | if (null == obj.maxAgeDate || obj.maxAgeDate! <= 0) { 78 | return Future.value(false); 79 | } 80 | if ((null == obj.maxStaleDate || obj.maxStaleDate! <= 0) && 81 | null != _config.defaultMaxStale) { 82 | obj.maxStale = _config.defaultMaxStale; 83 | } 84 | 85 | return _getCacheFutureResult(_memoryCacheStore, _diskCacheStore, 86 | _memoryCacheStore?.setCacheObj(obj), _diskCacheStore?.setCacheObj(obj)); 87 | } 88 | 89 | Future delete(String key, {String? subKey}) { 90 | key = _convertMd5(key); 91 | if (null != subKey) subKey = _convertMd5(subKey); 92 | 93 | return _getCacheFutureResult( 94 | _memoryCacheStore, 95 | _diskCacheStore, 96 | _memoryCacheStore?.delete(key, subKey: subKey), 97 | _diskCacheStore?.delete(key, subKey: subKey)); 98 | } 99 | 100 | Future clearExpired() { 101 | return _getCacheFutureResult(_memoryCacheStore, _diskCacheStore, 102 | _memoryCacheStore?.clearExpired(), _diskCacheStore?.clearExpired()); 103 | } 104 | 105 | Future clearAll() { 106 | return _getCacheFutureResult(_memoryCacheStore, _diskCacheStore, 107 | _memoryCacheStore?.clearAll(), _diskCacheStore?.clearAll()); 108 | } 109 | 110 | String _convertMd5(String str) { 111 | return hex(md5.convert(_utf8encoder.convert(str)).bytes); 112 | } 113 | 114 | Future _getCacheFutureResult( 115 | ICacheStore? memoryCacheStore, 116 | ICacheStore? diskCacheStore, 117 | Future? memoryCacheFuture, 118 | Future? diskCacheFuture) async { 119 | var result1 = 120 | (null == memoryCacheStore) ? true : (await memoryCacheFuture!); 121 | var result2 = (null == diskCacheStore) ? true : (await diskCacheFuture!); 122 | return result1 && result2; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /lib/src/core/obj.dart: -------------------------------------------------------------------------------- 1 | import 'package:json_annotation/json_annotation.dart'; 2 | 3 | part 'obj.g.dart'; 4 | 5 | @JsonSerializable() 6 | class CacheObj { 7 | String key; 8 | String? subKey; 9 | @JsonKey(name: "max_age_date") 10 | int? maxAgeDate; 11 | @JsonKey(name: "max_stale_date") 12 | int? maxStaleDate; 13 | List? content; 14 | int? statusCode; 15 | List? headers; 16 | 17 | CacheObj._( 18 | this.key, this.subKey, this.content, this.statusCode, this.headers); 19 | 20 | factory CacheObj(String key, List content, 21 | {String? subKey = "", 22 | Duration? maxAge, 23 | Duration? maxStale, 24 | int? statusCode = 200, 25 | List? headers}) { 26 | return CacheObj._(key, subKey, content, statusCode, headers) 27 | ..maxAge = maxAge 28 | ..maxStale = maxStale; 29 | } 30 | 31 | set maxAge(Duration? duration) { 32 | if (null != duration) this.maxAgeDate = _convertDuration(duration); 33 | } 34 | 35 | set maxStale(Duration? duration) { 36 | if (null != duration) this.maxStaleDate = _convertDuration(duration); 37 | } 38 | 39 | _convertDuration(Duration duration) => 40 | DateTime.now().add(duration).millisecondsSinceEpoch; 41 | 42 | factory CacheObj.fromJson(Map json) => 43 | _$CacheObjFromJson(json); 44 | 45 | toJson() => _$CacheObjToJson(this); 46 | } 47 | -------------------------------------------------------------------------------- /lib/src/core/obj.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'obj.dart'; 4 | 5 | // ************************************************************************** 6 | // JsonSerializableGenerator 7 | // ************************************************************************** 8 | 9 | CacheObj _$CacheObjFromJson(Map json) { 10 | return CacheObj( 11 | json['key'] as String, 12 | (json['content'] as List).map((e) => e as int).toList(), 13 | subKey: json['subKey'] as String?, 14 | statusCode: json['statusCode'] as int?, 15 | headers: (json['headers'] as List?)?.map((e) => e as int).toList(), 16 | ) 17 | ..maxAgeDate = json['max_age_date'] as int? 18 | ..maxStaleDate = json['max_stale_date'] as int?; 19 | } 20 | 21 | Map _$CacheObjToJson(CacheObj instance) => { 22 | 'key': instance.key, 23 | 'subKey': instance.subKey, 24 | 'max_age_date': instance.maxAgeDate, 25 | 'max_stale_date': instance.maxStaleDate, 26 | 'content': instance.content, 27 | 'statusCode': instance.statusCode, 28 | 'headers': instance.headers, 29 | }; 30 | -------------------------------------------------------------------------------- /lib/src/manager_dio.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'dart:io'; 3 | 4 | import 'package:dio/dio.dart'; 5 | import 'package:dio_http_cache/src/core/config.dart'; 6 | import 'package:dio_http_cache/src/core/manager.dart'; 7 | import 'package:dio_http_cache/src/core/obj.dart'; 8 | 9 | const DIO_CACHE_KEY_TRY_CACHE = "dio_cache_try_cache"; 10 | const DIO_CACHE_KEY_MAX_AGE = "dio_cache_max_age"; 11 | const DIO_CACHE_KEY_MAX_STALE = "dio_cache_max_stale"; 12 | const DIO_CACHE_KEY_PRIMARY_KEY = "dio_cache_primary_key"; 13 | const DIO_CACHE_KEY_SUB_KEY = "dio_cache_sub_key"; 14 | const DIO_CACHE_KEY_FORCE_REFRESH = "dio_cache_force_refresh"; 15 | const DIO_CACHE_HEADER_KEY_DATA_SOURCE = "dio_cache_header_key_data_source"; 16 | 17 | typedef _ParseHeadCallback = void Function( 18 | Duration? _maxAge, Duration? _maxStale); 19 | 20 | class DioCacheManager { 21 | late CacheManager _manager; 22 | InterceptorsWrapper? _interceptor; 23 | late String? _baseUrl; 24 | late String _defaultRequestMethod; 25 | 26 | DioCacheManager(CacheConfig config) { 27 | _manager = CacheManager(config); 28 | _baseUrl = config.baseUrl; 29 | _defaultRequestMethod = config.defaultRequestMethod; 30 | } 31 | 32 | /// interceptor for http cache. 33 | get interceptor { 34 | if (null == _interceptor) { 35 | _interceptor = InterceptorsWrapper( 36 | onRequest: _onRequest, onResponse: _onResponse, onError: _onError); 37 | } 38 | return _interceptor; 39 | } 40 | 41 | _onRequest(RequestOptions options, RequestInterceptorHandler handler) async { 42 | if ((options.extra[DIO_CACHE_KEY_TRY_CACHE] ?? false) != true) { 43 | return handler.next(options); 44 | } 45 | if (true == options.extra[DIO_CACHE_KEY_FORCE_REFRESH]) { 46 | return handler.next(options); 47 | } 48 | var responseDataFromCache = await _pullFromCacheBeforeMaxAge(options); 49 | if (null != responseDataFromCache) { 50 | return handler.resolve( 51 | _buildResponse( 52 | responseDataFromCache, responseDataFromCache.statusCode, options), 53 | true); 54 | } 55 | return handler.next(options); 56 | } 57 | 58 | _onResponse(Response response, ResponseInterceptorHandler handler) async { 59 | if ((response.requestOptions.extra[DIO_CACHE_KEY_TRY_CACHE] ?? false) == 60 | true && 61 | response.statusCode != null && 62 | response.statusCode! >= 200 && 63 | response.statusCode! < 300) { 64 | await _pushToCache(response); 65 | } 66 | return handler.next(response); 67 | } 68 | 69 | _onError(DioError e, ErrorInterceptorHandler handler) async { 70 | if ((e.requestOptions.extra[DIO_CACHE_KEY_TRY_CACHE] ?? false) == true) { 71 | var responseDataFromCache = 72 | await _pullFromCacheBeforeMaxStale(e.requestOptions); 73 | if (null != responseDataFromCache) { 74 | var response = _buildResponse(responseDataFromCache, 75 | responseDataFromCache.statusCode, e.requestOptions); 76 | 77 | return handler.resolve(response); 78 | } 79 | } 80 | return handler.next(e); 81 | } 82 | 83 | Response _buildResponse( 84 | CacheObj obj, int? statusCode, RequestOptions options) { 85 | Headers? headers; 86 | if (null != obj.headers) { 87 | headers = Headers.fromMap((Map>.from( 88 | jsonDecode(utf8.decode(obj.headers!)))) 89 | .map((k, v) => MapEntry(k, List.from(v)))); 90 | } 91 | if (null == headers) { 92 | headers = Headers(); 93 | options.headers.forEach((k, v) => headers!.add(k, v ?? "")); 94 | } 95 | // add flag 96 | headers.add(DIO_CACHE_HEADER_KEY_DATA_SOURCE, "from_cache"); 97 | dynamic data = obj.content; 98 | if (options.responseType != ResponseType.bytes) { 99 | data = jsonDecode(utf8.decode(data)); 100 | } 101 | return Response( 102 | data: data, 103 | headers: headers, 104 | requestOptions: options.copyWith( 105 | extra: options.extra..remove(DIO_CACHE_KEY_TRY_CACHE)), 106 | statusCode: statusCode ?? 200); 107 | } 108 | 109 | Future _pullFromCacheBeforeMaxAge(RequestOptions options) { 110 | return _manager.pullFromCacheBeforeMaxAge( 111 | _getPrimaryKeyFromOptions(options), 112 | subKey: _getSubKeyFromOptions(options)); 113 | } 114 | 115 | Future _pullFromCacheBeforeMaxStale(RequestOptions options) { 116 | return _manager.pullFromCacheBeforeMaxStale( 117 | _getPrimaryKeyFromOptions(options), 118 | subKey: _getSubKeyFromOptions(options)); 119 | } 120 | 121 | Future _pushToCache(Response response) { 122 | RequestOptions options = response.requestOptions; 123 | Duration? maxAge = options.extra[DIO_CACHE_KEY_MAX_AGE]; 124 | Duration? maxStale = options.extra[DIO_CACHE_KEY_MAX_STALE]; 125 | if (null == maxAge) { 126 | _tryParseHead(response, maxStale, (_maxAge, _maxStale) { 127 | maxAge = _maxAge; 128 | maxStale = _maxStale; 129 | }); 130 | } 131 | List? data; 132 | if (options.responseType == ResponseType.bytes) { 133 | data = response.data; 134 | } else { 135 | data = utf8.encode(jsonEncode(response.data)); 136 | } 137 | var obj = CacheObj(_getPrimaryKeyFromOptions(options), data!, 138 | subKey: _getSubKeyFromOptions(options), 139 | maxAge: maxAge, 140 | maxStale: maxStale, 141 | statusCode: response.statusCode, 142 | headers: utf8.encode(jsonEncode(response.headers.map))); 143 | return _manager.pushToCache(obj); 144 | } 145 | 146 | // try to get maxAge and maxStale from http headers 147 | void _tryParseHead( 148 | Response response, Duration? maxStale, _ParseHeadCallback callback) { 149 | Duration? _maxAge; 150 | var cacheControl = response.headers.value(HttpHeaders.cacheControlHeader); 151 | if (null != cacheControl) { 152 | // try to get maxAge and maxStale from cacheControl 153 | Map parameters; 154 | try { 155 | parameters = HeaderValue.parse( 156 | "${HttpHeaders.cacheControlHeader}: $cacheControl", 157 | parameterSeparator: ",", 158 | valueSeparator: "=") 159 | .parameters; 160 | _maxAge = _tryGetDurationFromMap(parameters, "s-maxage"); 161 | if (null == _maxAge) { 162 | _maxAge = _tryGetDurationFromMap(parameters, "max-age"); 163 | } 164 | // if maxStale has valued, don't get max-stale anymore. 165 | if (null == maxStale) { 166 | maxStale = _tryGetDurationFromMap(parameters, "max-stale"); 167 | } 168 | } catch (e) { 169 | print(e); 170 | } 171 | } else { 172 | // try to get maxAge from expires 173 | var expires = response.headers.value(HttpHeaders.expiresHeader); 174 | if (null != expires && expires.length > 4) { 175 | DateTime? endTime; 176 | try { 177 | endTime = HttpDate.parse(expires).toLocal(); 178 | } catch (e) { 179 | print(e); 180 | } 181 | if (null != endTime && endTime.compareTo(DateTime.now()) >= 0) { 182 | _maxAge = endTime.difference(DateTime.now()); 183 | } 184 | } 185 | } 186 | callback(_maxAge, maxStale); 187 | } 188 | 189 | Duration? _tryGetDurationFromMap( 190 | Map parameters, String key) { 191 | if (parameters.containsKey(key)) { 192 | var value = int.tryParse(parameters[key]!); 193 | if (null != value && value >= 0) { 194 | return Duration(seconds: value); 195 | } 196 | } 197 | return null; 198 | } 199 | 200 | String _getPrimaryKeyFromOptions(RequestOptions options) { 201 | var primaryKey = options.extra.containsKey(DIO_CACHE_KEY_PRIMARY_KEY) 202 | ? options.extra[DIO_CACHE_KEY_PRIMARY_KEY] 203 | : _getPrimaryKeyFromUri(options.uri); 204 | 205 | return "${_getRequestMethod(options.method)}-$primaryKey"; 206 | } 207 | 208 | String _getRequestMethod(String? requestMethod) { 209 | if (null != requestMethod && requestMethod.length > 0) { 210 | return requestMethod.toUpperCase(); 211 | } 212 | return _defaultRequestMethod.toUpperCase(); 213 | } 214 | 215 | String? _getSubKeyFromOptions(RequestOptions options) { 216 | return options.extra.containsKey(DIO_CACHE_KEY_SUB_KEY) 217 | ? options.extra[DIO_CACHE_KEY_SUB_KEY] 218 | : _getSubKeyFromUri(options.uri, data: options.data); 219 | } 220 | 221 | String _getPrimaryKeyFromUri(Uri uri) => "${uri.host}${uri.path}"; 222 | 223 | String _getSubKeyFromUri(Uri uri, {dynamic data}) => 224 | "${data?.toString()}_${uri.query}"; 225 | 226 | /// delete local cache by primaryKey and optional subKey 227 | Future delete(String primaryKey, 228 | {String? requestMethod, String? subKey}) => 229 | _manager.delete("${_getRequestMethod(requestMethod)}-$primaryKey", 230 | subKey: subKey); 231 | 232 | /// no matter what subKey is, delete local cache if primary matched. 233 | Future deleteByPrimaryKeyWithUri(Uri uri, {String? requestMethod}) => 234 | delete(_getPrimaryKeyFromUri(uri), requestMethod: requestMethod); 235 | 236 | Future deleteByPrimaryKey(String path, {String? requestMethod}) => 237 | deleteByPrimaryKeyWithUri(_getUriByPath(_baseUrl, path), 238 | requestMethod: requestMethod); 239 | 240 | /// delete local cache when both primaryKey and subKey matched. 241 | Future deleteByPrimaryKeyAndSubKeyWithUri(Uri uri, 242 | {String? requestMethod, String? subKey, dynamic data}) => 243 | delete(_getPrimaryKeyFromUri(uri), 244 | requestMethod: requestMethod, 245 | subKey: subKey ?? _getSubKeyFromUri(uri, data: data)); 246 | 247 | Future deleteByPrimaryKeyAndSubKey(String path, 248 | {String? requestMethod, 249 | Map? queryParameters, 250 | String? subKey, 251 | dynamic data}) => 252 | deleteByPrimaryKeyAndSubKeyWithUri( 253 | _getUriByPath(_baseUrl, path, 254 | data: data, queryParameters: queryParameters), 255 | requestMethod: requestMethod, 256 | subKey: subKey, 257 | data: data); 258 | 259 | /// clear all expired cache. 260 | Future clearExpired() => _manager.clearExpired(); 261 | 262 | /// empty local cache. 263 | Future clearAll() => _manager.clearAll(); 264 | 265 | Uri _getUriByPath(String? baseUrl, String path, 266 | {dynamic data, Map? queryParameters}) { 267 | if (!path.startsWith(RegExp(r"https?:"))) { 268 | assert(baseUrl != null && baseUrl.length > 0); 269 | } 270 | return RequestOptions( 271 | baseUrl: baseUrl, 272 | path: path, 273 | data: data, 274 | queryParameters: queryParameters) 275 | .uri; 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /lib/src/store/store_disk.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:dio_http_cache/src/core/config.dart'; 4 | import 'package:dio_http_cache/src/core/obj.dart'; 5 | import 'package:dio_http_cache/src/store/store_impl.dart'; 6 | import 'package:path/path.dart'; 7 | import 'package:sqflite/sqflite.dart'; 8 | 9 | class DiskCacheStore extends ICacheStore { 10 | final String? _databasePath; 11 | final String _databaseName; 12 | final Encrypt? _encrypt; 13 | final Decrypt? _decrypt; 14 | final String _tableCacheObject = "cache_dio"; 15 | final String _columnKey = "key"; 16 | final String _columnSubKey = "subKey"; 17 | final String _columnMaxAgeDate = "max_age_date"; 18 | final String _columnMaxStaleDate = "max_stale_date"; 19 | final String _columnContent = "content"; 20 | final String _columnStatusCode = "statusCode"; 21 | final String _columnHeaders = "headers"; 22 | 23 | Database? _db; 24 | static const int _curDBVersion = 3; 25 | 26 | Future get _database async { 27 | if (null == _db) { 28 | var path = _databasePath; 29 | if (null == path || path.length <= 0) { 30 | path = await getDatabasesPath(); 31 | } 32 | await Directory(path).create(recursive: true); 33 | path = join(path, "$_databaseName.db"); 34 | _db = await openDatabase(path, 35 | version: _curDBVersion, 36 | onConfigure: (db) => _tryFixDbNoVersionBug(db, path!), 37 | onCreate: _onCreate, 38 | onUpgrade: _onUpgrade); 39 | await _clearExpired(_db); 40 | } 41 | return _db; 42 | } 43 | 44 | _tryFixDbNoVersionBug(Database db, String dbPath) async { 45 | if ((await db.getVersion()) == 0) { 46 | var isTableUserLogExist = await db 47 | .rawQuery( 48 | "select DISTINCT tbl_name from sqlite_master where tbl_name = '$_tableCacheObject'") 49 | .then((v) => (v.length > 0)); 50 | if (isTableUserLogExist) { 51 | await db.setVersion(1); 52 | } 53 | } 54 | } 55 | 56 | _getCreateTableSql() => ''' 57 | CREATE TABLE IF NOT EXISTS $_tableCacheObject ( 58 | $_columnKey text, 59 | $_columnSubKey text, 60 | $_columnMaxAgeDate integer, 61 | $_columnMaxStaleDate integer, 62 | $_columnContent BLOB, 63 | $_columnStatusCode integer, 64 | $_columnHeaders BLOB, 65 | PRIMARY KEY ($_columnKey, $_columnSubKey) 66 | ) 67 | '''; 68 | 69 | _onCreate(Database db, int version) async { 70 | await db.execute(_getCreateTableSql()); 71 | } 72 | 73 | List?> _dbUpgradeList() => [ 74 | // 0 -> 1 75 | null, 76 | // 1 -> 2 77 | [ 78 | "ALTER TABLE $_tableCacheObject ADD COLUMN $_columnStatusCode integer;" 79 | ], 80 | // 2 -> 3 : Change $_columnContent from text to BLOB 81 | ["DROP TABLE IF EXISTS $_tableCacheObject;", _getCreateTableSql()], 82 | ]; 83 | 84 | _onUpgrade(Database db, int oldVersion, int newVersion) async { 85 | var mergeLength = _dbUpgradeList().length; 86 | if (oldVersion < 0 || oldVersion >= mergeLength) return; 87 | await db.transaction((txn) async { 88 | var tempVersion = oldVersion; 89 | while (tempVersion < newVersion) { 90 | if (tempVersion < mergeLength) { 91 | var sqlList = _dbUpgradeList()[tempVersion]; 92 | if (null != sqlList && sqlList.length > 0) { 93 | sqlList.forEach((sql) async { 94 | sql = sql.trim(); 95 | if (sql.length > 0) { 96 | await txn.execute(sql); 97 | } 98 | }); 99 | } 100 | } 101 | tempVersion++; 102 | } 103 | }); 104 | } 105 | 106 | DiskCacheStore( 107 | this._databasePath, this._databaseName, this._encrypt, this._decrypt) 108 | : super(); 109 | 110 | @override 111 | Future getCacheObj(String key, {String? subKey}) async { 112 | var db = await _database; 113 | if (null == db) return null; 114 | var where = "$_columnKey=\"$key\""; 115 | if (null != subKey) where += " and $_columnSubKey=\"$subKey\""; 116 | var resultList = await db.query(_tableCacheObject, where: where); 117 | if (resultList.isEmpty) return null; 118 | return await _decryptCacheObj(CacheObj.fromJson(resultList[0])); 119 | } 120 | 121 | @override 122 | Future setCacheObj(CacheObj obj) async { 123 | var db = await _database; 124 | if (null == db) return false; 125 | var content = await _encryptCacheStr(obj.content); 126 | var headers = await _encryptCacheStr(obj.headers); 127 | await db.insert( 128 | _tableCacheObject, 129 | { 130 | _columnKey: obj.key, 131 | _columnSubKey: obj.subKey ?? "", 132 | _columnMaxAgeDate: obj.maxAgeDate ?? 0, 133 | _columnMaxStaleDate: obj.maxStaleDate ?? 0, 134 | _columnContent: content, 135 | _columnStatusCode: obj.statusCode, 136 | _columnHeaders: headers 137 | }, 138 | conflictAlgorithm: ConflictAlgorithm.replace); 139 | return true; 140 | } 141 | 142 | @override 143 | Future delete(String key, {String? subKey}) async { 144 | var db = await _database; 145 | if (null == db) return false; 146 | var where = "$_columnKey=\"$key\""; 147 | if (null != subKey) where += " and $_columnSubKey=\"$subKey\""; 148 | return 0 != await db.delete(_tableCacheObject, where: where); 149 | } 150 | 151 | @override 152 | Future clearExpired() async { 153 | var db = await _database; 154 | return _clearExpired(db); 155 | } 156 | 157 | Future _clearExpired(Database? db) async { 158 | if (null == db) return false; 159 | var now = DateTime.now().millisecondsSinceEpoch; 160 | var where1 = "$_columnMaxStaleDate > 0 and $_columnMaxStaleDate < $now"; 161 | var where2 = "$_columnMaxStaleDate <= 0 and $_columnMaxAgeDate < $now"; 162 | return 0 != 163 | await db.delete(_tableCacheObject, where: "( $where1 ) or ( $where2 )"); 164 | } 165 | 166 | @override 167 | Future clearAll() async { 168 | var db = await _database; 169 | if (null == db) return false; 170 | return 0 != await db.delete(_tableCacheObject); 171 | } 172 | 173 | Future _decryptCacheObj(CacheObj obj) async { 174 | obj.content = await _decryptCacheStr(obj.content); 175 | obj.headers = await _decryptCacheStr(obj.headers); 176 | return obj; 177 | } 178 | 179 | Future?> _decryptCacheStr(List? bytes) async { 180 | if (null == bytes) return null; 181 | if (null != _decrypt) { 182 | bytes = await _decrypt!(bytes); 183 | } 184 | return bytes; 185 | } 186 | 187 | Future?> _encryptCacheStr(List? bytes) async { 188 | if (null == bytes) return null; 189 | if (null != _encrypt) { 190 | bytes = await _encrypt!(bytes); 191 | } 192 | return bytes; 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /lib/src/store/store_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:dio_http_cache/src/core/obj.dart'; 2 | 3 | abstract class ICacheStore { 4 | ICacheStore(); 5 | 6 | Future getCacheObj(String key, {String? subKey}); 7 | 8 | Future setCacheObj(CacheObj obj); 9 | 10 | Future delete(String key, {String? subKey}); 11 | 12 | Future clearExpired(); 13 | 14 | Future clearAll(); 15 | } 16 | -------------------------------------------------------------------------------- /lib/src/store/store_memory.dart: -------------------------------------------------------------------------------- 1 | import 'dart:collection'; 2 | 3 | import 'package:dio_http_cache/src/core/obj.dart'; 4 | import 'package:dio_http_cache/src/store/store_impl.dart'; 5 | import 'package:quiver/cache.dart'; 6 | 7 | class MemoryCacheStore extends ICacheStore { 8 | final int _maxMemoryCacheCount; 9 | late MapCache _mapCache; 10 | late Map> _keys; 11 | 12 | MemoryCacheStore(this._maxMemoryCacheCount) : super() { 13 | _initMap(); 14 | } 15 | 16 | _initMap() { 17 | _mapCache = MapCache.lru(maximumSize: _maxMemoryCacheCount); 18 | _keys = HashMap(); 19 | } 20 | 21 | @override 22 | Future getCacheObj(String key, {String? subKey = ""}) async => 23 | _mapCache.get("${key}_$subKey"); 24 | 25 | @override 26 | Future setCacheObj(CacheObj obj) async { 27 | _mapCache.set("${obj.key}_${obj.subKey}", obj); 28 | _storeKey(obj); 29 | return true; 30 | } 31 | 32 | @override 33 | Future delete(String key, {String? subKey}) async { 34 | _removeKey(key, subKey: subKey).forEach((key) => _mapCache.invalidate(key)); 35 | return true; 36 | } 37 | 38 | @override 39 | Future clearExpired() { 40 | return clearAll(); 41 | } 42 | 43 | @override 44 | Future clearAll() async { 45 | _initMap(); 46 | return true; 47 | } 48 | 49 | _storeKey(CacheObj obj) { 50 | List? subKeyList = _keys[obj.key]; 51 | if (null == subKeyList) subKeyList = []; 52 | subKeyList.add(obj.subKey ?? ""); 53 | _keys[obj.key] = subKeyList; 54 | } 55 | 56 | List _removeKey(String key, {String? subKey}) { 57 | List? subKeyList = _keys[key]; 58 | if (null == subKeyList || subKeyList.length <= 0) return []; 59 | if (null == subKey) { 60 | _keys.remove(key); 61 | return subKeyList.map((sKey) => "${key}_$sKey").toList(); 62 | } else { 63 | subKeyList.remove(subKey); 64 | _keys[key] = subKeyList; 65 | return ["${key}_$subKey"]; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dio_http_cache 2 | description: http cache lib for Flutter dio like RxCache.It use sqflite as disk cache,and google/quiver-dart/LRU strategy as memory cache. 3 | version: 0.3.0 4 | authors: 5 | - Hurshi 6 | - Eric Kok 7 | homepage: https://github.com/hurshi/dio-http-cache 8 | 9 | environment: 10 | sdk: '>=2.12.0 <3.0.0' 11 | 12 | dependencies: 13 | crypto: ^3.0.1 14 | dio: ^4.0.0 15 | flutter: 16 | sdk: flutter 17 | json_serializable: ^4.1.4 18 | json_annotation: ^4.0.1 19 | path: ^1.8.0 20 | quiver: ^3.0.1 21 | sqflite: ^2.0.0+3 22 | 23 | dev_dependencies: 24 | build_runner: ^2.0.6 25 | flutter_test: 26 | sdk: flutter 27 | 28 | flutter: 29 | module: 30 | androidX: true 31 | -------------------------------------------------------------------------------- /shells/cleanpub.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 1. 将会清空本地的依赖缓存, 4 | # 2. 重新从网上获取的依赖 5 | 6 | cd .. 7 | flutter clean 8 | rm pubspec.lock 9 | rm -rf build/ 10 | rm -rf .android/ 11 | rm -rf .ios/ 12 | rm -rf .idea/ 13 | find . -name '*.iml' -type f -delete 14 | rm .flutter-plugins 15 | rm .packages 16 | rm -rf ~/.pub-cache/ 17 | flutter pub upgrade 18 | flutter create --org io.github.hurshi . 19 | 20 | 21 | -------------------------------------------------------------------------------- /shells/json.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | flutter packages pub run build_runner build --define "json_serializable=any_map=true" --delete-conflicting-outputs 3 | -------------------------------------------------------------------------------- /test/dio_cache_test.dart: -------------------------------------------------------------------------------- 1 | //import 'package:flutter_test/flutter_test.dart'; 2 | // 3 | //import 'package:dio_cache/dio_http_cache.dart'; 4 | // 5 | //void main() { 6 | // test('adds one to input values', () { 7 | // final calculator = Calculator(); 8 | // expect(calculator.addOne(2), 3); 9 | // expect(calculator.addOne(-7), -6); 10 | // expect(calculator.addOne(0), 1); 11 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError); 12 | // }); 13 | //} 14 | --------------------------------------------------------------------------------