├── .gitignore ├── .metadata ├── .vscode └── launch.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── dart.yml ├── example ├── example.dart ├── pubspec.lock └── pubspec.yaml ├── lib ├── Cache.dart ├── Parse.dart ├── flutter_cache.dart └── shouldCache.dart ├── pubspec.lock ├── pubspec.yaml └── test └── flutter_cache_test.dart /.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 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Flutter.podspec 62 | **/ios/Flutter/Generated.xcconfig 63 | **/ios/Flutter/app.flx 64 | **/ios/Flutter/app.zip 65 | **/ios/Flutter/flutter_assets/ 66 | **/ios/Flutter/flutter_export_environment.sh 67 | **/ios/ServiceDefinitions.json 68 | **/ios/Runner/GeneratedPluginRegistrant.* 69 | 70 | # Exceptions to above rules. 71 | !**/ios/**/default.mode1v3 72 | !**/ios/**/default.mode2v3 73 | !**/ios/**/default.pbxuser 74 | !**/ios/**/default.perspectivev3 75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 76 | -------------------------------------------------------------------------------- /.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: 1118d24794088119bbd649462e320d246533001e 8 | channel: master 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Flutter", 9 | "request": "launch", 10 | "type": "dart" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## [0.0.1] 2 | 3 | * Initial Release Version 0 4 | 5 | ## [0.0.2] 6 | 7 | * Added support for anonymous function 8 | * Update Readme with detailed Explaination 9 | 10 | ## [0.0.3] 11 | 12 | * fix readme, showed wrong example. 13 | 14 | ## [0.0.4] 15 | 16 | * added example on /example 17 | 18 | ## [0.0.5] 19 | 20 | * clear cache trace on cache destroy. 21 | * added test for cache destroy. 22 | 23 | ## [0.0.6] 24 | 25 | * await on anonymous function execute. 26 | * added ShouldCache type 27 | 28 | ## [0.0.7] 29 | 30 | * Can set optional value if key not exists in `load()` 31 | * Import package method instead of Cache class 32 | * Upgrade to dependencies to Flutter 2 33 | * Update Test from flutter_test to test package 34 | 35 | ## [0.1.0] 36 | 37 | * Migrated to null safety 38 | * Update Shared Preference and Flutter constraint -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) SekolahCode 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flutter_cache 2 | [![Dependencies Status](https://img.shields.io/librariesio/github/sekolahcode/flutter_cache)](https://libraries.io/github/SekolahCode/flutter_cache) 3 | [![file size](https://img.shields.io/github/size/sekolahcode/flutter_cache/lib/flutter_cache.dart)](https://img.shields.io/github/size/sekolahcode/flutter_cache/lib/flutter_cache.dart) 4 | [![GitHub Issues](https://img.shields.io/github/issues/sekolahcode/flutter_cache)](https://github.com/SekolahCode/flutter_cache/issues) 5 | [![follows on twitter](https://img.shields.io/twitter/follow/sekolahcode?label=Follow&style=social)](https://twitter.com/sekolahcode) 6 | 7 | A simple cache package for flutter. This package is a wrapper for shared preference and makes working with shared preference easier. Once it has been installed, you can do these things. 8 | 9 | ```dart 10 | import 'package:flutter_cache/flutter_cache.dart' as cache; 11 | 12 | // create new cache. 13 | cache.remember('key', 'data'); 14 | cache.write('key', 'data'); 15 | 16 | // add Cache lifetime on create 17 | cache.remember('key', 'data', 120); 18 | cache.write('key', 'data', 120); 19 | 20 | // load Cache by key 21 | cache.load('key'); // This will load the cache data. 22 | 23 | // return `defaultValue` if key not exists 24 | cache.load('key', 'defaultValue') 25 | 26 | // destroy single cache by key 27 | cache.destroy('key'); 28 | 29 | // destroy all cache 30 | cache.clear(); 31 | 32 | ``` 33 | ## Getting Started 34 | 35 | ### Installation 36 | 37 | First include the package dependency in your project's `pubspec.yaml` file 38 | 39 | ```yaml 40 | dependencies: 41 | flutter_cache: ^0.1.0 42 | ``` 43 | 44 | You can install the package via pub get: 45 | 46 | ```bash 47 | flutter pub get 48 | ``` 49 | 50 | Then you can import it in your dart code like so 51 | 52 | ```dart 53 | import 'package:flutter_cache/flutter_cache.dart' as cache; 54 | ``` 55 | 56 | ### What Can this Package Do ? 57 | 58 | 1. Cache `String`, `Map`, `List` and `List` for forever or for a limited time. 59 | 2. Load the cache you've cached. 60 | 3. Clear All Cache. 61 | 4. Clear Single Cache. 62 | 63 | ### Usage 64 | 65 | #### Cache Fetch Data from API 66 | 67 | If data already exist, then it will use the data in the cache. If it's not, It will fetch the data. You can also set Cache lifetime so your app would fetch again everytime the Cache dies. 68 | 69 | ```dart 70 | import 'dart:convert'; 71 | import 'package:http/http.dart' as http; 72 | import 'package:flutter_cache/flutter_cache.dart' as cache; 73 | 74 | var employees = await cache.remember('employees', () async { 75 | var response = await http.get('http://dummy.restapiexample.com/api/v1/employees'); 76 | return jsonDecode(response.body)['data'].cast(); 77 | }, 120); // cache for 2 mins 78 | 79 | // or 80 | 81 | // cache for 2 mins 82 | var employees = await cache.remember( 83 | 'servers', 84 | () async => jsonDecode( 85 | (await http.get( 'http://dummy.restapiexample.com/api/v1/employees' ) 86 | ).body )['data'].cast() 87 | , 120); 88 | ``` 89 | 90 | #### Saved data for limited time 91 | 92 | The data will be destroyed when it reached the time you set. 93 | 94 | ```dart 95 | cache.remember('key', 'data', 120); // saved for 2 mins or 120 seconds 96 | cache.write('key', 'data', 120); 97 | ``` 98 | 99 | #### Cache multipe datatype 100 | 101 | You can cache multiple datatype. Supported datatype for now are `String`, `Map`, `List` and `List`. When you use `cache.load()` to get back the data, it will return the data in the original datatype. 102 | 103 | ```dart 104 | cache.remember('key', { 105 | 'name' : 'Ashraf Kamarudin', 106 | 'depth2' : { 107 | 'name' : 'depth2', 108 | 'depth3' : { 109 | 'name': 'depth3' 110 | } 111 | } 112 | }); 113 | 114 | cache.load('key'); // will return data in map datatype. 115 | ``` 116 | -------------------------------------------------------------------------------- /dart.yml: -------------------------------------------------------------------------------- 1 | name: Dart CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | container: 15 | image: google/dart:latest 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Install dependencies 20 | run: pub get 21 | - name: Run tests 22 | run: flutter test 23 | -------------------------------------------------------------------------------- /example/example.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_cache/flutter_cache.dart' as cache; 2 | 3 | void main() async { 4 | // create new cache. 5 | cache.remember('key', 'data'); 6 | cache.write('key', 'data'); 7 | 8 | // add Cache lifetime on create 9 | cache.remember('key', 'data', 120); 10 | cache.write('key', 'data', 120); 11 | 12 | // load Cache by key 13 | // return `defaultValue` if key not exists 14 | cache.load('key', 'defaultValue'); 15 | 16 | // destroy single cache by key 17 | cache.destroy('key'); 18 | 19 | // destroy all cache 20 | cache.clear(); 21 | 22 | await cache.remember('key', () { 23 | return 'test'; // or logic fetching data from api; 24 | }); 25 | 26 | // or 27 | 28 | await cache.remember('key', () => 'test'); 29 | 30 | cache.remember('key', 'data', 120); // saved for 2 mins or 120 seconds 31 | cache.write('key', 'data', 120); 32 | 33 | // multi depth map datatype. 34 | cache.remember('key', { 35 | 'name': 'Ashraf Kamarudin', 36 | 'depth2': { 37 | 'name': 'depth2', 38 | 'depth3': {'name': 'depth3'} 39 | } 40 | }); 41 | 42 | cache.load('key'); // will return data in map datatype. 43 | } 44 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "2.5.0" 11 | boolean_selector: 12 | dependency: transitive 13 | description: 14 | name: boolean_selector 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "2.1.0" 18 | characters: 19 | dependency: transitive 20 | description: 21 | name: characters 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "1.1.0" 25 | charcode: 26 | dependency: transitive 27 | description: 28 | name: charcode 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "1.2.0" 32 | clock: 33 | dependency: transitive 34 | description: 35 | name: clock 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "1.1.0" 39 | collection: 40 | dependency: transitive 41 | description: 42 | name: collection 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.15.0" 46 | fake_async: 47 | dependency: transitive 48 | description: 49 | name: fake_async 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | ffi: 54 | dependency: transitive 55 | description: 56 | name: ffi 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.1.3" 60 | file: 61 | dependency: transitive 62 | description: 63 | name: file 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "5.2.1" 67 | flutter: 68 | dependency: "direct main" 69 | description: flutter 70 | source: sdk 71 | version: "0.0.0" 72 | flutter_test: 73 | dependency: "direct dev" 74 | description: flutter 75 | source: sdk 76 | version: "0.0.0" 77 | flutter_web_plugins: 78 | dependency: transitive 79 | description: flutter 80 | source: sdk 81 | version: "0.0.0" 82 | intl: 83 | dependency: transitive 84 | description: 85 | name: intl 86 | url: "https://pub.dartlang.org" 87 | source: hosted 88 | version: "0.16.1" 89 | js: 90 | dependency: transitive 91 | description: 92 | name: js 93 | url: "https://pub.dartlang.org" 94 | source: hosted 95 | version: "0.6.3" 96 | matcher: 97 | dependency: transitive 98 | description: 99 | name: matcher 100 | url: "https://pub.dartlang.org" 101 | source: hosted 102 | version: "0.12.10" 103 | meta: 104 | dependency: transitive 105 | description: 106 | name: meta 107 | url: "https://pub.dartlang.org" 108 | source: hosted 109 | version: "1.3.0" 110 | path: 111 | dependency: transitive 112 | description: 113 | name: path 114 | url: "https://pub.dartlang.org" 115 | source: hosted 116 | version: "1.8.0" 117 | path_provider_linux: 118 | dependency: transitive 119 | description: 120 | name: path_provider_linux 121 | url: "https://pub.dartlang.org" 122 | source: hosted 123 | version: "0.0.1+2" 124 | path_provider_platform_interface: 125 | dependency: transitive 126 | description: 127 | name: path_provider_platform_interface 128 | url: "https://pub.dartlang.org" 129 | source: hosted 130 | version: "1.0.4" 131 | path_provider_windows: 132 | dependency: transitive 133 | description: 134 | name: path_provider_windows 135 | url: "https://pub.dartlang.org" 136 | source: hosted 137 | version: "0.0.4+3" 138 | platform: 139 | dependency: transitive 140 | description: 141 | name: platform 142 | url: "https://pub.dartlang.org" 143 | source: hosted 144 | version: "2.2.1" 145 | plugin_platform_interface: 146 | dependency: transitive 147 | description: 148 | name: plugin_platform_interface 149 | url: "https://pub.dartlang.org" 150 | source: hosted 151 | version: "1.0.3" 152 | process: 153 | dependency: transitive 154 | description: 155 | name: process 156 | url: "https://pub.dartlang.org" 157 | source: hosted 158 | version: "3.0.13" 159 | shared_preferences: 160 | dependency: "direct main" 161 | description: 162 | name: shared_preferences 163 | url: "https://pub.dartlang.org" 164 | source: hosted 165 | version: "0.5.12+4" 166 | shared_preferences_linux: 167 | dependency: transitive 168 | description: 169 | name: shared_preferences_linux 170 | url: "https://pub.dartlang.org" 171 | source: hosted 172 | version: "0.0.2+4" 173 | shared_preferences_macos: 174 | dependency: transitive 175 | description: 176 | name: shared_preferences_macos 177 | url: "https://pub.dartlang.org" 178 | source: hosted 179 | version: "0.0.1+11" 180 | shared_preferences_platform_interface: 181 | dependency: transitive 182 | description: 183 | name: shared_preferences_platform_interface 184 | url: "https://pub.dartlang.org" 185 | source: hosted 186 | version: "1.0.4" 187 | shared_preferences_web: 188 | dependency: transitive 189 | description: 190 | name: shared_preferences_web 191 | url: "https://pub.dartlang.org" 192 | source: hosted 193 | version: "0.1.2+7" 194 | shared_preferences_windows: 195 | dependency: transitive 196 | description: 197 | name: shared_preferences_windows 198 | url: "https://pub.dartlang.org" 199 | source: hosted 200 | version: "0.0.2+3" 201 | sky_engine: 202 | dependency: transitive 203 | description: flutter 204 | source: sdk 205 | version: "0.0.99" 206 | source_span: 207 | dependency: transitive 208 | description: 209 | name: source_span 210 | url: "https://pub.dartlang.org" 211 | source: hosted 212 | version: "1.8.1" 213 | stack_trace: 214 | dependency: transitive 215 | description: 216 | name: stack_trace 217 | url: "https://pub.dartlang.org" 218 | source: hosted 219 | version: "1.10.0" 220 | stream_channel: 221 | dependency: transitive 222 | description: 223 | name: stream_channel 224 | url: "https://pub.dartlang.org" 225 | source: hosted 226 | version: "2.1.0" 227 | string_scanner: 228 | dependency: transitive 229 | description: 230 | name: string_scanner 231 | url: "https://pub.dartlang.org" 232 | source: hosted 233 | version: "1.1.0" 234 | term_glyph: 235 | dependency: transitive 236 | description: 237 | name: term_glyph 238 | url: "https://pub.dartlang.org" 239 | source: hosted 240 | version: "1.2.0" 241 | test_api: 242 | dependency: transitive 243 | description: 244 | name: test_api 245 | url: "https://pub.dartlang.org" 246 | source: hosted 247 | version: "0.2.19" 248 | typed_data: 249 | dependency: transitive 250 | description: 251 | name: typed_data 252 | url: "https://pub.dartlang.org" 253 | source: hosted 254 | version: "1.3.0" 255 | vector_math: 256 | dependency: transitive 257 | description: 258 | name: vector_math 259 | url: "https://pub.dartlang.org" 260 | source: hosted 261 | version: "2.1.0" 262 | win32: 263 | dependency: transitive 264 | description: 265 | name: win32 266 | url: "https://pub.dartlang.org" 267 | source: hosted 268 | version: "1.7.4+1" 269 | xdg_directories: 270 | dependency: transitive 271 | description: 272 | name: xdg_directories 273 | url: "https://pub.dartlang.org" 274 | source: hosted 275 | version: "0.1.0" 276 | sdks: 277 | dart: ">=2.12.0-0.0 <3.0.0" 278 | flutter: ">=1.12.13+hotfix.5" 279 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_cache 2 | description: A simple cache package for flutter. This package is a wrapper for shared preference and makes working with shared preference easier. 3 | version: 0.0.6 4 | author: Ashraf Kamarudin 5 | homepage: https://github.com/SekolahCode/flutter_cache 6 | 7 | environment: 8 | sdk: ">=2.7.0 <3.0.0" 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | shared_preferences: ^0.5.6 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://dart.dev/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | 25 | # To add assets to your package, add an assets section, like this: 26 | # assets: 27 | # - images/a_dot_burr.jpeg 28 | # - images/a_dot_ham.jpeg 29 | # 30 | # For details regarding assets in packages, see 31 | # https://flutter.dev/assets-and-images/#from-packages 32 | # 33 | # An image asset can refer to one or more resolution-specific "variants", see 34 | # https://flutter.dev/assets-and-images/#resolution-aware. 35 | 36 | # To add custom fonts to your package, add a fonts section here, 37 | # in this "flutter" section. Each entry in this list should have a 38 | # "family" key with the font family name, and a "fonts" key with a 39 | # list giving the asset and other descriptors for the font. For 40 | # example: 41 | # fonts: 42 | # - family: Schyler 43 | # fonts: 44 | # - asset: fonts/Schyler-Regular.ttf 45 | # - asset: fonts/Schyler-Italic.ttf 46 | # style: italic 47 | # - family: Trajan Pro 48 | # fonts: 49 | # - asset: fonts/TrajanPro.ttf 50 | # - asset: fonts/TrajanPro_Bold.ttf 51 | # weight: 700 52 | # 53 | # For details regarding fonts in packages, see 54 | # https://flutter.dev/custom-fonts/#from-packages 55 | -------------------------------------------------------------------------------- /lib/Cache.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:shared_preferences/shared_preferences.dart'; 3 | import 'package:flutter_cache/Parse.dart'; 4 | 5 | class Cache { 6 | late String key; 7 | 8 | /* Cache Content*/ 9 | String? contentKey; 10 | var content; 11 | 12 | /* Cache Content's Type*/ 13 | String? typeKey; 14 | String? type; 15 | 16 | /* Cache Expiry*/ 17 | int? expiredAfter; 18 | 19 | /* 20 | * Cache Class Constructors Section 21 | */ 22 | Cache(key, data) { 23 | Map parsedData = Parse.content(data); 24 | 25 | this.key = key; 26 | this.setContent(parsedData['content']); 27 | this.setType(parsedData['type']); 28 | } 29 | 30 | Cache.rebuild(key) { 31 | this.key = key; 32 | } 33 | 34 | /* 35 | * Cache Class Setters & Getters 36 | */ 37 | setKey(String key) { 38 | this.key = key; 39 | } 40 | 41 | setContent(var data, [String? contentKey]) { 42 | this.content = data; 43 | this.contentKey = contentKey ?? this.generateCompositeKey('content'); 44 | } 45 | 46 | setType(String? type, [String? typeKey]) { 47 | this.type = type; 48 | this.typeKey = typeKey ?? this.generateCompositeKey('type'); 49 | } 50 | 51 | setExpiredAfter(int expiredAfter) { 52 | this.expiredAfter = expiredAfter + Cache.currentTimeInSeconds(); 53 | } 54 | 55 | /* 56 | * Saved cached contents into Shared Preference 57 | * 58 | * @return void 59 | */ 60 | void save(Cache cache) async { 61 | SharedPreferences prefs = await SharedPreferences.getInstance(); 62 | 63 | // set Original Cache key to cache content's key and cache type's key 64 | prefs.setString(cache.key, 65 | jsonEncode({'content': cache.contentKey, 'type': cache.typeKey})); 66 | 67 | if (cache.content is String) 68 | prefs.setString(cache.contentKey!, cache.content); 69 | 70 | if (cache.content is List) 71 | prefs.setStringList(cache.contentKey!, cache.content); 72 | 73 | if (cache.expiredAfter != null) 74 | prefs.setInt(key + 'ExpiredAt', cache.expiredAfter!); 75 | 76 | prefs.setString(cache.typeKey!, cache.type!); 77 | } 78 | 79 | /* 80 | * Cache Class Helper Function Section 81 | * 82 | * This is where all custom functions used by this class reside. 83 | * All functions should be private. 84 | */ 85 | String generateCompositeKey(String keyType) { 86 | return keyType + Cache.currentTimeInSeconds().toString() + this.key; 87 | } 88 | 89 | static int currentTimeInSeconds() { 90 | var ms = (new DateTime.now()).millisecondsSinceEpoch; 91 | return (ms / 1000).round(); 92 | } 93 | 94 | static bool isExpired(int? cacheExpiryInfo) { 95 | if (cacheExpiryInfo != null && 96 | cacheExpiryInfo < Cache.currentTimeInSeconds()) { 97 | return true; 98 | } 99 | 100 | return false; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /lib/Parse.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter_cache/shouldCache.dart'; 3 | 4 | class Parse { 5 | static content(var data) { 6 | /* @type String */ 7 | if (data is String) return {'content': data, 'type': 'String'}; 8 | 9 | /* @type Map */ 10 | if (data is Map) return {'content': jsonEncode(data), 'type': 'Map'}; 11 | 12 | /* @type List */ 13 | if (data is List) { 14 | return {'content': data, 'type': 'List'}; 15 | } 16 | 17 | /* @type List */ 18 | if (data is List) { 19 | List list = data.map((i) => jsonEncode(i)).toList(); 20 | return {'content': list, 'type': 'List'}; 21 | } 22 | 23 | /* @type List */ 24 | if (data is List) { 25 | List list = data.map((i) => jsonEncode(i.toMap())).toList(); 26 | return {'content': list, 'type': 'List'}; 27 | } 28 | 29 | throw ('Unsupported Data Type'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /lib/flutter_cache.dart: -------------------------------------------------------------------------------- 1 | library flutter_cache; 2 | 3 | import 'dart:convert'; 4 | import 'package:flutter_cache/Cache.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | /* 8 | * This function will return cached data if exist, 9 | * If not exist, will create new cached data. 10 | * 11 | * @return Cache.content 12 | */ 13 | Future remember(String key, var data, [int? expiredAt]) async { 14 | if (await load(key) == null) { 15 | if (data is Function) { 16 | data = await data(); 17 | } 18 | 19 | return write(key, data, expiredAt); 20 | } 21 | 22 | return load(key); 23 | } 24 | 25 | /* 26 | * This will overwrite data if exist and create new if not. 27 | * 28 | * @return Cache.content 29 | */ 30 | Future write(String key, var data, [int? expiredAfter]) async { 31 | Cache cache = new Cache(key, data); 32 | if (expiredAfter != null) cache.setExpiredAfter(expiredAfter); 33 | 34 | cache.save(cache); 35 | 36 | return load(key); 37 | } 38 | 39 | /* 40 | * load saved cached data. 41 | * 42 | * @return Cache.content 43 | */ 44 | Future load(String key, [var defaultValue = null, bool list = false]) async { 45 | SharedPreferences prefs = await SharedPreferences.getInstance(); 46 | 47 | // Guard 48 | if (prefs.getString(key) == null) return defaultValue; 49 | 50 | if (Cache.isExpired(prefs.getInt(key + 'ExpiredAt'))) { 51 | destroy(key); 52 | return null; 53 | } 54 | 55 | Map keys = jsonDecode(prefs.getString(key)!); 56 | Cache cache = new Cache.rebuild(key); 57 | String? cacheType = prefs.getString(keys['type']); 58 | var cacheContent; 59 | 60 | if (cacheType == 'String') cacheContent = prefs.getString(keys['content']); 61 | 62 | if (cacheType == 'Map') 63 | cacheContent = jsonDecode(prefs.getString(keys['content'])!); 64 | 65 | if (cacheType == 'List') 66 | cacheContent = prefs.getStringList(keys['content']); 67 | 68 | if (cacheType == 'List') 69 | cacheContent = prefs.getStringList(keys['content'])! 70 | .map((i) => jsonDecode(i)) 71 | .toList(); 72 | 73 | cache.setContent(cacheContent, keys['content']); 74 | cache.setType(cacheType, keys['type']); 75 | 76 | return cache.content; 77 | } 78 | 79 | /* 80 | * will clear all shared preference data 81 | * 82 | * @return void 83 | */ 84 | void clear() async { 85 | SharedPreferences prefs = await SharedPreferences.getInstance(); 86 | prefs.clear(); 87 | } 88 | 89 | /* 90 | * unset single shared preference key 91 | * 92 | * @return void 93 | */ 94 | void destroy(String key) async { 95 | SharedPreferences prefs = await SharedPreferences.getInstance(); 96 | Map keys = jsonDecode(prefs.getString(key)!); 97 | 98 | // remove all cache trace 99 | prefs.remove(key); 100 | prefs.remove(keys['content']); 101 | prefs.remove(keys['type']); 102 | prefs.remove(key + 'ExpiredAt'); 103 | } 104 | -------------------------------------------------------------------------------- /lib/shouldCache.dart: -------------------------------------------------------------------------------- 1 | 2 | abstract class ShouldCache { 3 | Map toMap(); 4 | } -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | _fe_analyzer_shared: 5 | dependency: transitive 6 | description: 7 | name: _fe_analyzer_shared 8 | url: "https://pub.dartlang.org" 9 | source: hosted 10 | version: "17.0.0" 11 | analyzer: 12 | dependency: transitive 13 | description: 14 | name: analyzer 15 | url: "https://pub.dartlang.org" 16 | source: hosted 17 | version: "1.1.0" 18 | args: 19 | dependency: transitive 20 | description: 21 | name: args 22 | url: "https://pub.dartlang.org" 23 | source: hosted 24 | version: "2.0.0" 25 | async: 26 | dependency: transitive 27 | description: 28 | name: async 29 | url: "https://pub.dartlang.org" 30 | source: hosted 31 | version: "2.5.0" 32 | boolean_selector: 33 | dependency: transitive 34 | description: 35 | name: boolean_selector 36 | url: "https://pub.dartlang.org" 37 | source: hosted 38 | version: "2.1.0" 39 | characters: 40 | dependency: transitive 41 | description: 42 | name: characters 43 | url: "https://pub.dartlang.org" 44 | source: hosted 45 | version: "1.1.0" 46 | charcode: 47 | dependency: transitive 48 | description: 49 | name: charcode 50 | url: "https://pub.dartlang.org" 51 | source: hosted 52 | version: "1.2.0" 53 | cli_util: 54 | dependency: transitive 55 | description: 56 | name: cli_util 57 | url: "https://pub.dartlang.org" 58 | source: hosted 59 | version: "0.3.0" 60 | collection: 61 | dependency: transitive 62 | description: 63 | name: collection 64 | url: "https://pub.dartlang.org" 65 | source: hosted 66 | version: "1.15.0" 67 | convert: 68 | dependency: transitive 69 | description: 70 | name: convert 71 | url: "https://pub.dartlang.org" 72 | source: hosted 73 | version: "3.0.0" 74 | coverage: 75 | dependency: transitive 76 | description: 77 | name: coverage 78 | url: "https://pub.dartlang.org" 79 | source: hosted 80 | version: "1.0.1" 81 | crypto: 82 | dependency: transitive 83 | description: 84 | name: crypto 85 | url: "https://pub.dartlang.org" 86 | source: hosted 87 | version: "3.0.0" 88 | ffi: 89 | dependency: transitive 90 | description: 91 | name: ffi 92 | url: "https://pub.dartlang.org" 93 | source: hosted 94 | version: "1.0.0" 95 | file: 96 | dependency: transitive 97 | description: 98 | name: file 99 | url: "https://pub.dartlang.org" 100 | source: hosted 101 | version: "6.1.0" 102 | flutter: 103 | dependency: "direct main" 104 | description: flutter 105 | source: sdk 106 | version: "0.0.0" 107 | flutter_web_plugins: 108 | dependency: transitive 109 | description: flutter 110 | source: sdk 111 | version: "0.0.0" 112 | glob: 113 | dependency: transitive 114 | description: 115 | name: glob 116 | url: "https://pub.dartlang.org" 117 | source: hosted 118 | version: "2.0.0" 119 | http_multi_server: 120 | dependency: transitive 121 | description: 122 | name: http_multi_server 123 | url: "https://pub.dartlang.org" 124 | source: hosted 125 | version: "3.0.0" 126 | http_parser: 127 | dependency: transitive 128 | description: 129 | name: http_parser 130 | url: "https://pub.dartlang.org" 131 | source: hosted 132 | version: "4.0.0" 133 | io: 134 | dependency: transitive 135 | description: 136 | name: io 137 | url: "https://pub.dartlang.org" 138 | source: hosted 139 | version: "1.0.0" 140 | js: 141 | dependency: transitive 142 | description: 143 | name: js 144 | url: "https://pub.dartlang.org" 145 | source: hosted 146 | version: "0.6.3" 147 | logging: 148 | dependency: transitive 149 | description: 150 | name: logging 151 | url: "https://pub.dartlang.org" 152 | source: hosted 153 | version: "1.0.0" 154 | matcher: 155 | dependency: transitive 156 | description: 157 | name: matcher 158 | url: "https://pub.dartlang.org" 159 | source: hosted 160 | version: "0.12.10" 161 | meta: 162 | dependency: transitive 163 | description: 164 | name: meta 165 | url: "https://pub.dartlang.org" 166 | source: hosted 167 | version: "1.3.0" 168 | mime: 169 | dependency: transitive 170 | description: 171 | name: mime 172 | url: "https://pub.dartlang.org" 173 | source: hosted 174 | version: "1.0.0" 175 | node_preamble: 176 | dependency: transitive 177 | description: 178 | name: node_preamble 179 | url: "https://pub.dartlang.org" 180 | source: hosted 181 | version: "2.0.0" 182 | package_config: 183 | dependency: transitive 184 | description: 185 | name: package_config 186 | url: "https://pub.dartlang.org" 187 | source: hosted 188 | version: "2.0.0" 189 | path: 190 | dependency: transitive 191 | description: 192 | name: path 193 | url: "https://pub.dartlang.org" 194 | source: hosted 195 | version: "1.8.0" 196 | path_provider_linux: 197 | dependency: transitive 198 | description: 199 | name: path_provider_linux 200 | url: "https://pub.dartlang.org" 201 | source: hosted 202 | version: "2.0.0" 203 | path_provider_platform_interface: 204 | dependency: transitive 205 | description: 206 | name: path_provider_platform_interface 207 | url: "https://pub.dartlang.org" 208 | source: hosted 209 | version: "2.0.1" 210 | path_provider_windows: 211 | dependency: transitive 212 | description: 213 | name: path_provider_windows 214 | url: "https://pub.dartlang.org" 215 | source: hosted 216 | version: "2.0.0" 217 | pedantic: 218 | dependency: transitive 219 | description: 220 | name: pedantic 221 | url: "https://pub.dartlang.org" 222 | source: hosted 223 | version: "1.11.0" 224 | platform: 225 | dependency: transitive 226 | description: 227 | name: platform 228 | url: "https://pub.dartlang.org" 229 | source: hosted 230 | version: "3.0.0" 231 | plugin_platform_interface: 232 | dependency: transitive 233 | description: 234 | name: plugin_platform_interface 235 | url: "https://pub.dartlang.org" 236 | source: hosted 237 | version: "2.0.0" 238 | pool: 239 | dependency: transitive 240 | description: 241 | name: pool 242 | url: "https://pub.dartlang.org" 243 | source: hosted 244 | version: "1.5.0" 245 | process: 246 | dependency: transitive 247 | description: 248 | name: process 249 | url: "https://pub.dartlang.org" 250 | source: hosted 251 | version: "4.1.0" 252 | pub_semver: 253 | dependency: transitive 254 | description: 255 | name: pub_semver 256 | url: "https://pub.dartlang.org" 257 | source: hosted 258 | version: "2.0.0" 259 | shared_preferences: 260 | dependency: "direct main" 261 | description: 262 | name: shared_preferences 263 | url: "https://pub.dartlang.org" 264 | source: hosted 265 | version: "2.0.4" 266 | shared_preferences_linux: 267 | dependency: transitive 268 | description: 269 | name: shared_preferences_linux 270 | url: "https://pub.dartlang.org" 271 | source: hosted 272 | version: "2.0.0" 273 | shared_preferences_macos: 274 | dependency: transitive 275 | description: 276 | name: shared_preferences_macos 277 | url: "https://pub.dartlang.org" 278 | source: hosted 279 | version: "2.0.0" 280 | shared_preferences_platform_interface: 281 | dependency: transitive 282 | description: 283 | name: shared_preferences_platform_interface 284 | url: "https://pub.dartlang.org" 285 | source: hosted 286 | version: "2.0.0" 287 | shared_preferences_web: 288 | dependency: transitive 289 | description: 290 | name: shared_preferences_web 291 | url: "https://pub.dartlang.org" 292 | source: hosted 293 | version: "2.0.0" 294 | shared_preferences_windows: 295 | dependency: transitive 296 | description: 297 | name: shared_preferences_windows 298 | url: "https://pub.dartlang.org" 299 | source: hosted 300 | version: "2.0.0" 301 | shelf: 302 | dependency: transitive 303 | description: 304 | name: shelf 305 | url: "https://pub.dartlang.org" 306 | source: hosted 307 | version: "1.0.0" 308 | shelf_packages_handler: 309 | dependency: transitive 310 | description: 311 | name: shelf_packages_handler 312 | url: "https://pub.dartlang.org" 313 | source: hosted 314 | version: "3.0.0" 315 | shelf_static: 316 | dependency: transitive 317 | description: 318 | name: shelf_static 319 | url: "https://pub.dartlang.org" 320 | source: hosted 321 | version: "1.0.0" 322 | shelf_web_socket: 323 | dependency: transitive 324 | description: 325 | name: shelf_web_socket 326 | url: "https://pub.dartlang.org" 327 | source: hosted 328 | version: "1.0.0" 329 | sky_engine: 330 | dependency: transitive 331 | description: flutter 332 | source: sdk 333 | version: "0.0.99" 334 | source_map_stack_trace: 335 | dependency: transitive 336 | description: 337 | name: source_map_stack_trace 338 | url: "https://pub.dartlang.org" 339 | source: hosted 340 | version: "2.1.0" 341 | source_maps: 342 | dependency: transitive 343 | description: 344 | name: source_maps 345 | url: "https://pub.dartlang.org" 346 | source: hosted 347 | version: "0.10.10" 348 | source_span: 349 | dependency: transitive 350 | description: 351 | name: source_span 352 | url: "https://pub.dartlang.org" 353 | source: hosted 354 | version: "1.8.1" 355 | stack_trace: 356 | dependency: transitive 357 | description: 358 | name: stack_trace 359 | url: "https://pub.dartlang.org" 360 | source: hosted 361 | version: "1.10.0" 362 | stream_channel: 363 | dependency: transitive 364 | description: 365 | name: stream_channel 366 | url: "https://pub.dartlang.org" 367 | source: hosted 368 | version: "2.1.0" 369 | string_scanner: 370 | dependency: transitive 371 | description: 372 | name: string_scanner 373 | url: "https://pub.dartlang.org" 374 | source: hosted 375 | version: "1.1.0" 376 | term_glyph: 377 | dependency: transitive 378 | description: 379 | name: term_glyph 380 | url: "https://pub.dartlang.org" 381 | source: hosted 382 | version: "1.2.0" 383 | test: 384 | dependency: "direct dev" 385 | description: 386 | name: test 387 | url: "https://pub.dartlang.org" 388 | source: hosted 389 | version: "1.16.7" 390 | test_api: 391 | dependency: transitive 392 | description: 393 | name: test_api 394 | url: "https://pub.dartlang.org" 395 | source: hosted 396 | version: "0.3.0" 397 | test_core: 398 | dependency: transitive 399 | description: 400 | name: test_core 401 | url: "https://pub.dartlang.org" 402 | source: hosted 403 | version: "0.3.18" 404 | typed_data: 405 | dependency: transitive 406 | description: 407 | name: typed_data 408 | url: "https://pub.dartlang.org" 409 | source: hosted 410 | version: "1.3.0" 411 | vector_math: 412 | dependency: transitive 413 | description: 414 | name: vector_math 415 | url: "https://pub.dartlang.org" 416 | source: hosted 417 | version: "2.1.0" 418 | vm_service: 419 | dependency: transitive 420 | description: 421 | name: vm_service 422 | url: "https://pub.dartlang.org" 423 | source: hosted 424 | version: "6.1.0+1" 425 | watcher: 426 | dependency: transitive 427 | description: 428 | name: watcher 429 | url: "https://pub.dartlang.org" 430 | source: hosted 431 | version: "1.0.0" 432 | web_socket_channel: 433 | dependency: transitive 434 | description: 435 | name: web_socket_channel 436 | url: "https://pub.dartlang.org" 437 | source: hosted 438 | version: "2.0.0" 439 | webkit_inspection_protocol: 440 | dependency: transitive 441 | description: 442 | name: webkit_inspection_protocol 443 | url: "https://pub.dartlang.org" 444 | source: hosted 445 | version: "1.0.0" 446 | win32: 447 | dependency: transitive 448 | description: 449 | name: win32 450 | url: "https://pub.dartlang.org" 451 | source: hosted 452 | version: "2.0.0" 453 | xdg_directories: 454 | dependency: transitive 455 | description: 456 | name: xdg_directories 457 | url: "https://pub.dartlang.org" 458 | source: hosted 459 | version: "0.2.0" 460 | yaml: 461 | dependency: transitive 462 | description: 463 | name: yaml 464 | url: "https://pub.dartlang.org" 465 | source: hosted 466 | version: "3.1.0" 467 | sdks: 468 | dart: ">=2.12.0 <3.0.0" 469 | flutter: ">=1.20.0" 470 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_cache 2 | description: A simple cache package for flutter. This package is a wrapper for shared preference and makes working with shared preference easier. 3 | version: 0.1.0 4 | author: Ashraf Kamarudin 5 | homepage: https://github.com/SekolahCode/flutter_cache 6 | 7 | environment: 8 | sdk: '>=2.12.0 <3.0.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | shared_preferences: ^2.0.4 14 | 15 | dev_dependencies: 16 | test: ^1.16.7 17 | 18 | # For information on the generic Dart part of this file, see the 19 | # following page: https://dart.dev/tools/pub/pubspec 20 | 21 | # The following section is specific to Flutter. 22 | flutter: 23 | 24 | # To add assets to your package, add an assets section, like this: 25 | # assets: 26 | # - images/a_dot_burr.jpeg 27 | # - images/a_dot_ham.jpeg 28 | # 29 | # For details regarding assets in packages, see 30 | # https://flutter.dev/assets-and-images/#from-packages 31 | # 32 | # An image asset can refer to one or more resolution-specific "variants", see 33 | # https://flutter.dev/assets-and-images/#resolution-aware. 34 | 35 | # To add custom fonts to your package, add a fonts section here, 36 | # in this "flutter" section. Each entry in this list should have a 37 | # "family" key with the font family name, and a "fonts" key with a 38 | # list giving the asset and other descriptors for the font. For 39 | # example: 40 | # fonts: 41 | # - family: Schyler 42 | # fonts: 43 | # - asset: fonts/Schyler-Regular.ttf 44 | # - asset: fonts/Schyler-Italic.ttf 45 | # style: italic 46 | # - family: Trajan Pro 47 | # fonts: 48 | # - asset: fonts/TrajanPro.ttf 49 | # - asset: fonts/TrajanPro_Bold.ttf 50 | # weight: 700 51 | # 52 | # For details regarding fonts in packages, see 53 | # https://flutter.dev/custom-fonts/#from-packages 54 | -------------------------------------------------------------------------------- /test/flutter_cache_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:test/test.dart'; 4 | import 'package:flutter_cache/flutter_cache.dart'; 5 | import 'package:shared_preferences/shared_preferences.dart'; 6 | 7 | void main() { 8 | late String string; 9 | late Map map; 10 | late Map map2; 11 | late List listString; 12 | late List listMap; 13 | late List listMap2; 14 | 15 | setUp(() async { 16 | SharedPreferences.setMockInitialValues({}); 17 | SharedPreferences prefs = await SharedPreferences.getInstance(); 18 | 19 | string = 'data'; 20 | 21 | map = {'data1': 'Ashraf Kamarudin', 'data2': 'Programmer'}; 22 | 23 | map2 = { 24 | // multi depth map 25 | 'name': 'Ashraf Kamarudin', 26 | 'depth2': { 27 | 'name': 'depth2', 28 | 'depth3': {'name': 'depth3'} 29 | } 30 | }; 31 | 32 | listString = ['Ashraf', 'Kamarudin']; 33 | 34 | listMap = [ 35 | {'name': 'Ashraf'}, 36 | {'name': 'Kamarudin'} 37 | ]; 38 | 39 | listMap2 = [ 40 | // multi depth list map 41 | {'name': 'Ashraf'}, 42 | {'name': 'Kamarudin'}, 43 | { 44 | 'name': 'Kamarudin', 45 | 'map': {'age': 'we'} 46 | }, 47 | ]; 48 | }); 49 | 50 | test('It Can Cache Multiple Type', () async { 51 | expect(await write('string', string), string); 52 | expect(await write('map', map), map); 53 | expect(await write('map2', map2), map2); 54 | expect(await write('listString', listString), listString); 55 | expect(await write('listMap', listMap), listMap); 56 | expect(await write('listMap2', listMap2), listMap2); 57 | }); 58 | 59 | test('It can load Cached data', () async { 60 | await write('string', string); 61 | await write('map', map); 62 | 63 | expect(await load('string'), string); 64 | expect(await load('map'), map); 65 | }); 66 | 67 | test('It can load Cached default data if not exists', () async { 68 | expect(await load('data', 'defaultData'), 'defaultData'); 69 | }); 70 | 71 | test('It will load if data exist else create new then load', () async { 72 | write('existing', 'ExistingData'); 73 | 74 | expect(await remember('string', string), string); 75 | expect(await remember('existing', 'NewData'), 'ExistingData'); 76 | }); 77 | 78 | test('It will remove cache data if passed expiry time', () async { 79 | write('willExpire', 'data', 2); 80 | 81 | expect(await load('willExpire'), 'data'); 82 | 83 | await Future.delayed(const Duration(seconds: 3), () {}); 84 | 85 | expect(await load('willExpire'), null); 86 | }); 87 | 88 | test('It can use anonymous function', () async { 89 | await remember('key', () { 90 | return 'old'; 91 | }, 5); 92 | 93 | expect( 94 | await remember('key', () { 95 | return 'new'; 96 | }), 97 | 'old'); 98 | 99 | await Future.delayed(const Duration(seconds: 6), () {}); 100 | 101 | expect( 102 | await remember('key', () { 103 | return 'new'; 104 | }), 105 | 'new'); 106 | 107 | expect( 108 | await remember('string', () { 109 | return 'test'; 110 | }), 111 | 'test'); 112 | 113 | expect(await remember('string', () => 'test'), 'test'); 114 | }); 115 | 116 | test('It will load first then fetch', () async { 117 | await remember('key', () { 118 | return 'old'; 119 | }, 5); 120 | 121 | expect( 122 | await remember('key', () { 123 | return 'new'; 124 | }), 125 | 'old'); 126 | 127 | await Future.delayed(const Duration(seconds: 6), () {}); 128 | 129 | expect( 130 | await remember('key', () { 131 | return 'new'; 132 | }), 133 | 'new'); 134 | }); 135 | 136 | test('It will delete all cache trace', () async { 137 | SharedPreferences prefs = await SharedPreferences.getInstance(); 138 | 139 | await write('string', string, 10); 140 | 141 | // get all keys before destroy 142 | Map keys = jsonDecode(prefs.getString('string')!); 143 | destroy('string'); 144 | 145 | // to make sure it works without await 146 | await Future.delayed(const Duration(seconds: 5), () {}); 147 | 148 | expect(prefs.getString('string'), null); 149 | expect(prefs.getString('string' + 'ExpiredAt'), null); 150 | expect(prefs.getString(keys['content']), null); 151 | expect(prefs.getString(keys['type']), null); 152 | }); 153 | } 154 | --------------------------------------------------------------------------------