├── .flutter-plugins ├── .flutter-plugins-dependencies ├── .fvm ├── flutter_sdk └── fvm_config.json ├── .gitignore ├── .idea ├── libraries │ ├── Dart_SDK.xml │ └── KotlinJavaRuntime.xml ├── modules.xml ├── runConfigurations │ └── main_dart.xml └── workspace.xml ├── .metadata ├── .vscode └── launch.json ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── app │ ├── build.gradle │ └── src │ │ ├── debug │ │ └── AndroidManifest.xml │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── clean_code_architecture_flutter │ │ │ │ └── MainActivity.kt │ │ └── 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 ├── clean_code_architecture_flutter_android.iml ├── gradle.properties ├── gradle │ └── wrapper │ │ └── gradle-wrapper.properties └── settings.gradle ├── build.yaml ├── ios ├── .gitignore ├── Flutter │ ├── .last_build_id │ ├── AppFrameworkInfo.plist │ ├── Debug.xcconfig │ └── Release.xcconfig ├── Podfile ├── Podfile.lock ├── Runner.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ └── xcshareddata │ │ └── xcschemes │ │ └── Runner.xcscheme ├── Runner.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── WorkspaceSettings.xcsettings └── Runner │ ├── AppDelegate.swift │ ├── 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 │ └── Runner-Bridging-Header.h ├── lib ├── common │ ├── configs │ │ └── configuration.dart │ ├── constants │ │ ├── exception_constants.dart │ │ ├── http_constants.dart │ │ ├── local_database_type_constants.dart │ │ └── route_constants.dart │ ├── exceptions │ │ ├── bad_request_exception.dart │ │ ├── forbidden_exception.dart │ │ ├── server_error_exception.dart │ │ ├── server_exception.dart │ │ └── unauthorized_exception.dart │ ├── extensions │ │ └── string_extension.dart │ ├── http │ │ └── http_client.dart │ ├── injector │ │ ├── injector.dart │ │ ├── injector_config.dart │ │ └── injector_config.g.dart │ └── utils │ │ ├── database_util.dart │ │ └── http_utils.dart ├── data │ ├── datasources │ │ ├── local │ │ │ └── local_database │ │ │ │ ├── base_local_database.dart │ │ │ │ ├── tables │ │ │ │ ├── todo_table.dart │ │ │ │ └── todo_table.g.dart │ │ │ │ └── todo_local_datasource.dart │ │ └── remote │ │ │ ├── constants │ │ │ └── todo_remote_datasource_constants.dart │ │ │ └── todo_remote_datasource.dart │ ├── models │ │ └── todo_model.dart │ └── repositories │ │ └── todo_repository_impl.dart ├── domain │ ├── entities │ │ └── todo_entity.dart │ ├── repositories │ │ └── todo_repository.dart │ └── usescases │ │ └── todo_usecase.dart ├── main.dart └── presentation │ ├── app.dart │ ├── journey │ ├── dashboard │ │ ├── dashboard_constants.dart │ │ ├── dashboard_routes.dart │ │ └── dashboard_screen.dart │ └── todo │ │ ├── bloc │ │ ├── todo_bloc.dart │ │ ├── todo_event.dart │ │ └── todo_state.dart │ │ ├── create_todo │ │ ├── create_todo_constants.dart │ │ └── create_todo_screen.dart │ │ ├── todo_list │ │ ├── todo_list_screen.dart │ │ └── widgets │ │ │ ├── todo_item.dart │ │ │ └── todo_list_constants.dart │ │ └── todo_routes.dart │ ├── routes.dart │ └── themes │ ├── custom_icons.dart │ ├── theme_colors.dart │ ├── theme_data.dart │ └── theme_text.dart ├── pubspec.yaml └── test ├── __setup__ ├── base_test_bloc.dart ├── navigation_mock.dart ├── path_provider_mock.dart └── wrapper.dart ├── common ├── exceptions │ ├── bad_request_exception_test.dart │ ├── forbidden_exception_test.dart │ ├── server_error_exception_test.dart │ └── unauthorized_exception_test.dart ├── http │ ├── __mock__ │ │ ├── http_client_data.dart │ │ └── http_client_mock.dart │ └── http_client_test.dart └── utils │ ├── __mock__ │ ├── sample_adapter.dart │ ├── sample_table.dart │ └── sample_table_mock.dart │ ├── database_utils_test.dart │ └── http_util_test.dart ├── data ├── datasources │ ├── local │ │ ├── __mock__ │ │ │ ├── base_local_datasource_mock.dart │ │ │ ├── sample_model.dart │ │ │ ├── todo_local_datasource_mock.dart │ │ │ └── todo_table_data.dart │ │ ├── base_local_datasource_test.dart │ │ └── todo_local_datasource_test.dart │ └── remote │ │ ├── __mock__ │ │ ├── http_client_mock.dart │ │ └── todo_remote_datasource_mock.dart │ │ └── todo_remote_datasource_test.dart ├── models │ ├── __mock__ │ │ └── todo_model_data.dart │ └── todo_model_test.dart └── repositories │ ├── __mock__ │ ├── todo_json_data.dart │ └── todo_repository_mock.dart │ └── todo_repository_impl_test.dart ├── domain ├── entities │ └── __mock__ │ │ └── todo_entity_data.dart └── usecases │ ├── __mock__ │ └── todo_usecase_mock.dart │ └── todo_usecase_test.dart ├── local_database_test_files ├── database_util_test.hive ├── database_util_test.lock ├── testbox.hive ├── testbox.lock ├── todo.hive └── todo.lock └── presentation ├── app_test.dart └── journey ├── dashboard └── dashboard_screen_test.dart └── todo ├── bloc ├── __mock__ │ └── todo_bloc_mock.dart └── todo_bloc_test.dart ├── create_todo └── create_todo_screen_test.dart ├── todo_list ├── todo_list_screen_test.dart └── widgets │ └── todo_item_test.dart └── todo_routes_test.dart /.flutter-plugins: -------------------------------------------------------------------------------- 1 | # This is a generated file; do not edit or check into version control. 2 | path_provider=/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.28/ 3 | path_provider_linux=/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/ 4 | path_provider_macos=/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/ 5 | path_provider_windows=/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/ 6 | -------------------------------------------------------------------------------- /.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"path_provider","path":"/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.28/","dependencies":[]}],"android":[{"name":"path_provider","path":"/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider-1.6.28/","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider_macos-0.0.4+8/","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider_linux-0.0.1+2/","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"/Users/rashmi/.pub-cache/hosted/pub.dartlang.org/path_provider_windows-0.0.4+3/","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2021-04-06 01:59:15.644330","version":"1.22.5"} -------------------------------------------------------------------------------- /.fvm/flutter_sdk: -------------------------------------------------------------------------------- 1 | /Users/rashmi/fvm/versions/1.22.5 -------------------------------------------------------------------------------- /.fvm/fvm_config.json: -------------------------------------------------------------------------------- 1 | {"flutterSdkVersion":"1.22.5"} -------------------------------------------------------------------------------- /.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 | build/ 7 | # If you're building an application, you may want to check-in your pubspec.lock 8 | pubspec.lock 9 | 10 | # Directory created by dartdoc 11 | # If you don't generate documentation locally you can remove this line. 12 | doc/api/ 13 | 14 | # Avoid committing generated Javascript files: 15 | *.dart.js 16 | *.info.json # Produced by the --dump-info flag. 17 | *.js # When generated by dart2js. Don't specify *.js if your 18 | # project includes source files written in JavaScript. 19 | *.js_ 20 | *.js.deps 21 | *.js.map 22 | -------------------------------------------------------------------------------- /.idea/libraries/Dart_SDK.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/libraries/KotlinJavaRuntime.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations/main_dart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/workspace.xml: -------------------------------------------------------------------------------- 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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /.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: 78910062997c3a836feee883712c241a5fd22983 8 | channel: unknown 9 | 10 | project_type: app 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": "clean_code_architecture_flutter", 9 | "request": "launch", 10 | "type": "dart", 11 | "flutterMode": "debug", 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # clean-code-architecture-flutter 2 | 3 | Seed app with clean code architecture 4 | 5 | In clean code architecture, there are three main layers 6 | 7 | - Data 8 | - Domain 9 | - Presentation 10 | along with 11 | - common (can also be called as core) 12 | 13 | ![image](https://user-images.githubusercontent.com/25193983/112195232-b8665d80-8c2f-11eb-83cb-e362b8bff420.png) 14 | 15 | ## Common 16 | 17 | This contains all the common functionalities for the application. 18 | e.g http client, env config, platform specific stuffs like network checker, etc. 19 | 20 | These common functionalities will be used by data layer, domain layer and presentation layer 21 | e.g http client by remote data source, network info by repository layer, enums by 22 | domain layer and config by presentation. 23 | 24 | ## Domain 25 | 26 | This is innermost layer and will contain the core business logic i.e. usecases and business object also called as entities, the usecase depends on the contract of repository (not implementation) 27 | 28 | ## Data 29 | 30 | The data layers contains the repository implementation and this layer is closest to actual data sources and is responsible for communication with data sources. 31 | 32 | The data layer returns the models and not entities and the model also contains the fromJson and toJson mapper. 33 | 34 | Note: We don’t have an extra mapper class, we are just going to leverage the named constructor by dart to map our data set from one form to our models. toJson and fromJson. 35 | 36 | ## Presentation 37 | 38 | This layer contains all the information about the UI and everything to show to the end user customer. The presentation layer also contains the bloc which is the state management and often termed as brain of UI. 39 | 40 | The presentation layer interact via domain layer, i.e entities and use cases. 41 | 42 | # Folder structure 43 | 44 | ``` 45 | ├── lib 46 | │ ├── common 47 | │ │ ├── configs 48 | │ │ │ └── configuration.dart 49 | │ │ ├── constants 50 | │ │ │ ├── exception_constants.dart 51 | │ │ │ ├── http_constants.dart 52 | │ │ │ ├── local_database_type_constants.dart 53 | │ │ │ └── route_constants.dart 54 | │ │ ├── exceptions 55 | │ │ │ ├── bad_request_exception.dart 56 | │ │ │ ├── forbidden_exception.dart 57 | │ │ │ ├── server_error_exception.dart 58 | │ │ │ ├── server_exception.dart 59 | │ │ │ └── unauthorized_exception.dart 60 | │ │ ├── extensions 61 | │ │ │ └── string_extension.dart 62 | │ │ ├── http 63 | │ │ │ └── http_client.dart 64 | │ │ ├── injector 65 | │ │ │ ├── injector.dart 66 | │ │ │ ├── injector_config.dart 67 | │ │ │ └── injector_config.g.dart 68 | │ │ └── utils 69 | │ │ ├── database_util.dart 70 | │ │ └── http_utils.dart 71 | │ ├── data 72 | │ │ ├── datasources 73 | │ │ │ ├── local 74 | │ │ │ │ ├── local_database 75 | │ │ │ │ │ ├── base_local_database.dart 76 | │ │ │ │ │ ├── tables 77 | │ │ │ │ │ │ ├── todo_table.dart 78 | │ │ │ │ │ │ └── todo_table.g.dart 79 | │ │ │ │ │ └── todo_local_datasource.dart 80 | │ │ │ │ └── shared_preference 81 | │ │ │ └── remote 82 | │ │ │ ├── constants 83 | │ │ │ │ └── todo_remote_datasource_constants.dart 84 | │ │ │ └── todo_remote_datasource.dart 85 | │ │ ├── models 86 | │ │ │ └── todo_model.dart 87 | │ │ └── repositories 88 | │ │ └── todo_repository_impl.dart 89 | │ ├── domain 90 | │ │ ├── entities 91 | │ │ │ └── todo_entity.dart 92 | │ │ ├── repositories 93 | │ │ │ └── todo_repository.dart 94 | │ │ └── usescases 95 | │ │ └── todo_usecase.dart 96 | │ ├── main.dart 97 | │ └── presentation 98 | │ ├── app.dart 99 | │ ├── common_bloc 100 | │ ├── journey 101 | │ │ ├── dashboard 102 | │ │ │ ├── dashboard_constants.dart 103 | │ │ │ ├── dashboard_routes.dart 104 | │ │ │ └── dashboard_screen.dart 105 | │ │ └── todo 106 | │ │ ├── bloc 107 | │ │ │ ├── todo_bloc.dart 108 | │ │ │ ├── todo_event.dart 109 | │ │ │ └── todo_state.dart 110 | │ │ ├── create_todo 111 | │ │ │ ├── create_todo_constants.dart 112 | │ │ │ └── create_todo_screen.dart 113 | │ │ ├── todo_list 114 | │ │ │ ├── todo_list_screen.dart 115 | │ │ │ └── widgets 116 | │ │ │ ├── todo_item.dart 117 | │ │ │ └── todo_list_constants.dart 118 | │ │ └── todo_routes.dart 119 | │ ├── routes.dart 120 | │ ├── themes 121 | │ │ ├── custom_icons.dart 122 | │ │ ├── theme_colors.dart 123 | │ │ ├── theme_data.dart 124 | │ │ └── theme_text.dart 125 | │ └── widgets 126 | ├── pubspec.lock 127 | ├── pubspec.yaml 128 | └── test 129 | ├── __setup__ 130 | │ ├── base_test_bloc.dart 131 | │ ├── navigation_mock.dart 132 | │ ├── path_provider_mock.dart 133 | │ └── wrapper.dart 134 | ├── common 135 | │ ├── exceptions 136 | │ │ ├── bad_request_exception_test.dart 137 | │ │ ├── forbidden_exception_test.dart 138 | │ │ ├── server_error_exception_test.dart 139 | │ │ └── unauthorized_exception_test.dart 140 | │ ├── http 141 | │ │ ├── __mock__ 142 | │ │ │ ├── http_client_data.dart 143 | │ │ │ └── http_client_mock.dart 144 | │ │ └── http_client_test.dart 145 | │ └── utils 146 | │ ├── __mock__ 147 | │ │ ├── sample_adapter.dart 148 | │ │ ├── sample_table.dart 149 | │ │ └── sample_table_mock.dart 150 | │ ├── database_utils_test.dart 151 | │ └── http_util_test.dart 152 | ├── data 153 | │ ├── datasources 154 | │ │ ├── local 155 | │ │ │ ├── __mock__ 156 | │ │ │ │ ├── base_local_datasource_mock.dart 157 | │ │ │ │ ├── sample_model.dart 158 | │ │ │ │ ├── todo_local_datasource_mock.dart 159 | │ │ │ │ └── todo_table_data.dart 160 | │ │ │ ├── base_local_datasource_test.dart 161 | │ │ │ └── todo_local_datasource_test.dart 162 | │ │ └── remote 163 | │ │ ├── __mock__ 164 | │ │ │ ├── http_client_mock.dart 165 | │ │ │ └── todo_remote_datasource_mock.dart 166 | │ │ └── todo_remote_datasource_test.dart 167 | │ ├── models 168 | │ │ ├── __mock__ 169 | │ │ │ └── todo_model_data.dart 170 | │ │ └── todo_model_test.dart 171 | │ └── repositories 172 | │ ├── __mock__ 173 | │ │ ├── todo_json_data.dart 174 | │ │ └── todo_repository_mock.dart 175 | │ └── todo_repository_impl_test.dart 176 | ├── domain 177 | │ ├── entities 178 | │ │ └── __mock__ 179 | │ │ └── todo_entity_data.dart 180 | │ └── usecases 181 | │ ├── __mock__ 182 | │ │ └── todo_usecase_mock.dart 183 | │ └── todo_usecase_test.dart 184 | ├── local_database_test_files 185 | │ ├── database_util_test.hive 186 | │ ├── database_util_test.lock 187 | │ ├── testbox.hive 188 | │ ├── testbox.lock 189 | │ ├── todo.hive 190 | │ └── todo.lock 191 | └── presentation 192 | ├── app_test.dart 193 | └── journey 194 | ├── dashboard 195 | │ └── dashboard_screen_test.dart 196 | └── todo 197 | ├── bloc 198 | │ ├── __mock__ 199 | │ │ └── todo_bloc_mock.dart 200 | │ └── todo_bloc_test.dart 201 | ├── create_todo 202 | │ └── create_todo_screen_test.dart 203 | ├── todo_list 204 | │ ├── todo_list_screen_test.dart 205 | │ └── widgets 206 | │ └── todo_item_test.dart 207 | └── todo_routes_test.dart 208 | 209 | ``` 210 | 211 | # How to run 212 | 213 | `flutter run` 214 | 215 | In case of addition to injector file, 216 | run `flutter pub run build_runner build --delete-conflicting-outputs` 217 | 218 | ## How to test 219 | 220 | `flutter test --coverage` 221 | To see percentage 222 | `lcov -r coverage/lcov.info "*/__test*__/*" "*.g.dart" -o coverage/lcov_cleaned.info` 223 | To see coverage of each file in html view 224 | `genhtml coverage/lcov_cleaned.info -o coverage` -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | strong-mode: 5 | implicit-dynamic: true 6 | errors: 7 | # allow having TODOs in the code 8 | todo: ignore 9 | omit_local_variable_types: ignore 10 | missing_required_param: error 11 | avoid_print: error 12 | exclude: 13 | # exclude files 14 | - lib/**.g.dart 15 | enable-experiment: 16 | - extension-methods 17 | 18 | linter: 19 | rules: 20 | - unnecessary_const 21 | - always_declare_return_types 22 | - always_put_control_body_on_new_line 23 | - always_require_non_null_named_parameters 24 | - valid_regexps 25 | - void_checks 26 | - annotate_overrides 27 | - prefer_single_quotes 28 | - avoid_as 29 | - avoid_empty_else 30 | - avoid_print 31 | - avoid_field_initializers_in_const_classes 32 | - avoid_init_to_null 33 | - avoid_null_checks_in_equality_operators 34 | - avoid_returning_null 35 | - avoid_returning_null_for_void 36 | - avoid_return_types_on_setters 37 | - avoid_returning_null_for_future 38 | - avoid_returning_this 39 | - avoid_single_cascade_in_expression_statements 40 | - await_only_futures 41 | - camel_case_types 42 | - avoid_void_async 43 | - cascade_invocations 44 | - close_sinks 45 | - cancel_subscriptions 46 | - constant_identifier_names 47 | - curly_braces_in_flow_control_structures 48 | - directives_ordering 49 | - prefer_spread_collections 50 | - prefer_constructors_over_static_methods 51 | - empty_statements 52 | - empty_catches 53 | - empty_constructor_bodies 54 | - unnecessary_getters_setters 55 | - file_names 56 | - implementation_imports 57 | - invariant_booleans 58 | - library_names 59 | - library_prefixes 60 | - literal_only_boolean_expressions 61 | - no_adjacent_strings_in_list 62 | - no_duplicate_case_values 63 | - null_closures 64 | - one_member_abstracts 65 | - only_throw_errors 66 | - package_names 67 | - package_prefixed_library_names 68 | - prefer_adjacent_string_concatenation 69 | - prefer_asserts_in_initializer_lists 70 | - prefer_asserts_with_message 71 | - prefer_conditional_assignment 72 | - prefer_const_constructors 73 | - prefer_const_declarations 74 | - prefer_const_literals_to_create_immutables 75 | - prefer_contains 76 | - prefer_final_fields 77 | - prefer_final_in_for_each 78 | - prefer_final_locals 79 | - prefer_for_elements_to_map_fromIterable 80 | - prefer_generic_function_type_aliases 81 | - prefer_if_null_operators 82 | - prefer_initializing_formals 83 | - prefer_inlined_adds 84 | - prefer_interpolation_to_compose_strings 85 | - prefer_iterable_whereType 86 | - prefer_null_aware_operators 87 | - prefer_typing_uninitialized_variables 88 | - provide_deprecation_message 89 | - recursive_getters 90 | - slash_for_doc_comments 91 | - sort_child_properties_last 92 | - sort_pub_dependencies 93 | - sort_unnamed_constructors_first 94 | - test_types_in_equals 95 | - type_annotate_public_apis 96 | - type_init_formals 97 | - unawaited_futures 98 | - unnecessary_await_in_return 99 | - unnecessary_brace_in_string_interps 100 | - unnecessary_new 101 | - unnecessary_null_aware_assignments 102 | - unnecessary_null_in_if_null_operators 103 | - unnecessary_overrides 104 | - unnecessary_parenthesis 105 | - unnecessary_this 106 | - unrelated_type_equality_checks 107 | - unsafe_html 108 | - use_full_hex_values_for_flutter_colors 109 | - use_rethrow_when_possible 110 | - use_setters_to_change_properties 111 | - use_string_buffers 112 | 113 | # nice to have 114 | - avoid_double_and_int_checks 115 | - avoid_bool_literals_in_conditional_expressions 116 | - avoid_function_literals_in_foreach_calls 117 | - unnecessary_lambdas 118 | - flutter_style_todos 119 | - join_return_with_assignment 120 | - lines_longer_than_80_chars 121 | - prefer_foreach 122 | - use_to_and_as_if_applicable 123 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | -------------------------------------------------------------------------------- /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 plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion 29 30 | 31 | sourceSets { 32 | main.java.srcDirs += 'src/main/kotlin' 33 | } 34 | 35 | lintOptions { 36 | disable 'InvalidPackage' 37 | } 38 | 39 | defaultConfig { 40 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 41 | applicationId "com.example.clean_code_architecture_flutter" 42 | minSdkVersion 16 43 | targetSdkVersion 29 44 | versionCode flutterVersionCode.toInteger() 45 | versionName flutterVersionName 46 | } 47 | 48 | buildTypes { 49 | release { 50 | // TODO: Add your own signing config for the release build. 51 | // Signing with the debug keys for now, so `flutter run --release` works. 52 | signingConfig signingConfigs.debug 53 | } 54 | } 55 | } 56 | 57 | flutter { 58 | source '../..' 59 | } 60 | 61 | dependencies { 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 63 | } 64 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 8 | 12 | 19 | 23 | 27 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /android/app/src/main/kotlin/com/example/clean_code_architecture_flutter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.clean_code_architecture_flutter 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.5.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | task clean(type: Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /android/clean_code_architecture_flutter_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /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-5.6.2-all.zip 7 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /build.yaml: -------------------------------------------------------------------------------- 1 | targets: 2 | $default: 3 | builders: 4 | kiwi: 5 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /ios/Flutter/.last_build_id: -------------------------------------------------------------------------------- 1 | 288cb429972f2f1686fcb168c26731e4 -------------------------------------------------------------------------------- /ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 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 | -------------------------------------------------------------------------------- /ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '9.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - path_provider (0.0.1): 4 | - Flutter 5 | 6 | DEPENDENCIES: 7 | - Flutter (from `Flutter`) 8 | - path_provider (from `.symlinks/plugins/path_provider/ios`) 9 | 10 | EXTERNAL SOURCES: 11 | Flutter: 12 | :path: Flutter 13 | path_provider: 14 | :path: ".symlinks/plugins/path_provider/ios" 15 | 16 | SPEC CHECKSUMS: 17 | Flutter: 0e3d915762c693b495b44d77113d4970485de6ec 18 | path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c 19 | 20 | PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c 21 | 22 | COCOAPODS: 1.9.3 23 | -------------------------------------------------------------------------------- /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 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 13 | 8777303A04A2C15502806CCF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3A8E061F546688E33C44414F /* Pods_Runner.framework */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXCopyFilesBuildPhase section */ 20 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 21 | isa = PBXCopyFilesBuildPhase; 22 | buildActionMask = 2147483647; 23 | dstPath = ""; 24 | dstSubfolderSpec = 10; 25 | files = ( 26 | ); 27 | name = "Embed Frameworks"; 28 | runOnlyForDeploymentPostprocessing = 0; 29 | }; 30 | /* End PBXCopyFilesBuildPhase section */ 31 | 32 | /* Begin PBXFileReference section */ 33 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 34 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 35 | 3A8E061F546688E33C44414F /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 36 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 37 | 6B5B24F3CC635585318E2D01 /* 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 = ""; }; 38 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 39 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 40 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 41 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 42 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 43 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 46 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 47 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 48 | C065135A50BBE2E57DF7DF58 /* 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 = ""; }; 49 | F115696FB03984342899D26A /* 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 = ""; }; 50 | /* End PBXFileReference section */ 51 | 52 | /* Begin PBXFrameworksBuildPhase section */ 53 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 54 | isa = PBXFrameworksBuildPhase; 55 | buildActionMask = 2147483647; 56 | files = ( 57 | 8777303A04A2C15502806CCF /* Pods_Runner.framework in Frameworks */, 58 | ); 59 | runOnlyForDeploymentPostprocessing = 0; 60 | }; 61 | /* End PBXFrameworksBuildPhase section */ 62 | 63 | /* Begin PBXGroup section */ 64 | 82803B68AAC682D741438F3C /* Pods */ = { 65 | isa = PBXGroup; 66 | children = ( 67 | F115696FB03984342899D26A /* Pods-Runner.debug.xcconfig */, 68 | C065135A50BBE2E57DF7DF58 /* Pods-Runner.release.xcconfig */, 69 | 6B5B24F3CC635585318E2D01 /* Pods-Runner.profile.xcconfig */, 70 | ); 71 | name = Pods; 72 | path = Pods; 73 | sourceTree = ""; 74 | }; 75 | 9740EEB11CF90186004384FC /* Flutter */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 79 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 80 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 81 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 82 | ); 83 | name = Flutter; 84 | sourceTree = ""; 85 | }; 86 | 97C146E51CF9000F007C117D = { 87 | isa = PBXGroup; 88 | children = ( 89 | 9740EEB11CF90186004384FC /* Flutter */, 90 | 97C146F01CF9000F007C117D /* Runner */, 91 | 97C146EF1CF9000F007C117D /* Products */, 92 | 82803B68AAC682D741438F3C /* Pods */, 93 | F1CDD7AB7D7759796C8AE235 /* Frameworks */, 94 | ); 95 | sourceTree = ""; 96 | }; 97 | 97C146EF1CF9000F007C117D /* Products */ = { 98 | isa = PBXGroup; 99 | children = ( 100 | 97C146EE1CF9000F007C117D /* Runner.app */, 101 | ); 102 | name = Products; 103 | sourceTree = ""; 104 | }; 105 | 97C146F01CF9000F007C117D /* Runner */ = { 106 | isa = PBXGroup; 107 | children = ( 108 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 109 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 110 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 111 | 97C147021CF9000F007C117D /* Info.plist */, 112 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 113 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 114 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 115 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 116 | ); 117 | path = Runner; 118 | sourceTree = ""; 119 | }; 120 | F1CDD7AB7D7759796C8AE235 /* Frameworks */ = { 121 | isa = PBXGroup; 122 | children = ( 123 | 3A8E061F546688E33C44414F /* Pods_Runner.framework */, 124 | ); 125 | name = Frameworks; 126 | sourceTree = ""; 127 | }; 128 | /* End PBXGroup section */ 129 | 130 | /* Begin PBXNativeTarget section */ 131 | 97C146ED1CF9000F007C117D /* Runner */ = { 132 | isa = PBXNativeTarget; 133 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 134 | buildPhases = ( 135 | 4F1C452EBEBB0DA6D06EF4D5 /* [CP] Check Pods Manifest.lock */, 136 | 9740EEB61CF901F6004384FC /* Run Script */, 137 | 97C146EA1CF9000F007C117D /* Sources */, 138 | 97C146EB1CF9000F007C117D /* Frameworks */, 139 | 97C146EC1CF9000F007C117D /* Resources */, 140 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 141 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 142 | D7D3C94B5E32A095D0FC9092 /* [CP] Embed Pods Frameworks */, 143 | ); 144 | buildRules = ( 145 | ); 146 | dependencies = ( 147 | ); 148 | name = Runner; 149 | productName = Runner; 150 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 151 | productType = "com.apple.product-type.application"; 152 | }; 153 | /* End PBXNativeTarget section */ 154 | 155 | /* Begin PBXProject section */ 156 | 97C146E61CF9000F007C117D /* Project object */ = { 157 | isa = PBXProject; 158 | attributes = { 159 | LastUpgradeCheck = 1020; 160 | ORGANIZATIONNAME = ""; 161 | TargetAttributes = { 162 | 97C146ED1CF9000F007C117D = { 163 | CreatedOnToolsVersion = 7.3.1; 164 | LastSwiftMigration = 1100; 165 | }; 166 | }; 167 | }; 168 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 169 | compatibilityVersion = "Xcode 9.3"; 170 | developmentRegion = en; 171 | hasScannedForEncodings = 0; 172 | knownRegions = ( 173 | en, 174 | Base, 175 | ); 176 | mainGroup = 97C146E51CF9000F007C117D; 177 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 178 | projectDirPath = ""; 179 | projectRoot = ""; 180 | targets = ( 181 | 97C146ED1CF9000F007C117D /* Runner */, 182 | ); 183 | }; 184 | /* End PBXProject section */ 185 | 186 | /* Begin PBXResourcesBuildPhase section */ 187 | 97C146EC1CF9000F007C117D /* Resources */ = { 188 | isa = PBXResourcesBuildPhase; 189 | buildActionMask = 2147483647; 190 | files = ( 191 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 192 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 193 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 194 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 195 | ); 196 | runOnlyForDeploymentPostprocessing = 0; 197 | }; 198 | /* End PBXResourcesBuildPhase section */ 199 | 200 | /* Begin PBXShellScriptBuildPhase section */ 201 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 202 | isa = PBXShellScriptBuildPhase; 203 | buildActionMask = 2147483647; 204 | files = ( 205 | ); 206 | inputPaths = ( 207 | ); 208 | name = "Thin Binary"; 209 | outputPaths = ( 210 | ); 211 | runOnlyForDeploymentPostprocessing = 0; 212 | shellPath = /bin/sh; 213 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 214 | }; 215 | 4F1C452EBEBB0DA6D06EF4D5 /* [CP] Check Pods Manifest.lock */ = { 216 | isa = PBXShellScriptBuildPhase; 217 | buildActionMask = 2147483647; 218 | files = ( 219 | ); 220 | inputFileListPaths = ( 221 | ); 222 | inputPaths = ( 223 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 224 | "${PODS_ROOT}/Manifest.lock", 225 | ); 226 | name = "[CP] Check Pods Manifest.lock"; 227 | outputFileListPaths = ( 228 | ); 229 | outputPaths = ( 230 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 231 | ); 232 | runOnlyForDeploymentPostprocessing = 0; 233 | shellPath = /bin/sh; 234 | 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"; 235 | showEnvVarsInLog = 0; 236 | }; 237 | 9740EEB61CF901F6004384FC /* Run Script */ = { 238 | isa = PBXShellScriptBuildPhase; 239 | buildActionMask = 2147483647; 240 | files = ( 241 | ); 242 | inputPaths = ( 243 | ); 244 | name = "Run Script"; 245 | outputPaths = ( 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | shellPath = /bin/sh; 249 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 250 | }; 251 | D7D3C94B5E32A095D0FC9092 /* [CP] Embed Pods Frameworks */ = { 252 | isa = PBXShellScriptBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | inputPaths = ( 257 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 258 | "${PODS_ROOT}/../Flutter/Flutter.framework", 259 | "${BUILT_PRODUCTS_DIR}/path_provider/path_provider.framework", 260 | ); 261 | name = "[CP] Embed Pods Frameworks"; 262 | outputPaths = ( 263 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 264 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/path_provider.framework", 265 | ); 266 | runOnlyForDeploymentPostprocessing = 0; 267 | shellPath = /bin/sh; 268 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 269 | showEnvVarsInLog = 0; 270 | }; 271 | /* End PBXShellScriptBuildPhase section */ 272 | 273 | /* Begin PBXSourcesBuildPhase section */ 274 | 97C146EA1CF9000F007C117D /* Sources */ = { 275 | isa = PBXSourcesBuildPhase; 276 | buildActionMask = 2147483647; 277 | files = ( 278 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 279 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 280 | ); 281 | runOnlyForDeploymentPostprocessing = 0; 282 | }; 283 | /* End PBXSourcesBuildPhase section */ 284 | 285 | /* Begin PBXVariantGroup section */ 286 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 287 | isa = PBXVariantGroup; 288 | children = ( 289 | 97C146FB1CF9000F007C117D /* Base */, 290 | ); 291 | name = Main.storyboard; 292 | sourceTree = ""; 293 | }; 294 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 295 | isa = PBXVariantGroup; 296 | children = ( 297 | 97C147001CF9000F007C117D /* Base */, 298 | ); 299 | name = LaunchScreen.storyboard; 300 | sourceTree = ""; 301 | }; 302 | /* End PBXVariantGroup section */ 303 | 304 | /* Begin XCBuildConfiguration section */ 305 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 306 | isa = XCBuildConfiguration; 307 | buildSettings = { 308 | ALWAYS_SEARCH_USER_PATHS = NO; 309 | CLANG_ANALYZER_NONNULL = YES; 310 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 311 | CLANG_CXX_LIBRARY = "libc++"; 312 | CLANG_ENABLE_MODULES = YES; 313 | CLANG_ENABLE_OBJC_ARC = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_EMPTY_BODY = YES; 321 | CLANG_WARN_ENUM_CONVERSION = YES; 322 | CLANG_WARN_INFINITE_RECURSION = YES; 323 | CLANG_WARN_INT_CONVERSION = YES; 324 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 325 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 326 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 327 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 328 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 329 | CLANG_WARN_STRICT_PROTOTYPES = YES; 330 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 331 | CLANG_WARN_UNREACHABLE_CODE = YES; 332 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 333 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 334 | COPY_PHASE_STRIP = NO; 335 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 336 | ENABLE_NS_ASSERTIONS = NO; 337 | ENABLE_STRICT_OBJC_MSGSEND = YES; 338 | GCC_C_LANGUAGE_STANDARD = gnu99; 339 | GCC_NO_COMMON_BLOCKS = YES; 340 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 341 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 342 | GCC_WARN_UNDECLARED_SELECTOR = YES; 343 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 344 | GCC_WARN_UNUSED_FUNCTION = YES; 345 | GCC_WARN_UNUSED_VARIABLE = YES; 346 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 347 | MTL_ENABLE_DEBUG_INFO = NO; 348 | SDKROOT = iphoneos; 349 | SUPPORTED_PLATFORMS = iphoneos; 350 | TARGETED_DEVICE_FAMILY = "1,2"; 351 | VALIDATE_PRODUCT = YES; 352 | }; 353 | name = Profile; 354 | }; 355 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 356 | isa = XCBuildConfiguration; 357 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 358 | buildSettings = { 359 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 360 | CLANG_ENABLE_MODULES = YES; 361 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 362 | ENABLE_BITCODE = NO; 363 | FRAMEWORK_SEARCH_PATHS = ( 364 | "$(inherited)", 365 | "$(PROJECT_DIR)/Flutter", 366 | ); 367 | INFOPLIST_FILE = Runner/Info.plist; 368 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 369 | LIBRARY_SEARCH_PATHS = ( 370 | "$(inherited)", 371 | "$(PROJECT_DIR)/Flutter", 372 | ); 373 | PRODUCT_BUNDLE_IDENTIFIER = com.example.cleanCodeArchitectureFlutter; 374 | PRODUCT_NAME = "$(TARGET_NAME)"; 375 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 376 | SWIFT_VERSION = 5.0; 377 | VERSIONING_SYSTEM = "apple-generic"; 378 | }; 379 | name = Profile; 380 | }; 381 | 97C147031CF9000F007C117D /* Debug */ = { 382 | isa = XCBuildConfiguration; 383 | buildSettings = { 384 | ALWAYS_SEARCH_USER_PATHS = NO; 385 | CLANG_ANALYZER_NONNULL = YES; 386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 387 | CLANG_CXX_LIBRARY = "libc++"; 388 | CLANG_ENABLE_MODULES = YES; 389 | CLANG_ENABLE_OBJC_ARC = YES; 390 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 391 | CLANG_WARN_BOOL_CONVERSION = YES; 392 | CLANG_WARN_COMMA = YES; 393 | CLANG_WARN_CONSTANT_CONVERSION = YES; 394 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 395 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 396 | CLANG_WARN_EMPTY_BODY = YES; 397 | CLANG_WARN_ENUM_CONVERSION = YES; 398 | CLANG_WARN_INFINITE_RECURSION = YES; 399 | CLANG_WARN_INT_CONVERSION = YES; 400 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 401 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 402 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 404 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 405 | CLANG_WARN_STRICT_PROTOTYPES = YES; 406 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 407 | CLANG_WARN_UNREACHABLE_CODE = YES; 408 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 409 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 410 | COPY_PHASE_STRIP = NO; 411 | DEBUG_INFORMATION_FORMAT = dwarf; 412 | ENABLE_STRICT_OBJC_MSGSEND = YES; 413 | ENABLE_TESTABILITY = YES; 414 | GCC_C_LANGUAGE_STANDARD = gnu99; 415 | GCC_DYNAMIC_NO_PIC = NO; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_OPTIMIZATION_LEVEL = 0; 418 | GCC_PREPROCESSOR_DEFINITIONS = ( 419 | "DEBUG=1", 420 | "$(inherited)", 421 | ); 422 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 423 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 424 | GCC_WARN_UNDECLARED_SELECTOR = YES; 425 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 426 | GCC_WARN_UNUSED_FUNCTION = YES; 427 | GCC_WARN_UNUSED_VARIABLE = YES; 428 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 429 | MTL_ENABLE_DEBUG_INFO = YES; 430 | ONLY_ACTIVE_ARCH = YES; 431 | SDKROOT = iphoneos; 432 | TARGETED_DEVICE_FAMILY = "1,2"; 433 | }; 434 | name = Debug; 435 | }; 436 | 97C147041CF9000F007C117D /* Release */ = { 437 | isa = XCBuildConfiguration; 438 | buildSettings = { 439 | ALWAYS_SEARCH_USER_PATHS = NO; 440 | CLANG_ANALYZER_NONNULL = YES; 441 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 442 | CLANG_CXX_LIBRARY = "libc++"; 443 | CLANG_ENABLE_MODULES = YES; 444 | CLANG_ENABLE_OBJC_ARC = YES; 445 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 446 | CLANG_WARN_BOOL_CONVERSION = YES; 447 | CLANG_WARN_COMMA = YES; 448 | CLANG_WARN_CONSTANT_CONVERSION = YES; 449 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 450 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 451 | CLANG_WARN_EMPTY_BODY = YES; 452 | CLANG_WARN_ENUM_CONVERSION = YES; 453 | CLANG_WARN_INFINITE_RECURSION = YES; 454 | CLANG_WARN_INT_CONVERSION = YES; 455 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 456 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 457 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 458 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 459 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 460 | CLANG_WARN_STRICT_PROTOTYPES = YES; 461 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 462 | CLANG_WARN_UNREACHABLE_CODE = YES; 463 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 464 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 465 | COPY_PHASE_STRIP = NO; 466 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 467 | ENABLE_NS_ASSERTIONS = NO; 468 | ENABLE_STRICT_OBJC_MSGSEND = YES; 469 | GCC_C_LANGUAGE_STANDARD = gnu99; 470 | GCC_NO_COMMON_BLOCKS = YES; 471 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 472 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 473 | GCC_WARN_UNDECLARED_SELECTOR = YES; 474 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 475 | GCC_WARN_UNUSED_FUNCTION = YES; 476 | GCC_WARN_UNUSED_VARIABLE = YES; 477 | IPHONEOS_DEPLOYMENT_TARGET = 9.0; 478 | MTL_ENABLE_DEBUG_INFO = NO; 479 | SDKROOT = iphoneos; 480 | SUPPORTED_PLATFORMS = iphoneos; 481 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 482 | TARGETED_DEVICE_FAMILY = "1,2"; 483 | VALIDATE_PRODUCT = YES; 484 | }; 485 | name = Release; 486 | }; 487 | 97C147061CF9000F007C117D /* Debug */ = { 488 | isa = XCBuildConfiguration; 489 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 490 | buildSettings = { 491 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 492 | CLANG_ENABLE_MODULES = YES; 493 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 494 | ENABLE_BITCODE = NO; 495 | FRAMEWORK_SEARCH_PATHS = ( 496 | "$(inherited)", 497 | "$(PROJECT_DIR)/Flutter", 498 | ); 499 | INFOPLIST_FILE = Runner/Info.plist; 500 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 501 | LIBRARY_SEARCH_PATHS = ( 502 | "$(inherited)", 503 | "$(PROJECT_DIR)/Flutter", 504 | ); 505 | PRODUCT_BUNDLE_IDENTIFIER = com.example.cleanCodeArchitectureFlutter; 506 | PRODUCT_NAME = "$(TARGET_NAME)"; 507 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 508 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 509 | SWIFT_VERSION = 5.0; 510 | VERSIONING_SYSTEM = "apple-generic"; 511 | }; 512 | name = Debug; 513 | }; 514 | 97C147071CF9000F007C117D /* Release */ = { 515 | isa = XCBuildConfiguration; 516 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 517 | buildSettings = { 518 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 519 | CLANG_ENABLE_MODULES = YES; 520 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 521 | ENABLE_BITCODE = NO; 522 | FRAMEWORK_SEARCH_PATHS = ( 523 | "$(inherited)", 524 | "$(PROJECT_DIR)/Flutter", 525 | ); 526 | INFOPLIST_FILE = Runner/Info.plist; 527 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 528 | LIBRARY_SEARCH_PATHS = ( 529 | "$(inherited)", 530 | "$(PROJECT_DIR)/Flutter", 531 | ); 532 | PRODUCT_BUNDLE_IDENTIFIER = com.example.cleanCodeArchitectureFlutter; 533 | PRODUCT_NAME = "$(TARGET_NAME)"; 534 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 535 | SWIFT_VERSION = 5.0; 536 | VERSIONING_SYSTEM = "apple-generic"; 537 | }; 538 | name = Release; 539 | }; 540 | /* End XCBuildConfiguration section */ 541 | 542 | /* Begin XCConfigurationList section */ 543 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 544 | isa = XCConfigurationList; 545 | buildConfigurations = ( 546 | 97C147031CF9000F007C117D /* Debug */, 547 | 97C147041CF9000F007C117D /* Release */, 548 | 249021D3217E4FDB00AE95B9 /* Profile */, 549 | ); 550 | defaultConfigurationIsVisible = 0; 551 | defaultConfigurationName = Release; 552 | }; 553 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 554 | isa = XCConfigurationList; 555 | buildConfigurations = ( 556 | 97C147061CF9000F007C117D /* Debug */, 557 | 97C147071CF9000F007C117D /* Release */, 558 | 249021D4217E4FDB00AE95B9 /* Profile */, 559 | ); 560 | defaultConfigurationIsVisible = 0; 561 | defaultConfigurationName = Release; 562 | }; 563 | /* End XCConfigurationList section */ 564 | }; 565 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 566 | } 567 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | clean_code_architecture_flutter 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 | -------------------------------------------------------------------------------- /ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /lib/common/configs/configuration.dart: -------------------------------------------------------------------------------- 1 | class Configuration { 2 | static const String host = 'https://api-nodejs-todolist.herokuapp.com'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/common/constants/exception_constants.dart: -------------------------------------------------------------------------------- 1 | class ExceptionConstants { 2 | static String unauthorized = 'UNAUTHORIZED'; 3 | static String badRequest = 'BAD_REQUEST'; 4 | static String forbidden = 'FORBIDDEN'; 5 | static String notFound = 'NOT_FOUND'; 6 | static String internalServerError = 'INTERNAL_SERVER_ERROR'; 7 | static String somethingWentWrong = '"something went wrong"'; 8 | } 9 | -------------------------------------------------------------------------------- /lib/common/constants/http_constants.dart: -------------------------------------------------------------------------------- 1 | class HttpConstants { 2 | static const String contentType = 'Content-Type'; 3 | static String authorization = 'Authorization'; 4 | static const String jsonContentType = 'application/json'; 5 | 6 | // hard-coded value for authorization 7 | static String authorizationValue = 8 | '''Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJfaWQiOiI2MDY3NmVjM2ZlMzcyMzAwMTcwYjFhZGUiLCJpYXQiOjE2MTczOTEyOTl9.cPAFPuOkHq8nw4VWUW_0xrooMZJCH-Ct6J6qgApOug8'''; 9 | } 10 | -------------------------------------------------------------------------------- /lib/common/constants/local_database_type_constants.dart: -------------------------------------------------------------------------------- 1 | /// Order the the ids should be increasing from child most Table to Parent table 2 | class HiveTypeIdConstants { 3 | static const int customerTableId = 0; 4 | } 5 | 6 | class HiveTableNameConstants { 7 | static const todoTableName = 'todo'; 8 | } 9 | -------------------------------------------------------------------------------- /lib/common/constants/route_constants.dart: -------------------------------------------------------------------------------- 1 | class RouteList { 2 | static const String todoList = 'todo-list'; 3 | static const String dashbaord = 'dashboard'; 4 | static const String createTodo = 'create-todo'; 5 | } 6 | -------------------------------------------------------------------------------- /lib/common/exceptions/bad_request_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 2 | import 'package:clean_code_architecture_flutter/common/exceptions/server_exception.dart'; 3 | 4 | class BadRequestException extends ServerException { 5 | BadRequestException(String message) 6 | : super( 7 | message: message ?? '', 8 | code: ExceptionConstants.badRequest, 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /lib/common/exceptions/forbidden_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 2 | import 'package:clean_code_architecture_flutter/common/exceptions/server_exception.dart'; 3 | 4 | class ForbiddenException extends ServerException { 5 | ForbiddenException(String message) 6 | : super( 7 | message: message ?? '', 8 | code: ExceptionConstants.forbidden, 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /lib/common/exceptions/server_error_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 2 | import 'package:clean_code_architecture_flutter/common/exceptions/server_exception.dart'; 3 | 4 | class ServerErrorException extends ServerException { 5 | ServerErrorException(String message) 6 | : super( 7 | message: message ?? '', 8 | code: ExceptionConstants.internalServerError, 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /lib/common/exceptions/server_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | class ServerException implements Exception { 4 | final String code; 5 | final String message; 6 | 7 | ServerException({@required this.message, this.code}); 8 | } 9 | -------------------------------------------------------------------------------- /lib/common/exceptions/unauthorized_exception.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 2 | import 'package:clean_code_architecture_flutter/common/exceptions/server_exception.dart'; 3 | 4 | class UnauthorisedException extends ServerException { 5 | UnauthorisedException(String message) 6 | : super( 7 | message: message ?? '', 8 | code: ExceptionConstants.unauthorized, 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /lib/common/extensions/string_extension.dart: -------------------------------------------------------------------------------- 1 | extension StringExtension on String {} 2 | -------------------------------------------------------------------------------- /lib/common/http/http_client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:http/http.dart'; 5 | 6 | import 'package:clean_code_architecture_flutter/common/configs/configuration.dart'; 7 | import 'package:clean_code_architecture_flutter/common/constants/http_constants.dart'; 8 | import 'package:clean_code_architecture_flutter/common/utils/http_utils.dart'; 9 | 10 | class HttpClient { 11 | Client client; 12 | String host; 13 | Map header; 14 | 15 | HttpClient({ 16 | @required this.host, 17 | this.client, 18 | }) { 19 | client ??= Client(); 20 | } 21 | 22 | factory HttpClient.setTodoAPIhost() => HttpClient(host: Configuration.host); 23 | 24 | Uri _getParsedUrl(String path) { 25 | return Uri.parse('$host$path'); 26 | } 27 | 28 | Map _generateAuthorizationHeader() => { 29 | HttpConstants.authorization: HttpConstants.authorizationValue, 30 | HttpConstants.contentType: HttpConstants.jsonContentType 31 | }; 32 | 33 | Map _generateRequestHeader([ 34 | Map overrideHeader = const {}, 35 | ]) => 36 | { 37 | ..._generateAuthorizationHeader(), 38 | ...overrideHeader, 39 | }; 40 | 41 | dynamic get(String path) async { 42 | final requestHeader = _generateRequestHeader(); 43 | 44 | final Response response = await client.get( 45 | _getParsedUrl(path), 46 | headers: requestHeader, 47 | ); 48 | 49 | return HttpUtil.getResponse( 50 | response, 51 | ); 52 | } 53 | 54 | dynamic post(String path, dynamic data) async { 55 | final requestHeader = _generateRequestHeader(); 56 | 57 | final Response response = await client.post( 58 | _getParsedUrl(path), 59 | body: HttpUtil.encodeRequestBody( 60 | data, requestHeader[HttpConstants.contentType]), 61 | headers: requestHeader, 62 | ); 63 | 64 | return HttpUtil.getResponse( 65 | response, 66 | ); 67 | } 68 | 69 | dynamic patch(String path, dynamic data) async { 70 | final requestHeader = _generateRequestHeader(); 71 | 72 | final Response response = await client.patch( 73 | _getParsedUrl(path), 74 | body: HttpUtil.encodeRequestBody( 75 | data, requestHeader[HttpConstants.contentType]), 76 | headers: requestHeader, 77 | ); 78 | 79 | return HttpUtil.getResponse( 80 | response, 81 | ); 82 | } 83 | 84 | dynamic put(String path, dynamic data) async { 85 | final requestHeader = _generateRequestHeader(); 86 | 87 | final Response response = await client.put( 88 | _getParsedUrl(path), 89 | body: json.encode(data), 90 | headers: requestHeader, 91 | ); 92 | 93 | return HttpUtil.getResponse( 94 | response, 95 | ); 96 | } 97 | 98 | dynamic delete(String path) async { 99 | final requestHeader = _generateRequestHeader(); 100 | 101 | final Response response = await client.delete( 102 | _getParsedUrl(path), 103 | headers: requestHeader, 104 | ); 105 | 106 | return HttpUtil.getResponse(response); 107 | } 108 | 109 | dynamic getImage(String url) async { 110 | final Response response = await client.get(url); 111 | return response; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /lib/common/injector/injector.dart: -------------------------------------------------------------------------------- 1 | import 'package:kiwi/kiwi.dart'; 2 | 3 | abstract class Injector { 4 | static KiwiContainer container = KiwiContainer(); 5 | 6 | static final T Function([String name]) resolve = container.resolve; 7 | } 8 | -------------------------------------------------------------------------------- /lib/common/injector/injector_config.dart: -------------------------------------------------------------------------------- 1 | import 'package:kiwi/kiwi.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/common/http/http_client.dart'; 4 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/todo_local_datasource.dart'; 5 | import 'package:clean_code_architecture_flutter/data/datasources/remote/todo_remote_datasource.dart'; 6 | import 'package:clean_code_architecture_flutter/data/repositories/todo_repository_impl.dart'; 7 | import 'package:clean_code_architecture_flutter/domain/repositories/todo_repository.dart'; 8 | import 'package:clean_code_architecture_flutter/domain/usescases/todo_usecase.dart'; 9 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 10 | 11 | part 'injector_config.g.dart'; 12 | 13 | abstract class InjectorConfig { 14 | static KiwiContainer container; 15 | 16 | static void setup() { 17 | container = KiwiContainer(); 18 | _$InjectorConfig()._configure(); 19 | } 20 | 21 | // ignore: type_annotate_public_apis 22 | static final resolve = container.resolve; 23 | 24 | void _configure() { 25 | _configureBlocs(); 26 | _configureUsecases(); 27 | _configureRepositories(); 28 | _configureRemoteDataSources(); 29 | _configureLocalDataSources(); 30 | _configureCommon(); 31 | } 32 | 33 | // ============ BLOCS ============ 34 | @Register.singleton(TodoBloc) 35 | void _configureBlocs(); 36 | 37 | // ============ USECASES ============ 38 | @Register.singleton(TodoUsecase) 39 | void _configureUsecases(); 40 | 41 | // ============ REPOSITORIES ============ 42 | @Register.singleton(TodoRepository, from: TodoRepositoryImpl) 43 | void _configureRepositories(); 44 | 45 | // ============ REMOTE DATASOURCES ============ 46 | @Register.singleton(TodoRemoteDatasource) 47 | void _configureRemoteDataSources(); 48 | 49 | // ============ LOCAL DATASOURCES ============ 50 | @Register.singleton(TodoLocalDatasource) 51 | void _configureLocalDataSources(); 52 | 53 | // ============ COMMON ============ 54 | @Register.singleton(HttpClient, constructorName: 'setTodoAPIhost') 55 | void _configureCommon(); 56 | } 57 | -------------------------------------------------------------------------------- /lib/common/injector/injector_config.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'injector_config.dart'; 4 | 5 | // ************************************************************************** 6 | // KiwiInjectorGenerator 7 | // ************************************************************************** 8 | 9 | class _$InjectorConfig extends InjectorConfig { 10 | void _configureBlocs() { 11 | final KiwiContainer container = KiwiContainer(); 12 | container.registerSingleton((c) => TodoBloc(todoUsecase: c())); 13 | } 14 | 15 | void _configureUsecases() { 16 | final KiwiContainer container = KiwiContainer(); 17 | container.registerSingleton( 18 | (c) => TodoUsecase(todoRepository: c())); 19 | } 20 | 21 | void _configureRepositories() { 22 | final KiwiContainer container = KiwiContainer(); 23 | container.registerSingleton((c) => TodoRepositoryImpl( 24 | todoRemoteDatasource: c(), 25 | todoLocalDatasource: c())); 26 | } 27 | 28 | void _configureRemoteDataSources() { 29 | final KiwiContainer container = KiwiContainer(); 30 | container.registerSingleton( 31 | (c) => TodoRemoteDatasource(httpClient: c())); 32 | } 33 | 34 | void _configureLocalDataSources() { 35 | final KiwiContainer container = KiwiContainer(); 36 | container.registerSingleton((c) => TodoLocalDatasource()); 37 | } 38 | 39 | void _configureCommon() { 40 | final KiwiContainer container = KiwiContainer(); 41 | container.registerSingleton((c) => HttpClient.setTodoAPIhost()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/common/utils/database_util.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io'; 2 | 3 | import 'package:hive/hive.dart'; 4 | import 'package:path_provider/path_provider.dart'; 5 | import 'package:flutter/material.dart'; 6 | 7 | class DatabaseUtil { 8 | static Future initDatabase() async { 9 | final Directory directory = await getApplicationDocumentsDirectory(); 10 | Hive.init(directory.path); 11 | } 12 | 13 | /// Register our auto generated Adapter to our Hive database 14 | static void registerAdapter(TypeAdapter adapter) { 15 | try { 16 | Hive.registerAdapter(adapter); 17 | } on HiveError catch (error) { 18 | debugPrint(error.toString()); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /lib/common/utils/http_utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:http/http.dart'; 4 | 5 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 6 | import 'package:clean_code_architecture_flutter/common/constants/http_constants.dart'; 7 | import 'package:clean_code_architecture_flutter/common/exceptions/bad_request_exception.dart'; 8 | import 'package:clean_code_architecture_flutter/common/exceptions/forbidden_exception.dart'; 9 | import 'package:clean_code_architecture_flutter/common/exceptions/server_error_exception.dart'; 10 | import 'package:clean_code_architecture_flutter/common/exceptions/unauthorized_exception.dart'; 11 | 12 | class HttpUtil { 13 | static dynamic encodeRequestBody(dynamic data, String contentType) { 14 | return contentType == HttpConstants.jsonContentType 15 | ? utf8.encode(json.encode(data)) 16 | : data; 17 | } 18 | 19 | static String getErroredResult(dynamic result) { 20 | if (result['error'] is String) { 21 | return result['error']; 22 | } else if (result['message'] is String) { 23 | return result['message']; 24 | } 25 | return ExceptionConstants.somethingWentWrong; 26 | } 27 | 28 | static dynamic getResponse(Response response) { 29 | switch (response.statusCode) { 30 | case 200: 31 | case 201: 32 | final responseJson = json.decode(response.body); 33 | return responseJson['data']; 34 | case 204: 35 | return null; 36 | case 400: 37 | throw BadRequestException( 38 | getErroredResult(json.decode(response.body)), 39 | ); 40 | case 401: 41 | throw UnauthorisedException( 42 | getErroredResult(json.decode(response.body)), 43 | ); 44 | case 403: 45 | throw ForbiddenException( 46 | getErroredResult(json.decode(response.body)), 47 | ); 48 | case 500: 49 | 50 | default: 51 | throw ServerErrorException( 52 | getErroredResult(json.decode(response.body)), 53 | ); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /lib/data/datasources/local/local_database/base_local_database.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:hive/hive.dart'; 3 | 4 | /// Abstract class takes 2 types. 5 | /// First datatype is used for storing value. [The key is stricted to be String] 6 | /// Second datatype is the model from which the table is extended from. This is 7 | /// used for a overriden function that takes inout as list of model datatype and 8 | /// converts to list of table datatype to insert into database 9 | /// 10 | /// This class contains basic functions such as 11 | /// get, getAll, insertOrUpdateAll, delete, deleteAll 12 | /// 13 | /// Initiate the class with box name. 14 | /// 15 | /// Every extended class should register the adapters in the constructor. 16 | /// 17 | /// Example: 18 | /// ```dart 19 | /// ContactsLocalDataSource() : super(boxName: 'contacts') { 20 | /// DatabaseUtil.registerAdapter(ContactTableAdapter()); 21 | /// DatabaseUtil.registerAdapter( 22 | /// AccountContactTableAdapter()); 23 | /// } 24 | /// ``` 25 | /// Adapter are the hive table adapter which is used for storing data 26 | /// It is generated code by hive. 27 | /// ContactTable will generate its adapter and by default the name will be 28 | /// ContactTableAdapter 29 | abstract class BaseLocalDataSource { 30 | String _boxName; 31 | Future> boxInstance; 32 | 33 | BaseLocalDataSource({ 34 | @required String boxName, 35 | }) { 36 | _boxName = boxName; 37 | } 38 | 39 | void _init() { 40 | boxInstance = Hive.openBox(_boxName); 41 | } 42 | 43 | Future> get getBoxInstance async => _openBox(); 44 | 45 | Future> getFormattedData(); 46 | 47 | Future insertOrUpdateAll(List todos); 48 | 49 | Future> _openBox() async { 50 | final Box box = await boxInstance; 51 | if (box == null || !box.isOpen) { 52 | _init(); 53 | return boxInstance; 54 | } 55 | return box; 56 | } 57 | 58 | Future get(String key) async { 59 | final Box box = await _openBox(); 60 | return box.get(key); 61 | } 62 | 63 | Future> getAll() async { 64 | final Box box = await _openBox(); 65 | return box.toMap().values.toList(); 66 | } 67 | 68 | Future put(String key, TableType value) async { 69 | final Box box = await _openBox(); 70 | await box.put(key, value); 71 | } 72 | 73 | Future putAll(Map items) async { 74 | final Box box = await _openBox(); 75 | await box.putAll(items); 76 | } 77 | 78 | Future delete(String key) async { 79 | final Box box = await _openBox(); 80 | await box.delete(key); 81 | } 82 | 83 | Future deleteAll() async { 84 | final Box box = await _openBox(); 85 | final List boxKeys = await keys; 86 | await box.deleteAll(boxKeys); 87 | } 88 | 89 | Future> get keys async { 90 | final Box box = await _openBox(); 91 | final List result = box.keys.map((k) => k.toString()).toList(); 92 | return result; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/data/datasources/local/local_database/tables/todo_table.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 2 | import 'package:hive/hive.dart'; 3 | import 'package:clean_code_architecture_flutter/common/constants/local_database_type_constants.dart'; 4 | 5 | part 'todo_table.g.dart'; 6 | 7 | @HiveType(typeId: HiveTypeIdConstants.customerTableId) 8 | class TodoTable extends TodoModel { 9 | @override 10 | @HiveField(1) 11 | String id; 12 | 13 | @override 14 | @HiveField(2) 15 | String description; 16 | 17 | @override 18 | @HiveField(3) 19 | bool completed; 20 | 21 | TodoTable({this.id, this.description, this.completed}) 22 | : super( 23 | id: id, 24 | description: description, 25 | completed: completed, 26 | ); 27 | 28 | factory TodoTable.fromModel(TodoModel model) => TodoTable( 29 | id: model.id, 30 | description: model.description, 31 | completed: model.completed, 32 | ); 33 | 34 | static TodoModel toModel(TodoTable table) => TodoModel( 35 | id: table.id, 36 | description: table.description, 37 | completed: table.completed, 38 | ); 39 | } 40 | -------------------------------------------------------------------------------- /lib/data/datasources/local/local_database/tables/todo_table.g.dart: -------------------------------------------------------------------------------- 1 | // GENERATED CODE - DO NOT MODIFY BY HAND 2 | 3 | part of 'todo_table.dart'; 4 | 5 | // ************************************************************************** 6 | // TypeAdapterGenerator 7 | // ************************************************************************** 8 | 9 | class TodoTableAdapter extends TypeAdapter { 10 | @override 11 | final int typeId = 0; 12 | 13 | @override 14 | TodoTable read(BinaryReader reader) { 15 | final numOfFields = reader.readByte(); 16 | final fields = { 17 | for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), 18 | }; 19 | return TodoTable( 20 | id: fields[1] as String, 21 | description: fields[2] as String, 22 | completed: fields[3] as bool, 23 | ); 24 | } 25 | 26 | @override 27 | void write(BinaryWriter writer, TodoTable obj) { 28 | writer 29 | ..writeByte(3) 30 | ..writeByte(1) 31 | ..write(obj.id) 32 | ..writeByte(2) 33 | ..write(obj.description) 34 | ..writeByte(3) 35 | ..write(obj.completed); 36 | } 37 | 38 | @override 39 | int get hashCode => typeId.hashCode; 40 | 41 | @override 42 | bool operator ==(Object other) => 43 | identical(this, other) || 44 | other is TodoTableAdapter && 45 | runtimeType == other.runtimeType && 46 | typeId == other.typeId; 47 | } 48 | -------------------------------------------------------------------------------- /lib/data/datasources/local/local_database/todo_local_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/constants/local_database_type_constants.dart'; 2 | import 'package:clean_code_architecture_flutter/common/utils/database_util.dart'; 3 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/base_local_database.dart'; 4 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/tables/todo_table.dart'; 5 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 6 | 7 | class TodoLocalDatasource extends BaseLocalDataSource { 8 | TodoLocalDatasource() : super(boxName: HiveTableNameConstants.todoTableName) { 9 | DatabaseUtil.registerAdapter(TodoTableAdapter()); 10 | } 11 | 12 | @override 13 | Future> getFormattedData() async { 14 | final List data = await getAll(); 15 | return data.map(TodoTable.toModel).toList(); 16 | } 17 | 18 | Future insertOrUpdateItem(TodoModel todo) async { 19 | await put(todo.id, TodoTable.fromModel(todo)); 20 | } 21 | 22 | @override 23 | Future insertOrUpdateAll(List todos) async { 24 | final Map todoMap = { 25 | for (var todo in todos) todo.id: TodoTable.fromModel(todo) 26 | }; 27 | await putAll(todoMap); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/data/datasources/remote/constants/todo_remote_datasource_constants.dart: -------------------------------------------------------------------------------- 1 | class TodoEndpoints { 2 | static const String getCreateUpdateDeletePath = '/task'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/data/datasources/remote/todo_remote_datasource.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/http/http_client.dart'; 2 | import 'package:clean_code_architecture_flutter/data/datasources/remote/constants/todo_remote_datasource_constants.dart'; 3 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 4 | 5 | class TodoRemoteDatasource { 6 | final HttpClient httpClient; 7 | TodoRemoteDatasource({this.httpClient}); 8 | 9 | Future> getAll() async { 10 | final List data = 11 | await httpClient.get(TodoEndpoints.getCreateUpdateDeletePath); 12 | return TodoModel.fromJsonList(data); 13 | } 14 | 15 | Future create(TodoModel todo) async { 16 | final Map data = await httpClient.post( 17 | TodoEndpoints.getCreateUpdateDeletePath, 18 | todo.toJson(), 19 | ); 20 | return TodoModel.fromJson(data); 21 | } 22 | 23 | Future update(TodoModel todo) async { 24 | final Map data = await httpClient.put( 25 | '${TodoEndpoints.getCreateUpdateDeletePath}/${todo.id}', 26 | todo.toJson(), 27 | ); 28 | return TodoModel.fromJson(data); 29 | } 30 | 31 | Future delete(String id) async { 32 | final Map data = await httpClient.delete( 33 | '${TodoEndpoints.getCreateUpdateDeletePath}/$id', 34 | ); 35 | return TodoModel.fromJson(data); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/data/models/todo_model.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 2 | 3 | class TodoModel extends TodoEntity { 4 | TodoModel({ 5 | String id, 6 | String description, 7 | bool completed, 8 | }) : super( 9 | id: id, 10 | description: description, 11 | completed: completed, 12 | ); 13 | 14 | factory TodoModel.fromJson(dynamic json) => TodoModel( 15 | id: json['_id'], 16 | description: json['description'], 17 | completed: json['completed'] ?? false, 18 | ); 19 | 20 | static List fromJsonList(List jsonList) => 21 | jsonList.map((json) => TodoModel.fromJson(json)).toList(); 22 | 23 | Map toJson() { 24 | final Map json = {}; 25 | if (completed != null) { 26 | json['completed'] = completed; 27 | } 28 | if (description != null) { 29 | json['description'] = description; 30 | } 31 | return json; 32 | } 33 | 34 | TodoModel.castFromEntity(final TodoEntity todo) 35 | : super( 36 | id: todo.id, 37 | description: todo.description, 38 | completed: todo.completed, 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /lib/data/repositories/todo_repository_impl.dart: -------------------------------------------------------------------------------- 1 | import 'package:pedantic/pedantic.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/todo_local_datasource.dart'; 4 | import 'package:clean_code_architecture_flutter/data/datasources/remote/todo_remote_datasource.dart'; 5 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 6 | import 'package:clean_code_architecture_flutter/domain/repositories/todo_repository.dart'; 7 | 8 | class TodoRepositoryImpl extends TodoRepository { 9 | final TodoRemoteDatasource todoRemoteDatasource; 10 | final TodoLocalDatasource todoLocalDatasource; 11 | 12 | TodoRepositoryImpl({ 13 | this.todoRemoteDatasource, 14 | this.todoLocalDatasource, 15 | }); 16 | 17 | @override 18 | Future> getAll({bool fromLocal}) async { 19 | List todos = await todoLocalDatasource.getFormattedData(); 20 | if (todos.isEmpty || !(fromLocal ?? true)) { 21 | todos = await todoRemoteDatasource.getAll(); 22 | 23 | unawaited(Future.wait( 24 | [ 25 | todoLocalDatasource.deleteAll(), 26 | todoLocalDatasource.insertOrUpdateAll(todos), 27 | ], 28 | )); 29 | } 30 | return todos; 31 | } 32 | 33 | @override 34 | Future create(TodoModel todoModel) async { 35 | final TodoModel todo = await todoRemoteDatasource.create(todoModel); 36 | if (todo != null) { 37 | await todoLocalDatasource.insertOrUpdateItem(todo); 38 | } 39 | return todo; 40 | } 41 | 42 | @override 43 | Future delete(String id) async { 44 | final TodoModel todo = await todoRemoteDatasource.delete(id); 45 | if (todo != null) { 46 | await todoLocalDatasource.delete(id); 47 | } 48 | } 49 | 50 | @override 51 | Future update(TodoModel model) async { 52 | final TodoModel todo = await todoRemoteDatasource.update(model); 53 | if (todo != null) { 54 | await todoLocalDatasource.insertOrUpdateItem(todo); 55 | } 56 | return todo; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/domain/entities/todo_entity.dart: -------------------------------------------------------------------------------- 1 | class TodoEntity { 2 | String id; 3 | String description; 4 | bool completed; 5 | 6 | TodoEntity({ 7 | this.id, 8 | this.description, 9 | this.completed, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /lib/domain/repositories/todo_repository.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 2 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 3 | 4 | abstract class TodoRepository { 5 | Future> getAll({bool fromLocal}); 6 | 7 | Future create(TodoModel todo); 8 | 9 | Future update(TodoModel todo); 10 | 11 | Future delete(String id); 12 | } 13 | -------------------------------------------------------------------------------- /lib/domain/usescases/todo_usecase.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 2 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 3 | import 'package:clean_code_architecture_flutter/domain/repositories/todo_repository.dart'; 4 | 5 | class TodoUsecase { 6 | final TodoRepository todoRepository; 7 | 8 | TodoUsecase({this.todoRepository}); 9 | 10 | Future> getAll({bool fromLocal}) async => 11 | todoRepository.getAll(fromLocal: fromLocal); 12 | 13 | Future create(TodoEntity todo) async { 14 | final TodoModel todoModel = TodoModel.castFromEntity(todo); 15 | return todoRepository.create(todoModel); 16 | } 17 | 18 | Future update(TodoEntity todo) async { 19 | final TodoModel todoModel = TodoModel.castFromEntity(todo); 20 | return todoRepository.update(todoModel); 21 | } 22 | 23 | Future delete(String id) async => todoRepository.delete(id); 24 | } 25 | -------------------------------------------------------------------------------- /lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/common/injector/injector_config.dart'; 4 | import 'package:clean_code_architecture_flutter/common/utils/database_util.dart'; 5 | import 'package:clean_code_architecture_flutter/presentation/app.dart'; 6 | 7 | Future main() async { 8 | WidgetsFlutterBinding.ensureInitialized(); 9 | InjectorConfig.setup(); 10 | await DatabaseUtil.initDatabase(); 11 | 12 | runApp(App()); 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/common/constants/route_constants.dart'; 4 | import 'package:clean_code_architecture_flutter/presentation/routes.dart'; 5 | import 'package:clean_code_architecture_flutter/presentation/themes/theme_data.dart'; 6 | 7 | class App extends StatelessWidget { 8 | @override 9 | Widget build(BuildContext context) { 10 | return MaterialApp( 11 | title: 'Todo', 12 | debugShowCheckedModeBanner: false, 13 | theme: appTheme(context), 14 | routes: Routes.getAll(), 15 | initialRoute: RouteList.dashbaord, 16 | ); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lib/presentation/journey/dashboard/dashboard_constants.dart: -------------------------------------------------------------------------------- 1 | class DashboardConstants { 2 | static const elevatedButtonKey = 'go_to_todo_list'; 3 | } 4 | -------------------------------------------------------------------------------- /lib/presentation/journey/dashboard/dashboard_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/common/constants/route_constants.dart'; 4 | import 'package:clean_code_architecture_flutter/presentation/journey/dashboard/dashboard_screen.dart'; 5 | 6 | class DashboardRoutes { 7 | static Map getAll() { 8 | return { 9 | RouteList.dashbaord: (context) => DashboardScreen(), 10 | }; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /lib/presentation/journey/dashboard/dashboard_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:clean_code_architecture_flutter/common/constants/route_constants.dart'; 3 | import 'package:clean_code_architecture_flutter/presentation/journey/dashboard/dashboard_constants.dart'; 4 | 5 | class DashboardScreen extends StatelessWidget { 6 | @override 7 | Widget build(BuildContext context) => Scaffold( 8 | appBar: AppBar( 9 | title: const Text('TO-DO'), 10 | centerTitle: true, 11 | ), 12 | body: Center( 13 | child: Column( 14 | mainAxisAlignment: MainAxisAlignment.center, 15 | children: [ 16 | Text( 17 | 'Welcome', 18 | style: Theme.of(context).textTheme.headline3, 19 | ), 20 | ElevatedButton( 21 | key: const ValueKey(DashboardConstants.elevatedButtonKey), 22 | onPressed: () { 23 | Navigator.of(context).pushNamed(RouteList.todoList); 24 | }, 25 | child: Text( 26 | 'Go', 27 | style: Theme.of(context).textTheme.button, 28 | ), 29 | ) 30 | ], 31 | ), 32 | ), 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/bloc/todo_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_bloc/flutter_bloc.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 4 | import 'package:clean_code_architecture_flutter/domain/usescases/todo_usecase.dart'; 5 | 6 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 7 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_state.dart'; 8 | 9 | class TodoBloc extends Bloc { 10 | final TodoUsecase todoUsecase; 11 | 12 | TodoBloc({this.todoUsecase}); 13 | 14 | @override 15 | TodoState get initialState => InitialTodos(); 16 | 17 | @override 18 | Stream mapEventToState(TodoEvent event) async* { 19 | switch (event.runtimeType) { 20 | case FetchTodos: 21 | yield* _mapFetchTodoState(event); 22 | break; 23 | case AddTodo: 24 | yield* _mapAddTodoState(event); 25 | break; 26 | case UpdateTodo: 27 | yield* _mapUpdateTodoState(event); 28 | break; 29 | case DeleteTodo: 30 | yield* _mapDeleteTodoState(event); 31 | break; 32 | default: 33 | } 34 | } 35 | 36 | Stream _mapFetchTodoState(FetchTodos event) async* { 37 | yield LoadingTodos(todos: state.todos); 38 | try { 39 | final todos = await todoUsecase.getAll(fromLocal: event.fromLocal); 40 | yield LoadedTodos(todos: todos); 41 | } catch (e) { 42 | yield ErrorTodos(); 43 | } 44 | } 45 | 46 | Stream _mapAddTodoState(AddTodo event) async* { 47 | final todos = state.todos; 48 | yield LoadingTodos(todos: todos); 49 | try { 50 | final todo = TodoEntity(description: event.description); 51 | final updatedTodo = await todoUsecase.create(todo); 52 | todos.add(updatedTodo); 53 | yield LoadedTodos(todos: todos); 54 | } catch (e) { 55 | yield ErrorTodos(); 56 | } 57 | } 58 | 59 | Stream _mapUpdateTodoState(UpdateTodo event) async* { 60 | final todos = state.todos; 61 | yield LoadingTodos(todos: todos); 62 | try { 63 | final todo = todos.singleWhere((TodoEntity todo) => todo.id == event.id); 64 | todo.completed = !todo.completed; 65 | final updatedTodo = await todoUsecase.update(todo); 66 | todos[todos.indexWhere((TodoEntity todo) => todo.id == updatedTodo.id)] = 67 | updatedTodo; 68 | yield LoadedTodos(todos: todos); 69 | } catch (e) { 70 | yield ErrorTodos(); 71 | } 72 | } 73 | 74 | Stream _mapDeleteTodoState(DeleteTodo event) async* { 75 | final todos = state.todos; 76 | yield LoadingTodos(todos: todos); 77 | try { 78 | await todoUsecase.delete(event.id); 79 | todos.removeWhere((TodoEntity todo) => todo.id == event.id); 80 | yield LoadedTodos(todos: todos); 81 | } catch (e) { 82 | yield ErrorTodos(); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/bloc/todo_event.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart'; 3 | 4 | @immutable 5 | abstract class TodoEvent extends Equatable {} 6 | 7 | class FetchTodos extends TodoEvent { 8 | final bool fromLocal; 9 | FetchTodos({this.fromLocal}); 10 | 11 | @override 12 | List get props => [fromLocal]; 13 | } 14 | 15 | class AddTodo extends TodoEvent { 16 | final String description; 17 | AddTodo({this.description}); 18 | 19 | @override 20 | List get props => [description]; 21 | } 22 | 23 | class UpdateTodo extends TodoEvent { 24 | final String id; 25 | UpdateTodo({this.id}); 26 | 27 | @override 28 | List get props => [id]; 29 | } 30 | 31 | class DeleteTodo extends TodoEvent { 32 | final String id; 33 | DeleteTodo({this.id}); 34 | 35 | @override 36 | List get props => [id]; 37 | } 38 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/bloc/todo_state.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 3 | 4 | @immutable 5 | abstract class TodoState { 6 | final List todos; 7 | final bool loading; 8 | TodoState({this.todos, this.loading}); 9 | } 10 | 11 | class InitialTodos extends TodoState { 12 | InitialTodos() : super(todos: [], loading: false); 13 | } 14 | 15 | class LoadingTodos extends TodoState { 16 | LoadingTodos({List todos}) : super(loading: true); 17 | } 18 | 19 | class LoadedTodos extends TodoState { 20 | LoadedTodos({List todos}) : super(todos: todos, loading: false); 21 | } 22 | 23 | class ErrorTodos extends TodoState { 24 | ErrorTodos() : super(loading: false); 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/create_todo/create_todo_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class CreateTodoConstants { 4 | static const descriptionFieldKey = ValueKey('description_text'); 5 | static const createTodoButton = ValueKey('create_todo_button'); 6 | } 7 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/create_todo/create_todo_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/create_todo/create_todo_constants.dart'; 3 | 4 | class CreateTodoScreen extends StatefulWidget { 5 | final Function onCreate; 6 | 7 | CreateTodoScreen({ 8 | Key key, 9 | this.onCreate, 10 | }) : super(key: key); 11 | 12 | @override 13 | _CreateTodoScreenState createState() => _CreateTodoScreenState(); 14 | } 15 | 16 | class _CreateTodoScreenState extends State { 17 | final descriptionController = TextEditingController(); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | } 23 | 24 | @override 25 | void dispose() { 26 | super.dispose(); 27 | } 28 | 29 | @override 30 | Widget build(BuildContext buildContext) => Scaffold( 31 | appBar: AppBar( 32 | title: const Text('Create Todo'), 33 | centerTitle: true, 34 | ), 35 | body: Center( 36 | child: Column( 37 | children: [ 38 | SizedBox( 39 | width: 200, 40 | child: TextField( 41 | key: CreateTodoConstants.descriptionFieldKey, 42 | controller: descriptionController, 43 | decoration: const InputDecoration(labelText: 'Description'), 44 | ), 45 | ), 46 | const SizedBox(height: 20), 47 | RaisedButton( 48 | key: CreateTodoConstants.createTodoButton, 49 | onPressed: () { 50 | widget.onCreate(descriptionController.text); 51 | }, 52 | child: const Text('Create'), 53 | ) 54 | ], 55 | ), 56 | ), 57 | ); 58 | } 59 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/todo_list/todo_list_screen.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_bloc/flutter_bloc.dart'; 3 | 4 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/widgets/todo_list_constants.dart'; 5 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 6 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 7 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/widgets/todo_item.dart'; 8 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 9 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 10 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_state.dart'; 11 | import 'package:clean_code_architecture_flutter/common/constants/route_constants.dart'; 12 | 13 | class TodoListScreen extends StatefulWidget { 14 | TodoListScreen({ 15 | Key key, 16 | }) : super(key: key); 17 | 18 | @override 19 | _TodoListScreenState createState() => _TodoListScreenState(); 20 | } 21 | 22 | class _TodoListScreenState extends State { 23 | // ignore: close_sinks 24 | TodoBloc todoBloc; 25 | 26 | @override 27 | void initState() { 28 | super.initState(); 29 | todoBloc = Injector.resolve()..add(FetchTodos()); 30 | } 31 | 32 | @override 33 | Widget build(BuildContext context) => Scaffold( 34 | appBar: AppBar( 35 | title: const Text('TODOS'), 36 | centerTitle: true, 37 | actions: [ 38 | IconButton( 39 | key: TodoListConstants.createTodoIcon, 40 | icon: const Icon(Icons.add), 41 | onPressed: () => 42 | Navigator.of(context).pushNamed(RouteList.createTodo), 43 | ) 44 | ], 45 | ), 46 | body: BlocBuilder( 47 | bloc: todoBloc, 48 | builder: (context, todoState) { 49 | return _mapStateToWidget(todoState); 50 | }, 51 | ), 52 | ); 53 | 54 | Widget _mapStateToWidget(TodoState todoState) { 55 | switch (todoState.runtimeType) { 56 | case LoadingTodos: 57 | return const Center( 58 | child: CircularProgressIndicator( 59 | backgroundColor: Colors.transparent, 60 | ), 61 | ); 62 | break; 63 | case LoadedTodos: 64 | final List todos = todoState.todos; 65 | if (todos == null || todos.isEmpty) { 66 | return Center( 67 | child: RaisedButton( 68 | key: TodoListConstants.createTodoButton, 69 | onPressed: () { 70 | Navigator.of(context).pushNamed(RouteList.createTodo); 71 | }, 72 | child: const Text('Add Todo'), 73 | ), 74 | ); 75 | } 76 | return _todoList(todos); 77 | break; 78 | case ErrorTodos: 79 | return const Center( 80 | child: Text('Errored'), 81 | ); 82 | break; 83 | default: 84 | return Container(); 85 | } 86 | } 87 | 88 | Widget _todoList(List todos) { 89 | return RefreshIndicator( 90 | onRefresh: () async { 91 | todoBloc.add(FetchTodos(fromLocal: false)); 92 | }, 93 | child: ListView.separated( 94 | itemCount: todos.length ?? 0, 95 | itemBuilder: (_, int index) => TodoItem( 96 | key: ValueKey('${todos[index].id}_item'), 97 | todo: todos[index], 98 | onUpdate: () => todoBloc 99 | ..add( 100 | UpdateTodo( 101 | id: todos[index].id, 102 | ), 103 | ), 104 | onDelete: () => todoBloc..add(DeleteTodo(id: todos[index].id)), 105 | ), 106 | separatorBuilder: (_, __) => const Divider(height: 1), 107 | ), 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/todo_list/widgets/todo_item.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 4 | 5 | class TodoItem extends StatelessWidget { 6 | final TodoEntity todo; 7 | final Function onUpdate; 8 | final Function onDelete; 9 | 10 | TodoItem({Key key, this.todo, this.onUpdate, this.onDelete}) 11 | : super(key: key); 12 | 13 | @override 14 | Widget build(BuildContext context) { 15 | return Dismissible( 16 | key: ValueKey(todo.id), 17 | direction: DismissDirection.endToStart, 18 | background: Container( 19 | color: Colors.red, 20 | child: const Align( 21 | alignment: Alignment.centerRight, 22 | child: Icon( 23 | Icons.delete, 24 | color: Colors.white, 25 | ), 26 | ), 27 | ), 28 | onDismissed: (DismissDirection direction) { 29 | if (direction == DismissDirection.endToStart) { 30 | onDelete(); 31 | } 32 | }, 33 | child: ListTile( 34 | title: Text( 35 | todo.description, 36 | style: todo.completed 37 | ? Theme.of(context) 38 | .textTheme 39 | .bodyText1 40 | .copyWith(decoration: TextDecoration.lineThrough) 41 | : Theme.of(context).textTheme.bodyText1, 42 | ), 43 | leading: IconButton( 44 | key: ValueKey('${todo.id}_icon'), 45 | onPressed: onUpdate, 46 | icon: Icon( 47 | todo.completed 48 | ? Icons.check_circle_rounded 49 | : Icons.check_circle_outline, 50 | ), 51 | ), 52 | ), 53 | ); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/todo_list/widgets/todo_list_constants.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class TodoListConstants { 4 | static const createTodoButton = ValueKey('create_todo_button'); 5 | static const createTodoIcon = ValueKey('create_todo_icon'); 6 | } 7 | -------------------------------------------------------------------------------- /lib/presentation/journey/todo/todo_routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 4 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 5 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 6 | 7 | import 'package:clean_code_architecture_flutter/common/constants/route_constants.dart'; 8 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/create_todo/create_todo_screen.dart'; 9 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/todo_list_screen.dart'; 10 | 11 | class TodoRoutes { 12 | static TodoBloc get _getTodoBloc => Injector.resolve(); 13 | 14 | static Map getAll() { 15 | return { 16 | RouteList.todoList: (context) => TodoListScreen(), 17 | RouteList.createTodo: (context) => CreateTodoScreen( 18 | onCreate: (String description) { 19 | _getTodoBloc.add(AddTodo(description: description)); 20 | Navigator.of(context).pop(); 21 | }, 22 | ), 23 | }; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/routes.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_routes.dart'; 4 | import 'package:clean_code_architecture_flutter/presentation/journey/dashboard/dashboard_routes.dart'; 5 | 6 | class Routes { 7 | static Map _getCombinedRoutes() => { 8 | ...DashboardRoutes.getAll(), 9 | ...TodoRoutes.getAll(), 10 | }; 11 | 12 | static Map getAll() => _getCombinedRoutes(); 13 | } 14 | -------------------------------------------------------------------------------- /lib/presentation/themes/custom_icons.dart: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/lib/presentation/themes/custom_icons.dart -------------------------------------------------------------------------------- /lib/presentation/themes/theme_colors.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class AppColor { 4 | static const Color primaryColor = Color(0xfffdaf27); 5 | static const Color white = Colors.white; 6 | static const Color black = Colors.black; 7 | } 8 | -------------------------------------------------------------------------------- /lib/presentation/themes/theme_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/presentation/themes/theme_colors.dart'; 2 | import 'package:clean_code_architecture_flutter/presentation/themes/theme_text.dart'; 3 | import 'package:flutter/material.dart'; 4 | 5 | ThemeData appTheme(BuildContext context) { 6 | return ThemeData( 7 | fontFamily: 'TTCommons', 8 | primaryColor: AppColor.primaryColor, 9 | textTheme: ThemeText.getDefaultTextTheme(), 10 | buttonTheme: ButtonThemeData( 11 | buttonColor: AppColor.primaryColor, 12 | shape: RoundedRectangleBorder( 13 | borderRadius: BorderRadius.circular(5.0), 14 | ), 15 | ), 16 | scaffoldBackgroundColor: AppColor.white, 17 | toggleableActiveColor: AppColor.primaryColor, 18 | elevatedButtonTheme: ElevatedButtonThemeData( 19 | style: ElevatedButton.styleFrom( 20 | onPrimary: AppColor.black, 21 | primary: AppColor.primaryColor, 22 | ), 23 | ), 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /lib/presentation/themes/theme_text.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ThemeText { 4 | // Default Text Style Following Guideline 5 | static const headline1 = TextStyle( 6 | fontSize: 38, 7 | fontWeight: FontWeight.bold, 8 | ); 9 | static const headline2 = TextStyle( 10 | fontSize: 32, 11 | fontWeight: FontWeight.normal, 12 | ); 13 | static const headline3 = TextStyle( 14 | fontSize: 26, 15 | fontWeight: FontWeight.normal, 16 | ); 17 | static const headline4 = TextStyle( 18 | fontSize: 22, 19 | fontWeight: FontWeight.normal, 20 | ); 21 | static const headline5 = TextStyle( 22 | fontSize: 20.0, 23 | fontWeight: FontWeight.normal, 24 | ); 25 | static const headline6 = TextStyle( 26 | fontSize: 19, 27 | fontWeight: FontWeight.normal, 28 | ); 29 | static const bodyText2 = TextStyle( 30 | fontSize: 18, 31 | fontWeight: FontWeight.normal, 32 | ); 33 | static const TextStyle bodyText1 = TextStyle( 34 | fontSize: 18, 35 | fontWeight: FontWeight.w600, 36 | ); 37 | static const TextStyle subtitle1 = TextStyle( 38 | fontSize: 17, 39 | fontWeight: FontWeight.normal, 40 | ); 41 | static const caption = TextStyle( 42 | fontSize: 15, 43 | fontWeight: FontWeight.normal, 44 | ); 45 | static const overline = TextStyle( 46 | fontSize: 14, 47 | fontWeight: FontWeight.normal, 48 | letterSpacing: 0.4, 49 | ); 50 | static const button = TextStyle( 51 | fontSize: 19, 52 | fontWeight: FontWeight.normal, 53 | ); 54 | 55 | static TextTheme getDefaultTextTheme() => const TextTheme( 56 | headline1: ThemeText.headline1, 57 | headline2: ThemeText.headline2, 58 | headline3: ThemeText.headline3, 59 | headline4: ThemeText.headline4, 60 | headline5: ThemeText.headline5, 61 | headline6: ThemeText.headline6, 62 | bodyText2: ThemeText.bodyText2, 63 | bodyText1: ThemeText.bodyText1, 64 | subtitle1: ThemeText.subtitle1, 65 | caption: ThemeText.caption, 66 | overline: ThemeText.overline, 67 | button: ThemeText.button, 68 | ); 69 | 70 | static const TextStyle buttonText = TextStyle( 71 | fontSize: 19, 72 | fontWeight: FontWeight.w600, 73 | wordSpacing: 0, 74 | ); 75 | } 76 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: clean_code_architecture_flutter 2 | description: A new Flutter project. 3 | 4 | publish_to: "none" # Remove this line if you wish to publish to pub.dev 5 | 6 | version: 1.0.0+1 7 | 8 | environment: 9 | sdk: ">=2.7.0 <3.0.0" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | flutter_bloc: ^3.2.0 15 | cupertino_icons: ^1.0.0 16 | http: ^0.12.0 17 | kiwi: 2.0.0 18 | hive: ^1.4.0+1 19 | path_provider: ^1.5.1 20 | mockito: 4.1.1 # Testing 21 | equatable: ^1.0.1 22 | 23 | dev_dependencies: 24 | bloc_test: ^3.1.0 25 | flutter_test: # Testing 26 | sdk: flutter 27 | kiwi_generator: ^2.0.0 28 | hive_generator: 0.8.1 29 | build_runner: 1.10.3 30 | analyzer: 0.40.4 31 | 32 | flutter: 33 | uses-material-design: true 34 | # assets: 35 | # - images/a_dot_burr.jpeg 36 | # - images/a_dot_ham.jpeg 37 | # example: 38 | # fonts: 39 | # - family: Schyler 40 | # fonts: 41 | # - asset: fonts/Schyler-Regular.ttf 42 | # - asset: fonts/Schyler-Italic.ttf 43 | # style: italic 44 | # - family: Trajan Pro 45 | # fonts: 46 | # - asset: fonts/TrajanPro.ttf 47 | # - asset: fonts/TrajanPro_Bold.ttf 48 | # weight: 700 49 | -------------------------------------------------------------------------------- /test/__setup__/base_test_bloc.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc/bloc.dart'; 2 | import 'package:bloc_test/bloc_test.dart'; 3 | import 'package:meta/meta.dart'; 4 | 5 | class BaseBlocTest, E, S> { 6 | BaseBlocTest(); 7 | void test(String description, 8 | {B Function() build, 9 | Future Function(B bloc) act, 10 | Duration wait, 11 | Future Function() verify, 12 | @required Iterable expect}) { 13 | return blocTest( 14 | description, 15 | build: build, 16 | wait: wait, 17 | verify: verify, 18 | act: act, 19 | expect: expect, 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /test/__setup__/navigation_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | 4 | class MockNavigatorObserver extends Mock implements NavigatorObserver {} 5 | 6 | class MockFunction extends Mock { 7 | void callback(); 8 | void callbackWithString(String description); 9 | } 10 | -------------------------------------------------------------------------------- /test/__setup__/path_provider_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | 3 | void setupPathProviderMock(String baseLocation) { 4 | const MethodChannel('plugins.flutter.io/path_provider') 5 | .setMockMethodCallHandler( 6 | (MethodCall methodCall) async { 7 | return Future.value(baseLocation); 8 | }, 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /test/__setup__/wrapper.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:clean_code_architecture_flutter/presentation/routes.dart'; 3 | 4 | Widget wrapWidget(Widget widget, NavigatorObserver observer) { 5 | final routes = Routes.getAll()..remove('/'); 6 | return MaterialApp( 7 | home: widget, 8 | routes: routes, 9 | navigatorObservers: [observer], 10 | ); 11 | } 12 | 13 | Widget wrapWidgetWithScaffold(Widget widget, NavigatorObserver observer) { 14 | final routes = Routes.getAll()..remove('/'); 15 | return MaterialApp( 16 | home: Scaffold(body: widget), 17 | routes: routes, 18 | navigatorObservers: [observer], 19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /test/common/exceptions/bad_request_exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 4 | import 'package:clean_code_architecture_flutter/common/exceptions/bad_request_exception.dart'; 5 | 6 | void main() { 7 | test('BadRequestException', () { 8 | // When 9 | final result = BadRequestException('message'); 10 | // Then 11 | expect(result.message, 'message'); 12 | expect(result.code, ExceptionConstants.badRequest); 13 | }); 14 | } 15 | -------------------------------------------------------------------------------- /test/common/exceptions/forbidden_exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 3 | import 'package:clean_code_architecture_flutter/common/exceptions/forbidden_exception.dart'; 4 | 5 | void main() { 6 | test('BadRequestException', () { 7 | // When 8 | final result = ForbiddenException('message'); 9 | // Then 10 | expect(result.message, 'message'); 11 | expect(result.code, ExceptionConstants.forbidden); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/common/exceptions/server_error_exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 3 | import 'package:clean_code_architecture_flutter/common/exceptions/server_error_exception.dart'; 4 | 5 | void main() { 6 | test('ServerErrorException', () { 7 | // When 8 | final result = ServerErrorException('message'); 9 | // Then 10 | expect(result.message, 'message'); 11 | expect(result.code, ExceptionConstants.internalServerError); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/common/exceptions/unauthorized_exception_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:clean_code_architecture_flutter/common/constants/exception_constants.dart'; 3 | import 'package:clean_code_architecture_flutter/common/exceptions/unauthorized_exception.dart'; 4 | 5 | void main() { 6 | test('BadRequestException', () { 7 | // When 8 | final result = UnauthorisedException('message'); 9 | // Then 10 | expect(result.message, 'message'); 11 | expect(result.code, ExceptionConstants.unauthorized); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/common/http/__mock__/http_client_data.dart: -------------------------------------------------------------------------------- 1 | const Map jsonData = { 2 | 'data': { 3 | 'id': '1', 4 | } 5 | }; 6 | 7 | const Map extractedJsonData = { 8 | 'id': '1', 9 | }; 10 | 11 | const String jsonString = '{"data":{"id":"1"}}'; 12 | -------------------------------------------------------------------------------- /test/common/http/__mock__/http_client_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/mockito.dart'; 2 | import 'package:http/http.dart' as http; 3 | 4 | class MockClient extends Mock implements http.Client {} 5 | -------------------------------------------------------------------------------- /test/common/http/http_client_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:http/http.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | 6 | import 'package:clean_code_architecture_flutter/common/http/http_client.dart'; 7 | import 'package:clean_code_architecture_flutter/common/configs/configuration.dart'; 8 | 9 | import '__mock__/http_client_data.dart'; 10 | import '__mock__/http_client_mock.dart'; 11 | 12 | void main() { 13 | WidgetsFlutterBinding.ensureInitialized(); 14 | 15 | HttpClient httpClient; 16 | MockClient client; 17 | 18 | setUpAll(() async { 19 | client = MockClient(); 20 | httpClient = HttpClient(host: Configuration.host, client: client); 21 | }); 22 | 23 | tearDownAll(() {}); 24 | 25 | group('Http client', () { 26 | test('Should call get', () async { 27 | //Given 28 | when(client.get( 29 | Uri.parse('${Configuration.host}/something'), 30 | headers: anyNamed('headers'), 31 | )).thenAnswer((_) async => Response(jsonString, 200)); 32 | 33 | // when 34 | final result = await httpClient.get('/something'); 35 | 36 | // Then 37 | expect(result, extractedJsonData); 38 | }); 39 | 40 | test('Should call get for image', () async { 41 | //Given 42 | when(client.get( 43 | '/image.png', 44 | )).thenAnswer((_) async => Response.bytes([], 200)); 45 | 46 | // when 47 | final result = await httpClient.getImage('/image.png'); 48 | 49 | // Then 50 | expect(result.runtimeType, Response); 51 | }); 52 | 53 | test('Should call post', () async { 54 | //Given 55 | when(client.post( 56 | Uri.parse('${Configuration.host}/something'), 57 | body: anyNamed('body'), 58 | headers: anyNamed('headers'), 59 | )).thenAnswer((_) async => Response(jsonString, 201)); 60 | 61 | // when 62 | final result = await httpClient.post('/something', extractedJsonData); 63 | 64 | // Then 65 | expect(result, extractedJsonData); 66 | }); 67 | 68 | test('Should call put', () async { 69 | //Given 70 | when(client.put( 71 | Uri.parse('${Configuration.host}/something'), 72 | body: anyNamed('body'), 73 | headers: anyNamed('headers'), 74 | )).thenAnswer((_) async => Response('', 204)); 75 | 76 | // when 77 | final result = await httpClient.put('/something', extractedJsonData); 78 | 79 | // Then 80 | expect(result, null); 81 | }); 82 | 83 | test('Should call put', () async { 84 | //Given 85 | when(client.patch( 86 | Uri.parse('${Configuration.host}/something'), 87 | body: anyNamed('body'), 88 | headers: anyNamed('headers'), 89 | )).thenAnswer((_) async => Response('', 204)); 90 | 91 | // when 92 | final result = await httpClient.patch('/something', extractedJsonData); 93 | 94 | // Then 95 | expect(result, null); 96 | }); 97 | 98 | test('Should call delete', () async { 99 | //Given 100 | when(client.delete( 101 | Uri.parse('${Configuration.host}/something/1'), 102 | headers: anyNamed('headers'), 103 | )).thenAnswer((_) async => Response(jsonString, 200)); 104 | 105 | // when 106 | final result = await httpClient.delete('/something/1'); 107 | 108 | // Then 109 | expect(result, extractedJsonData); 110 | }); 111 | }); 112 | } 113 | -------------------------------------------------------------------------------- /test/common/utils/__mock__/sample_adapter.dart: -------------------------------------------------------------------------------- 1 | import 'package:hive/hive.dart'; 2 | 3 | import 'sample_table.dart'; 4 | 5 | class SampleTableAdapter extends TypeAdapter { 6 | @override 7 | int get typeId => 0; 8 | 9 | @override 10 | void write(BinaryWriter writer, SampleTable obj) {} 11 | 12 | @override 13 | SampleTable read(BinaryReader reader) { 14 | return null; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test/common/utils/__mock__/sample_table.dart: -------------------------------------------------------------------------------- 1 | class SampleTable { 2 | String name; 3 | SampleTable({this.name}); 4 | } 5 | -------------------------------------------------------------------------------- /test/common/utils/__mock__/sample_table_mock.dart: -------------------------------------------------------------------------------- 1 | import 'sample_table.dart'; 2 | 3 | SampleTable sampleTable1 = SampleTable(name: 'key1'); 4 | SampleTable sampleTable2 = SampleTable(name: 'key2'); 5 | -------------------------------------------------------------------------------- /test/common/utils/database_utils_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | 3 | import 'package:hive/hive.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:clean_code_architecture_flutter/common/utils/database_util.dart'; 6 | import '../../__setup__/path_provider_mock.dart'; 7 | import '__mock__/sample_adapter.dart'; 8 | import '__mock__/sample_table_mock.dart'; 9 | 10 | /// Please maintain the test in order to test HIVE 11 | void main() { 12 | WidgetsFlutterBinding.ensureInitialized(); 13 | group('Database util:', () { 14 | const String boxName = 'database_util_test'; 15 | const baseLocation = './local_database_test_files/'; 16 | 17 | group('initDatabase:', () { 18 | test('should insert item when Hive is initiated', () async { 19 | // Given 20 | const String data = 'a'; 21 | setupPathProviderMock(baseLocation); 22 | await DatabaseUtil.initDatabase(); 23 | 24 | // When 25 | final box = await Hive.openBox(boxName); 26 | await box.add(data); 27 | 28 | // Then 29 | expect(box.toMap().values.toList(), [data]); 30 | await Hive.box(boxName).clear(); 31 | }); 32 | }); 33 | 34 | group('registerAdapter:', () { 35 | setUp(() { 36 | Hive.init(baseLocation); 37 | }); 38 | 39 | tearDown(() { 40 | Hive.box(boxName).clear(); 41 | }); 42 | test( 43 | 'should throw error while inserting a record' 44 | 'when adapter is not registered', () async { 45 | // Given 46 | Error error; 47 | 48 | // When 49 | try { 50 | final Box box = await Hive.openBox(boxName); 51 | await box.add(sampleTable1); 52 | } catch (e) { 53 | error = e; 54 | } 55 | 56 | // Then 57 | expect(error.runtimeType, HiveError); 58 | expect(error.toString().contains('register an adapter'), true); 59 | }); 60 | 61 | test('should insert record when adapter is registered', () async { 62 | // Given 63 | final Box box = await Hive.openBox(boxName); 64 | 65 | // When 66 | DatabaseUtil.registerAdapter(SampleTableAdapter()); 67 | await box.add(sampleTable1); 68 | 69 | // Then 70 | expect(box.toMap().values.toList(), [sampleTable1]); 71 | }); 72 | }); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /test/common/utils/http_util_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:http/http.dart'; 4 | import 'package:mockito/mockito.dart'; 5 | 6 | import 'package:clean_code_architecture_flutter/common/constants/http_constants.dart'; 7 | import 'package:clean_code_architecture_flutter/common/exceptions/bad_request_exception.dart'; 8 | import 'package:clean_code_architecture_flutter/common/exceptions/forbidden_exception.dart'; 9 | import 'package:clean_code_architecture_flutter/common/exceptions/server_error_exception.dart'; 10 | import 'package:clean_code_architecture_flutter/common/exceptions/unauthorized_exception.dart'; 11 | import 'package:clean_code_architecture_flutter/common/utils/http_utils.dart'; 12 | 13 | class Request extends BaseRequest { 14 | Request(String method, Uri url) : super(method, url); 15 | } 16 | 17 | class RequestFn extends Mock implements Function { 18 | Future withBody( 19 | dynamic url, { 20 | Map headers = const {}, 21 | dynamic body, 22 | }); 23 | 24 | Future withoutBody( 25 | dynamic url, { 26 | Map headers = const {}, 27 | }); 28 | } 29 | 30 | void main() { 31 | group('HttpUtil', () { 32 | group('encodeRequestBody', () { 33 | test( 34 | 'should encode the data in json format ' 35 | 'when content type is json', 36 | () { 37 | // given 38 | final Map data = {'k1': 'v1'}; 39 | 40 | // when 41 | final dynamic result = 42 | HttpUtil.encodeRequestBody(data, HttpConstants.jsonContentType); 43 | final dynamic decoded = json.decode(utf8.decode(result)); 44 | 45 | // then 46 | expect(decoded['k1'], 'v1'); 47 | }, 48 | ); 49 | 50 | test( 51 | 'should not encode to any type when content type is not json', 52 | () { 53 | // given 54 | final Map data = {'k1': 'v1'}; 55 | 56 | // when 57 | final dynamic result = 58 | HttpUtil.encodeRequestBody(data, 'some-random-type'); 59 | 60 | // then 61 | expect(result['k1'], 'v1'); 62 | }, 63 | ); 64 | }); 65 | 66 | group('getResponse', () { 67 | const jsonString = '{"data": "v1"}'; 68 | group('when 200 status code', () { 69 | test('should decode json and return body', () { 70 | // given 71 | final Response response = Response(jsonString, 200); 72 | 73 | // when 74 | final dynamic result = HttpUtil.getResponse(response); 75 | 76 | // then 77 | expect(result, 'v1'); 78 | }); 79 | }); 80 | 81 | group('when 201 status code', () { 82 | test('should decode json and return body', () { 83 | // given 84 | final Response response = Response(jsonString, 201); 85 | 86 | // when 87 | final dynamic result = HttpUtil.getResponse(response); 88 | 89 | // then 90 | expect(result, 'v1'); 91 | }); 92 | }); 93 | group('when 204 status code', () { 94 | test('should return null in body', () { 95 | // given 96 | final Response response = Response(jsonString, 204); 97 | 98 | // when 99 | final dynamic result = HttpUtil.getResponse( 100 | response, 101 | ); 102 | 103 | // then 104 | expect(result, null); 105 | }); 106 | }); 107 | group('when 400 status code', () { 108 | test('should throw BadRequestException', () { 109 | // given 110 | final Response response = 111 | Response('{"error": {"some": "som2"}}', 400); 112 | 113 | // when 114 | try { 115 | HttpUtil.getResponse(response); 116 | } 117 | // then 118 | catch (exception) { 119 | expect(exception is BadRequestException, true); 120 | } 121 | }); 122 | }); 123 | group('when 401 status code', () { 124 | test('should throw unauthorized exception ', () { 125 | // given 126 | final Response response = Response(jsonString, 401); 127 | 128 | // when 129 | try { 130 | HttpUtil.getResponse(response); 131 | } 132 | // then 133 | catch (exception) { 134 | expect(exception is UnauthorisedException, true); 135 | } 136 | }); 137 | }); 138 | group('when 403 status code', () { 139 | test('should throw ForbiddenException', () { 140 | // given 141 | final Response response = Response(jsonString, 403); 142 | 143 | // when 144 | try { 145 | HttpUtil.getResponse(response); 146 | } 147 | // then 148 | catch (exception) { 149 | expect(exception is ForbiddenException, true); 150 | } 151 | }); 152 | }); 153 | 154 | group('when 500 status code', () { 155 | test('should throw ServerErrorException', () { 156 | // given 157 | final Response response = Response( 158 | '{"error": { "message": "some test", "code": "test" } }', 500); 159 | 160 | // when 161 | try { 162 | HttpUtil.getResponse(response); 163 | } 164 | // then 165 | catch (exception) { 166 | expect(exception is ServerErrorException, true); 167 | } 168 | }); 169 | }); 170 | }); 171 | }); 172 | } 173 | -------------------------------------------------------------------------------- /test/data/datasources/local/__mock__/base_local_datasource_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/utils/database_util.dart'; 2 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/base_local_database.dart'; 3 | 4 | import '../../../../common/utils/__mock__/sample_adapter.dart'; 5 | import '../../../../common/utils/__mock__/sample_table.dart'; 6 | import 'sample_model.dart'; 7 | 8 | class ExtendedLocalDataSource 9 | extends BaseLocalDataSource { 10 | ExtendedLocalDataSource() : super(boxName: 'testBox') { 11 | DatabaseUtil.registerAdapter(SampleTableAdapter()); 12 | } 13 | 14 | @override 15 | Future> getFormattedData() async { 16 | return null; 17 | } 18 | 19 | @override 20 | Future insertOrUpdateAll(List contacts) async {} 21 | } 22 | -------------------------------------------------------------------------------- /test/data/datasources/local/__mock__/sample_model.dart: -------------------------------------------------------------------------------- 1 | class SampleModel { 2 | String name; 3 | SampleModel({this.name}); 4 | } 5 | -------------------------------------------------------------------------------- /test/data/datasources/local/__mock__/todo_local_datasource_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/mockito.dart'; 2 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/todo_local_datasource.dart'; 3 | 4 | class MockTodoLocalDatasource extends Mock implements TodoLocalDatasource {} 5 | -------------------------------------------------------------------------------- /test/data/datasources/local/__mock__/todo_table_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/tables/todo_table.dart'; 2 | 3 | final TodoTable todoTable1 = TodoTable( 4 | id: '1', 5 | description: 'create an app', 6 | completed: false, 7 | ); 8 | -------------------------------------------------------------------------------- /test/data/datasources/local/base_local_datasource_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 2 | import 'package:hive/hive.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import '../../../common/utils/__mock__/sample_table.dart'; 6 | import '../../../common/utils/__mock__/sample_table_mock.dart'; 7 | import '__mock__/base_local_datasource_mock.dart'; 8 | 9 | void main() { 10 | const baseLocation = './local_database_test_files/'; 11 | group('Base local datasource', () { 12 | Hive.init(baseLocation); 13 | ExtendedLocalDataSource extendedLocalDataSource; 14 | final Map items = { 15 | '1': sampleTable1, 16 | '2': sampleTable2, 17 | }; 18 | 19 | group('CRUD function', () { 20 | Box box; 21 | setUp(() async { 22 | extendedLocalDataSource = ExtendedLocalDataSource(); 23 | await extendedLocalDataSource.deleteAll(); 24 | box = await extendedLocalDataSource.boxInstance; 25 | }); 26 | 27 | tearDown(() async { 28 | if (box.isOpen) { 29 | await box.clear(); 30 | } 31 | Injector.container?.clear(); 32 | }); 33 | 34 | test('should return list of items when getAll is called', () async { 35 | // Given 36 | final expectedResult = [sampleTable1, sampleTable2]; 37 | await box.putAll(items); 38 | 39 | // When 40 | final List result = await extendedLocalDataSource.getAll(); 41 | 42 | // Then 43 | expect(result, expectedResult); 44 | }); 45 | 46 | test('should return item when getOne is called when key', () async { 47 | // Given 48 | await box.putAll(items); 49 | 50 | // When 51 | final SampleTable result = await extendedLocalDataSource.get('1'); 52 | 53 | // Then 54 | expect(result, items['1']); 55 | }); 56 | 57 | test('should insert items when putAll is called', () async { 58 | // When 59 | await extendedLocalDataSource.putAll(items); 60 | 61 | // Then 62 | expect(box.toMap(), items); 63 | }); 64 | 65 | test('should delete item when delete is called with key', () async { 66 | // Given 67 | await box.putAll(items); 68 | 69 | // When 70 | await extendedLocalDataSource.delete('1'); 71 | 72 | // Then 73 | expect(box.toMap().values.toList(), [sampleTable2]); 74 | }); 75 | 76 | test('should delete item when deleteAll is called with key', () async { 77 | // Given 78 | await box.putAll(items); 79 | 80 | // When 81 | await extendedLocalDataSource.deleteAll(); 82 | 83 | // Then 84 | expect(box.toMap().values.toList(), []); 85 | }); 86 | 87 | test('should get all keys', () async { 88 | // Given 89 | await box.putAll(items); 90 | 91 | // When 92 | final List keys = await extendedLocalDataSource.keys; 93 | 94 | // Then 95 | expect(keys, items.keys); 96 | }); 97 | 98 | test('should return empty list when box empty', () async { 99 | // Given 100 | 101 | // When 102 | final List keys = await extendedLocalDataSource.keys; 103 | 104 | // Then 105 | expect(keys, isEmpty); 106 | }); 107 | }); 108 | }); 109 | } 110 | -------------------------------------------------------------------------------- /test/data/datasources/local/todo_local_datasource_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:hive/hive.dart'; 3 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 4 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/tables/todo_table.dart'; 5 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/todo_local_datasource.dart'; 6 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 7 | import '__mock__/todo_table_data.dart'; 8 | 9 | void main() { 10 | group('province local datasource', () { 11 | const baseLocation = './local_database_test_files/'; 12 | Hive.init(baseLocation); 13 | 14 | TodoLocalDatasource todoLocalDatasource; 15 | Box box; 16 | 17 | setUp(() async { 18 | todoLocalDatasource = TodoLocalDatasource(); 19 | await todoLocalDatasource.deleteAll(); 20 | box = await todoLocalDatasource.boxInstance; 21 | }); 22 | 23 | tearDown(() async { 24 | await box.clear(); 25 | Injector.container?.clear(); 26 | }); 27 | 28 | test( 29 | 'should return todo items when getFormattedData is called. ' 30 | 'Given that box contains one record', () async { 31 | // Given 32 | await box.add(todoTable1); 33 | 34 | // When 35 | final List todoTableList = 36 | await todoLocalDatasource.getFormattedData(); 37 | 38 | // Then 39 | expect(todoTableList.length, 1); 40 | expect(todoTableList[0].id, todoTable1.id); 41 | }); 42 | test( 43 | 'box should contain one record ' 44 | 'when insertOrUpdateAll is called with one item', () async { 45 | // When 46 | await todoLocalDatasource.insertOrUpdateAll([todoTable1]); 47 | 48 | // Then 49 | expect(box.toMap().length, 1); 50 | expect(box.toMap().containsKey(todoTable1.id), true); 51 | 52 | final TodoTable todoTable = box.toMap().values.toList()[0]; 53 | expect(todoTable.id, todoTable.id); 54 | }); 55 | 56 | test( 57 | 'box should contain one record ' 58 | 'when insertOrUpdateItem is called with one item', () async { 59 | // When 60 | await todoLocalDatasource.insertOrUpdateItem(todoTable1); 61 | 62 | // Then 63 | expect(box.toMap().length, 1); 64 | expect(box.toMap().containsKey(todoTable1.id), true); 65 | 66 | final TodoTable todoTable = box.toMap().values.toList()[0]; 67 | expect(todoTable.id, todoTable.id); 68 | }); 69 | }); 70 | } 71 | -------------------------------------------------------------------------------- /test/data/datasources/remote/__mock__/http_client_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/mockito.dart'; 2 | import 'package:clean_code_architecture_flutter/common/http/http_client.dart'; 3 | 4 | class MockHttpClient extends Mock implements HttpClient {} 5 | -------------------------------------------------------------------------------- /test/data/datasources/remote/__mock__/todo_remote_datasource_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/mockito.dart'; 2 | import 'package:clean_code_architecture_flutter/data/datasources/remote/todo_remote_datasource.dart'; 3 | 4 | class MockTodoRemoteDatasource extends Mock implements TodoRemoteDatasource {} 5 | -------------------------------------------------------------------------------- /test/data/datasources/remote/todo_remote_datasource_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | 4 | import 'package:clean_code_architecture_flutter/data/datasources/remote/constants/todo_remote_datasource_constants.dart'; 5 | import 'package:clean_code_architecture_flutter/common/http/http_client.dart'; 6 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 7 | import 'package:clean_code_architecture_flutter/data/datasources/remote/todo_remote_datasource.dart'; 8 | import '../../models/__mock__/todo_model_data.dart'; 9 | import '../../repositories/__mock__/todo_json_data.dart'; 10 | import '__mock__/http_client_mock.dart'; 11 | 12 | void main() { 13 | HttpClient httpClient = MockHttpClient(); 14 | TodoRemoteDatasource todoRemoteDatasource; 15 | 16 | setUp( 17 | () { 18 | Injector.container.registerInstance(MockHttpClient()); 19 | httpClient = Injector.resolve(); 20 | 21 | todoRemoteDatasource = TodoRemoteDatasource( 22 | httpClient: httpClient, 23 | ); 24 | }, 25 | ); 26 | 27 | tearDown( 28 | () { 29 | Injector.container.clear(); 30 | }, 31 | ); 32 | 33 | group('todo remote datasource', () { 34 | group('get all todos', () { 35 | test('Should call httpclient', () async { 36 | when(httpClient.get(TodoEndpoints.getCreateUpdateDeletePath)) 37 | .thenAnswer((_) async => [todoJson]); 38 | 39 | //when 40 | final result = await todoRemoteDatasource.getAll(); 41 | 42 | //then 43 | expect(result.length, 1); 44 | expect(result[0].description, todoModel1.description); 45 | verify(httpClient.get(TodoEndpoints.getCreateUpdateDeletePath)) 46 | .called(1); 47 | }); 48 | }); 49 | group('create todo', () { 50 | test('Should call httpclient', () async { 51 | when(httpClient.post(TodoEndpoints.getCreateUpdateDeletePath, todoJson)) 52 | .thenAnswer((_) async => todoJson); 53 | 54 | //when 55 | final result = await todoRemoteDatasource.create(todoModel1); 56 | 57 | //then 58 | expect(result.description, todoModel1.description); 59 | verify(httpClient.post( 60 | TodoEndpoints.getCreateUpdateDeletePath, todoJson)) 61 | .called(1); 62 | }); 63 | }); 64 | 65 | group('update todo', () { 66 | test('Should call httpclient', () async { 67 | when(httpClient.put( 68 | '${TodoEndpoints.getCreateUpdateDeletePath}/${todoModel1.id}', 69 | todoJson)) 70 | .thenAnswer((_) async => todoJson); 71 | 72 | //when 73 | final result = await todoRemoteDatasource.update(todoModel1); 74 | 75 | //then 76 | expect(result.completed, todoModel1.completed); 77 | verify(httpClient.put( 78 | '${TodoEndpoints.getCreateUpdateDeletePath}/${todoModel1.id}', 79 | todoJson, 80 | )).called(1); 81 | }); 82 | }); 83 | 84 | group('delete todo', () { 85 | test('Should call httpclient', () async { 86 | when(httpClient.delete( 87 | '${TodoEndpoints.getCreateUpdateDeletePath}/${todoModel1.id}', 88 | )).thenAnswer((_) async => todoJsonResponse); 89 | 90 | //when 91 | final result = await todoRemoteDatasource.delete(todoModel1.id); 92 | 93 | //then 94 | expect(result.description, todoModel1.description); 95 | verify(httpClient.delete( 96 | '${TodoEndpoints.getCreateUpdateDeletePath}/${todoModel1.id}', 97 | )).called(1); 98 | }); 99 | }); 100 | }); 101 | } 102 | -------------------------------------------------------------------------------- /test/data/models/__mock__/todo_model_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 2 | 3 | TodoModel todoModel1 = TodoModel( 4 | id: '1', 5 | description: 'create an app', 6 | completed: false, 7 | ); 8 | -------------------------------------------------------------------------------- /test/data/models/todo_model_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/data/models/todo_model.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import '../repositories/__mock__/todo_json_data.dart'; 5 | 6 | void main() { 7 | group('Interest Rate Model', () { 8 | test('should init from list json', () { 9 | // When 10 | final List todoModel = TodoModel.fromJsonList([todoJson]); 11 | // Then 12 | expect(todoModel.length, 1); 13 | }); 14 | 15 | test('should init from json', () { 16 | // When 17 | final TodoModel todoModel = TodoModel.fromJson(todoJson); 18 | // Then 19 | expect(todoModel.description, todoJson['description']); 20 | }); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /test/data/repositories/__mock__/todo_json_data.dart: -------------------------------------------------------------------------------- 1 | Map todoJson = { 2 | 'description': 'create an app', 3 | 'completed': false, 4 | }; 5 | 6 | Map todoJsonResponse = { 7 | '_id': '1', 8 | 'description': 'create an app', 9 | 'completed': false, 10 | }; 11 | -------------------------------------------------------------------------------- /test/data/repositories/__mock__/todo_repository_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/mockito.dart'; 2 | 3 | import 'package:clean_code_architecture_flutter/domain/repositories/todo_repository.dart'; 4 | 5 | class MockTodoRepository extends Mock implements TodoRepository {} 6 | -------------------------------------------------------------------------------- /test/data/repositories/todo_repository_impl_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | 4 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 5 | import 'package:clean_code_architecture_flutter/data/datasources/local/local_database/todo_local_datasource.dart'; 6 | import 'package:clean_code_architecture_flutter/data/datasources/remote/todo_remote_datasource.dart'; 7 | import 'package:clean_code_architecture_flutter/data/repositories/todo_repository_impl.dart'; 8 | 9 | import '../datasources/local/__mock__/todo_local_datasource_mock.dart'; 10 | import '../datasources/remote/__mock__/todo_remote_datasource_mock.dart'; 11 | import '../models/__mock__/todo_model_data.dart'; 12 | 13 | void main() { 14 | TodoRepositoryImpl todoRepositoryImpl; 15 | TodoLocalDatasource todoLocalDatasource = MockTodoLocalDatasource(); 16 | TodoRemoteDatasource todoRemoteDatasource = MockTodoRemoteDatasource(); 17 | 18 | setUp( 19 | () { 20 | Injector.container 21 | .registerInstance(MockTodoRemoteDatasource()); 22 | Injector.container 23 | .registerInstance(MockTodoLocalDatasource()); 24 | todoRemoteDatasource = Injector.resolve(); 25 | todoLocalDatasource = Injector.resolve(); 26 | 27 | todoRepositoryImpl = TodoRepositoryImpl( 28 | todoLocalDatasource: todoLocalDatasource, 29 | todoRemoteDatasource: todoRemoteDatasource, 30 | ); 31 | }, 32 | ); 33 | 34 | tearDown( 35 | () { 36 | Injector.container.clear(); 37 | }, 38 | ); 39 | 40 | group('todo Repository Impl Test', () { 41 | group('get all todos', () { 42 | test('Should get all todos from local datasource', () async { 43 | when(todoLocalDatasource.getFormattedData()) 44 | .thenAnswer((_) async => [todoModel1]); 45 | 46 | //when 47 | final result = await todoRepositoryImpl.getAll(); 48 | 49 | //then 50 | expect(result.length, 1); 51 | expect(result[0].description, todoModel1.description); 52 | verify(todoLocalDatasource.getFormattedData()).called(1); 53 | verifyNever(todoRemoteDatasource.getAll()); 54 | verifyNever(todoLocalDatasource.deleteAll()); 55 | verifyNever(todoLocalDatasource.insertOrUpdateAll([todoModel1])); 56 | }); 57 | 58 | test('should get all todo from api when localdatasource is empty', 59 | () async { 60 | when(todoLocalDatasource.getFormattedData()) 61 | .thenAnswer((_) async => []); 62 | when(todoLocalDatasource.deleteAll()).thenAnswer((_) async => null); 63 | when(todoLocalDatasource.insertOrUpdateAll([todoModel1])) 64 | .thenAnswer((_) async => null); 65 | when(todoRemoteDatasource.getAll()) 66 | .thenAnswer((_) async => [todoModel1]); 67 | 68 | //when 69 | final result = await todoRepositoryImpl.getAll(); 70 | 71 | //then 72 | expect(result.length, 1); 73 | expect(result[0].description, todoModel1.description); 74 | verify(todoLocalDatasource.getFormattedData()).called(1); 75 | verify(todoRemoteDatasource.getAll()).called(1); 76 | verify(todoLocalDatasource.deleteAll()).called(1); 77 | verify(todoLocalDatasource.insertOrUpdateAll([todoModel1])).called(1); 78 | }); 79 | }); 80 | group('create todo', () { 81 | test('should call api and create todo in local datasource', () async { 82 | when(todoLocalDatasource.insertOrUpdateItem(todoModel1)) 83 | .thenAnswer((_) async => null); 84 | when(todoRemoteDatasource.create(todoModel1)) 85 | .thenAnswer((_) async => todoModel1); 86 | 87 | //when 88 | final result = await todoRepositoryImpl.create(todoModel1); 89 | 90 | //then 91 | expect(result.description, todoModel1.description); 92 | verify(todoLocalDatasource.insertOrUpdateItem(todoModel1)).called(1); 93 | verify(todoRemoteDatasource.create(todoModel1)).called(1); 94 | }); 95 | 96 | test('should call api only when response is null', () async { 97 | when(todoRemoteDatasource.create(todoModel1)) 98 | .thenAnswer((_) async => null); 99 | 100 | //when 101 | final result = await todoRepositoryImpl.create(todoModel1); 102 | 103 | //then 104 | expect(result, null); 105 | verify(todoRemoteDatasource.create(todoModel1)).called(1); 106 | verifyNever(todoLocalDatasource.insertOrUpdateItem(todoModel1)); 107 | }); 108 | }); 109 | 110 | group('update todo', () { 111 | test('should call api and update todo in local datasource', () async { 112 | when(todoLocalDatasource.insertOrUpdateItem(todoModel1)) 113 | .thenAnswer((_) async => null); 114 | when(todoRemoteDatasource.update(todoModel1)) 115 | .thenAnswer((_) async => todoModel1); 116 | 117 | //when 118 | final result = await todoRepositoryImpl.update(todoModel1); 119 | 120 | //then 121 | expect(result.description, todoModel1.description); 122 | verify(todoLocalDatasource.insertOrUpdateItem(todoModel1)).called(1); 123 | verify(todoRemoteDatasource.update(todoModel1)).called(1); 124 | }); 125 | 126 | test('should call api only when response is null', () async { 127 | when(todoRemoteDatasource.create(todoModel1)) 128 | .thenAnswer((_) async => null); 129 | 130 | //when 131 | final result = await todoRepositoryImpl.create(todoModel1); 132 | 133 | //then 134 | expect(result, null); 135 | verify(todoRemoteDatasource.create(todoModel1)).called(1); 136 | verifyNever(todoLocalDatasource.insertOrUpdateItem(todoModel1)); 137 | }); 138 | }); 139 | 140 | group('delete todo', () { 141 | test('should call api and delete todo in local datasource', () async { 142 | when(todoLocalDatasource.insertOrUpdateItem(todoModel1)) 143 | .thenAnswer((_) async => null); 144 | when(todoRemoteDatasource.delete(todoModel1.id)) 145 | .thenAnswer((_) async => todoModel1); 146 | 147 | //when 148 | await todoRepositoryImpl.delete(todoModel1.id); 149 | 150 | //then 151 | verify(todoLocalDatasource.delete(todoModel1.id)).called(1); 152 | verify(todoRemoteDatasource.delete(todoModel1.id)).called(1); 153 | }); 154 | 155 | test('should call api only when response is null', () async { 156 | when(todoRemoteDatasource.delete(todoModel1.id)) 157 | .thenAnswer((_) async => null); 158 | 159 | //when 160 | await todoRepositoryImpl.delete(todoModel1.id); 161 | 162 | //then 163 | verify(todoRemoteDatasource.delete(todoModel1.id)).called(1); 164 | verifyNever(todoLocalDatasource.delete(todoModel1.id)); 165 | }); 166 | }); 167 | }); 168 | } 169 | -------------------------------------------------------------------------------- /test/domain/entities/__mock__/todo_entity_data.dart: -------------------------------------------------------------------------------- 1 | import 'package:clean_code_architecture_flutter/domain/entities/todo_entity.dart'; 2 | 3 | TodoEntity todoEntity1 = TodoEntity( 4 | id: '1', 5 | description: 'create an app', 6 | completed: false, 7 | ); 8 | 9 | TodoEntity todoEntity2 = TodoEntity( 10 | id: '2', 11 | description: 'Write testcase', 12 | completed: false, 13 | ); 14 | -------------------------------------------------------------------------------- /test/domain/usecases/__mock__/todo_usecase_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:mockito/mockito.dart'; 2 | import 'package:clean_code_architecture_flutter/domain/usescases/todo_usecase.dart'; 3 | 4 | class MockTodoUsecase extends Mock implements TodoUsecase {} 5 | -------------------------------------------------------------------------------- /test/domain/usecases/todo_usecase_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | 4 | import 'package:clean_code_architecture_flutter/domain/repositories/todo_repository.dart'; 5 | import 'package:clean_code_architecture_flutter/domain/usescases/todo_usecase.dart'; 6 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 7 | 8 | import '../../data/models/__mock__/todo_model_data.dart'; 9 | import '../../data/repositories/__mock__/todo_repository_mock.dart'; 10 | import '../entities/__mock__/todo_entity_data.dart'; 11 | 12 | void main() { 13 | TodoRepository todoRepository = MockTodoRepository(); 14 | TodoUsecase todoUsecase; 15 | 16 | setUp( 17 | () { 18 | Injector.container.registerInstance(MockTodoRepository()); 19 | todoRepository = Injector.resolve(); 20 | 21 | todoUsecase = TodoUsecase( 22 | todoRepository: todoRepository, 23 | ); 24 | }, 25 | ); 26 | 27 | tearDown( 28 | () { 29 | Injector.container.clear(); 30 | }, 31 | ); 32 | 33 | group('todo usecase', () { 34 | group('get all todos', () { 35 | test('Should call todo respository', () async { 36 | when(todoRepository.getAll()).thenAnswer((_) async => [todoEntity1]); 37 | 38 | //when 39 | final result = await todoUsecase.getAll(); 40 | 41 | //then 42 | expect(result.length, 1); 43 | expect(result[0].description, todoEntity1.description); 44 | verify(todoRepository.getAll()).called(1); 45 | }); 46 | }); 47 | group('create todo', () { 48 | test('Should call todo Repository', () async { 49 | when(todoRepository.create(any)).thenAnswer((_) async => todoEntity1); 50 | 51 | //when 52 | final result = await todoUsecase.create(todoEntity1); 53 | 54 | //then 55 | expect(result.description, todoEntity1.description); 56 | verify(todoRepository.create(any)).called(1); 57 | }); 58 | }); 59 | 60 | group('update todo', () { 61 | test('Should call todo Repository', () async { 62 | when(todoRepository.update(any)).thenAnswer((_) async => todoEntity1); 63 | 64 | //when 65 | final result = await todoUsecase.update(todoEntity1); 66 | 67 | //then 68 | expect(result.completed, todoModel1.completed); 69 | verify(todoRepository.update(any)).called(1); 70 | }); 71 | }); 72 | 73 | group('delete todo', () { 74 | test('Should call todo Repository', () async { 75 | when(todoRepository.delete(todoEntity1.id)) 76 | .thenAnswer((_) async => null); 77 | 78 | //when 79 | await todoUsecase.delete(todoEntity1.id); 80 | 81 | //then 82 | 83 | verify(todoRepository.delete(todoEntity1.id)).called(1); 84 | }); 85 | }); 86 | }); 87 | } 88 | -------------------------------------------------------------------------------- /test/local_database_test_files/database_util_test.hive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/test/local_database_test_files/database_util_test.hive -------------------------------------------------------------------------------- /test/local_database_test_files/database_util_test.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/test/local_database_test_files/database_util_test.lock -------------------------------------------------------------------------------- /test/local_database_test_files/testbox.hive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/test/local_database_test_files/testbox.hive -------------------------------------------------------------------------------- /test/local_database_test_files/testbox.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/test/local_database_test_files/testbox.lock -------------------------------------------------------------------------------- /test/local_database_test_files/todo.hive: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/test/local_database_test_files/todo.hive -------------------------------------------------------------------------------- /test/local_database_test_files/todo.lock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RashmiRanganathan/clean-code-architecture-flutter/0ded9d03c1fc8cdc5b9947502613e136907fcbea/test/local_database_test_files/todo.lock -------------------------------------------------------------------------------- /test/presentation/app_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:clean_code_architecture_flutter/presentation/app.dart'; 4 | 5 | void main() { 6 | testWidgets('render app when loaded', (WidgetTester tester) async { 7 | // when 8 | await tester.pumpWidget(App()); 9 | 10 | // Then 11 | expect(find.byType(MaterialApp), findsOneWidget); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /test/presentation/journey/dashboard/dashboard_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | import 'package:clean_code_architecture_flutter/presentation/journey/dashboard/dashboard_constants.dart'; 5 | import 'package:clean_code_architecture_flutter/presentation/journey/dashboard/dashboard_screen.dart'; 6 | 7 | import '../../../__setup__/navigation_mock.dart'; 8 | import '../../../__setup__/wrapper.dart'; 9 | 10 | void main() { 11 | MockNavigatorObserver mockObserver; 12 | 13 | setUp(() { 14 | mockObserver = MockNavigatorObserver(); 15 | }); 16 | testWidgets('render app dashboard screen ', (WidgetTester tester) async { 17 | // when 18 | await tester.pumpWidget(wrapWidget(DashboardScreen(), mockObserver)); 19 | final widget = 20 | find.byKey(const ValueKey(DashboardConstants.elevatedButtonKey)); 21 | 22 | // Then 23 | expect(find.text('TO-DO'), findsOneWidget); 24 | expect(widget, findsOneWidget); 25 | 26 | // When 27 | await tester.tap(widget); 28 | 29 | // Then 30 | verify(mockObserver.didPush(any, any)); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /test/presentation/journey/todo/bloc/__mock__/todo_bloc_mock.dart: -------------------------------------------------------------------------------- 1 | import 'package:bloc_test/bloc_test.dart'; 2 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 3 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 4 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_state.dart'; 5 | 6 | class MockTodoBloc extends MockBloc implements TodoBloc {} 7 | -------------------------------------------------------------------------------- /test/presentation/journey/todo/bloc/todo_bloc_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | 4 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 5 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 6 | import 'package:clean_code_architecture_flutter/domain/usescases/todo_usecase.dart'; 7 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 8 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_state.dart'; 9 | import '../../../../__setup__/base_test_bloc.dart'; 10 | import '../../../../domain/entities/__mock__/todo_entity_data.dart'; 11 | import '../../../../domain/usecases/__mock__/todo_usecase_mock.dart'; 12 | 13 | void main() { 14 | final BaseBlocTest todoBlocTest = 15 | BaseBlocTest(); 16 | TodoUsecase todoUsecase; 17 | TodoBloc todoBloc; 18 | 19 | setUp(() { 20 | todoUsecase = MockTodoUsecase(); 21 | todoBloc = TodoBloc(todoUsecase: todoUsecase); 22 | }); 23 | 24 | tearDown(() { 25 | todoBloc?.close(); 26 | Injector.container.clear(); 27 | }); 28 | group('TodoBloc', () { 29 | todoBlocTest 30 | ..test('initial state should be Intial state', 31 | build: () => todoBloc, 32 | expect: [ 33 | isA(), 34 | ], 35 | verify: () async { 36 | verifyNoMoreInteractions(todoUsecase); 37 | }) 38 | ..test('should get all todos', 39 | build: () => todoBloc, 40 | act: (bloc) async { 41 | when(todoUsecase.getAll()).thenAnswer((_) async => [todoEntity1]); 42 | bloc.add(FetchTodos()); 43 | }, 44 | expect: [ 45 | isA(), 46 | isA(), 47 | isA(), 48 | ], 49 | verify: () async { 50 | verify(todoUsecase.getAll()).called(1); 51 | }) 52 | ..test('should create todo', 53 | build: () => todoBloc, 54 | act: (bloc) async { 55 | when(todoUsecase.create(todoEntity1)) 56 | .thenAnswer((_) async => todoEntity1); 57 | bloc.add(AddTodo(description: todoEntity1.description)); 58 | }, 59 | expect: [ 60 | isA(), 61 | isA(), 62 | isA(), 63 | ], 64 | verify: () async { 65 | verify(todoUsecase.create(any)).called(1); 66 | }) 67 | ..test('should update todo', 68 | build: () => todoBloc, 69 | act: (bloc) async { 70 | when(todoUsecase.getAll()).thenAnswer((_) async => [todoEntity1]); 71 | when(todoUsecase.update(todoEntity1..completed = true)) 72 | .thenAnswer((_) async => todoEntity1..completed = true); 73 | bloc..add(FetchTodos())..add(UpdateTodo(id: todoEntity1.id)); 74 | }, 75 | expect: [ 76 | isA(), 77 | isA(), 78 | isA(), 79 | isA(), 80 | isA(), 81 | ], 82 | verify: () async { 83 | verify(todoUsecase.getAll()).called(1); 84 | verify(todoUsecase.update(todoEntity1..completed = true)).called(1); 85 | }) 86 | ..test('should delete todo', 87 | build: () => todoBloc, 88 | act: (bloc) async { 89 | when(todoUsecase.getAll()).thenAnswer((_) async => [todoEntity1]); 90 | when(todoUsecase.delete(todoEntity1.id)) 91 | .thenAnswer((_) async => null); 92 | bloc..add(FetchTodos())..add(DeleteTodo(id: todoEntity1.id)); 93 | }, 94 | expect: [ 95 | isA(), 96 | isA(), 97 | isA(), 98 | isA(), 99 | isA(), 100 | ], 101 | verify: () async { 102 | verify(todoUsecase.getAll()).called(1); 103 | verify(todoUsecase.delete(todoEntity1.id)).called(1); 104 | }); 105 | }); 106 | } 107 | -------------------------------------------------------------------------------- /test/presentation/journey/todo/create_todo/create_todo_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_test/flutter_test.dart'; 2 | import 'package:mockito/mockito.dart'; 3 | 4 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/create_todo/create_todo_constants.dart'; 5 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/create_todo/create_todo_screen.dart'; 6 | import '../../../../__setup__/navigation_mock.dart'; 7 | import '../../../../__setup__/wrapper.dart'; 8 | 9 | void main() { 10 | MockNavigatorObserver mockObserver; 11 | Function onCreate; 12 | setUp(() { 13 | mockObserver = MockNavigatorObserver(); 14 | onCreate = MockFunction().callbackWithString; 15 | }); 16 | 17 | testWidgets('Should call oncreate function on tap ', 18 | (WidgetTester tester) async { 19 | // when 20 | await tester.pumpWidget(wrapWidget( 21 | CreateTodoScreen( 22 | onCreate: onCreate, 23 | ), 24 | mockObserver)); 25 | final widget = find.byKey(CreateTodoConstants.descriptionFieldKey); 26 | final button = find.byKey(CreateTodoConstants.createTodoButton); 27 | 28 | // Then 29 | expect(find.text('Create Todo'), findsOneWidget); 30 | expect(widget, findsOneWidget); 31 | 32 | // When 33 | await tester.enterText(widget, 'create todo app'); 34 | await tester.tap(button); 35 | 36 | // Then 37 | verify(onCreate(any)).called(1); 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /test/presentation/journey/todo/todo_list/todo_list_screen_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | 5 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 6 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/widgets/todo_list_constants.dart'; 7 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/widgets/todo_item.dart'; 8 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 9 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 10 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_state.dart'; 11 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/todo_list_screen.dart'; 12 | import '../../../../__setup__/navigation_mock.dart'; 13 | import '../../../../__setup__/wrapper.dart'; 14 | import '../../../../domain/entities/__mock__/todo_entity_data.dart'; 15 | import '../bloc/__mock__/todo_bloc_mock.dart'; 16 | 17 | void main() { 18 | MockNavigatorObserver mockObserver; 19 | TodoBloc todoBloc; 20 | 21 | setUp(() { 22 | mockObserver = MockNavigatorObserver(); 23 | Injector.container.registerInstance(MockTodoBloc()); 24 | todoBloc = Injector.resolve(); 25 | }); 26 | 27 | tearDown(() { 28 | todoBloc?.close(); 29 | Injector.container.clear(); 30 | }); 31 | 32 | testWidgets('should render todolist', (WidgetTester tester) async { 33 | // when 34 | when(todoBloc.state) 35 | .thenAnswer((_) => LoadedTodos(todos: [todoEntity1, todoEntity2])); 36 | await tester.pumpWidget(wrapWidget( 37 | TodoListScreen(), 38 | mockObserver, 39 | )); 40 | 41 | final button = find.byKey(ValueKey('${todoEntity1.id}_icon')); 42 | 43 | // Then 44 | expect(find.text('TODOS'), findsOneWidget); 45 | expect(find.byType(TodoItem), findsNWidgets(2)); 46 | expect(button, findsOneWidget); 47 | }); 48 | 49 | testWidgets('should navigate to create todo', (WidgetTester tester) async { 50 | // when 51 | when(todoBloc.state).thenAnswer((_) => LoadedTodos(todos: [todoEntity1])); 52 | await tester.pumpWidget(wrapWidget( 53 | TodoListScreen(), 54 | mockObserver, 55 | )); 56 | 57 | final button = find.byKey(TodoListConstants.createTodoIcon); 58 | await tester.tap(button); 59 | 60 | // Then 61 | verify(mockObserver.didPush(any, any)); 62 | }); 63 | 64 | testWidgets('should show add todo button', (WidgetTester tester) async { 65 | // when 66 | when(todoBloc.state).thenAnswer((_) => LoadedTodos(todos: const [])); 67 | await tester.pumpWidget(wrapWidget( 68 | TodoListScreen(), 69 | mockObserver, 70 | )); 71 | 72 | final widget = find.byKey(TodoListConstants.createTodoButton); 73 | 74 | // Then 75 | expect(widget, findsOneWidget); 76 | 77 | // When 78 | await tester.tap(widget); 79 | 80 | // Then 81 | verify(mockObserver.didPush(any, any)); 82 | }); 83 | 84 | testWidgets('should refresh screen on pull', (WidgetTester tester) async { 85 | // when 86 | when(todoBloc.state).thenAnswer((_) => LoadedTodos(todos: [todoEntity1])); 87 | await tester.pumpWidget(wrapWidget( 88 | TodoListScreen(), 89 | mockObserver, 90 | )); 91 | 92 | final widget = find.byType(RefreshIndicator); 93 | 94 | // Then 95 | expect(widget, findsOneWidget); 96 | 97 | // When 98 | await tester.drag(widget, const Offset(0.0, 1000)); 99 | await tester.pumpAndSettle(); 100 | 101 | // Then 102 | verify(todoBloc.add(FetchTodos(fromLocal: false))).called(1); 103 | }); 104 | 105 | testWidgets('should show error screen ', (WidgetTester tester) async { 106 | // when 107 | when(todoBloc.state).thenAnswer((_) => ErrorTodos()); 108 | await tester.pumpWidget(wrapWidget( 109 | TodoListScreen(), 110 | mockObserver, 111 | )); 112 | 113 | final widget = find.text('Errored'); 114 | 115 | // Then 116 | expect(widget, findsOneWidget); 117 | }); 118 | 119 | testWidgets('should show empty screen on intial state', 120 | (WidgetTester tester) async { 121 | // when 122 | when(todoBloc.state).thenAnswer((_) => InitialTodos()); 123 | await tester.pumpWidget(wrapWidget( 124 | TodoListScreen(), 125 | mockObserver, 126 | )); 127 | 128 | final widget = find.byType(Container); 129 | 130 | // Then 131 | expect(widget, findsOneWidget); 132 | }); 133 | 134 | testWidgets('should add delete event', (WidgetTester tester) async { 135 | // when 136 | when(todoBloc.state).thenAnswer((_) => LoadedTodos(todos: [todoEntity1])); 137 | await tester.pumpWidget(wrapWidget( 138 | TodoListScreen(), 139 | mockObserver, 140 | )); 141 | 142 | final widget = find.byWidgetPredicate((Widget widget) => 143 | widget is TodoItem && widget.key == ValueKey('${todoEntity1.id}_item')); 144 | 145 | await tester.drag(widget, const Offset(-1000.0, 0.0)); 146 | await tester.pumpAndSettle(); 147 | 148 | verify(todoBloc.add(FetchTodos())).called(1); 149 | verify(todoBloc.add(DeleteTodo(id: todoEntity1.id))).called(1); 150 | }); 151 | 152 | testWidgets('should add update event', (WidgetTester tester) async { 153 | // when 154 | when(todoBloc.state).thenAnswer((_) => LoadedTodos(todos: [todoEntity1])); 155 | await tester.pumpWidget(wrapWidget( 156 | TodoListScreen(), 157 | mockObserver, 158 | )); 159 | 160 | final button = find.byKey(ValueKey('${todoEntity1.id}_icon')); 161 | 162 | await tester.tap(button); 163 | 164 | verify(todoBloc.add(FetchTodos())).called(1); 165 | verify(todoBloc.add(UpdateTodo(id: todoEntity1.id))).called(1); 166 | }); 167 | } 168 | -------------------------------------------------------------------------------- /test/presentation/journey/todo/todo_list/widgets/todo_item_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | 5 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/widgets/todo_item.dart'; 6 | 7 | import '../../../../../__setup__/navigation_mock.dart'; 8 | import '../../../../../__setup__/wrapper.dart'; 9 | import '../../../../../domain/entities/__mock__/todo_entity_data.dart'; 10 | 11 | void main() { 12 | MockNavigatorObserver mockObserver; 13 | Function onUpdate; 14 | Function onDelete; 15 | setUp(() { 16 | mockObserver = MockNavigatorObserver(); 17 | onUpdate = MockFunction().callback; 18 | onDelete = MockFunction().callback; 19 | }); 20 | 21 | testWidgets('should call onUpdate on click of icon', 22 | (WidgetTester tester) async { 23 | // when 24 | await tester.pumpWidget(wrapWidgetWithScaffold( 25 | TodoItem( 26 | todo: todoEntity1, 27 | onUpdate: onUpdate, 28 | onDelete: onDelete, 29 | ), 30 | mockObserver)); 31 | 32 | final button = find.byKey(ValueKey('${todoEntity1.id}_icon')); 33 | 34 | // Then 35 | expect(find.byType(ListTile), findsOneWidget); 36 | expect(find.byType(Dismissible), findsOneWidget); 37 | expect(find.text(todoEntity1.description), findsOneWidget); 38 | expect(find.byType(IconButton), findsOneWidget); 39 | expect(button, findsOneWidget); 40 | 41 | // When 42 | await tester.tap(button); 43 | 44 | // Then 45 | verify(onUpdate()).called(1); 46 | }); 47 | 48 | testWidgets('should call onDelete on dismiss', (WidgetTester tester) async { 49 | // when 50 | await tester.pumpWidget(wrapWidgetWithScaffold( 51 | TodoItem( 52 | todo: todoEntity1..completed = true, 53 | onUpdate: onUpdate, 54 | onDelete: onDelete, 55 | ), 56 | mockObserver)); 57 | 58 | final widget = find.byKey(ValueKey(todoEntity1.id)); 59 | 60 | // Then 61 | expect(find.byType(ListTile), findsOneWidget); 62 | expect(find.byType(Dismissible), findsOneWidget); 63 | expect(find.text(todoEntity1.description), findsOneWidget); 64 | expect(find.byType(IconButton), findsOneWidget); 65 | expect(widget, findsOneWidget); 66 | 67 | // When 68 | await tester.drag(widget, const Offset(-1000.0, 0.0)); 69 | await tester.pumpAndSettle(); 70 | 71 | // Then 72 | verify(onDelete()).called(1); 73 | }); 74 | } 75 | -------------------------------------------------------------------------------- /test/presentation/journey/todo/todo_routes_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:mockito/mockito.dart'; 4 | 5 | import 'package:clean_code_architecture_flutter/presentation/app.dart'; 6 | import 'package:clean_code_architecture_flutter/presentation/journey/dashboard/dashboard_constants.dart'; 7 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_event.dart'; 8 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/create_todo/create_todo_constants.dart'; 9 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/todo_list/widgets/todo_list_constants.dart'; 10 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_state.dart'; 11 | import 'package:clean_code_architecture_flutter/common/injector/injector.dart'; 12 | import 'package:clean_code_architecture_flutter/presentation/journey/todo/bloc/todo_bloc.dart'; 13 | import '../../../domain/entities/__mock__/todo_entity_data.dart'; 14 | import 'bloc/__mock__/todo_bloc_mock.dart'; 15 | 16 | void main() { 17 | TodoBloc todoBloc; 18 | 19 | setUp(() { 20 | Injector.container.registerInstance(MockTodoBloc()); 21 | todoBloc = Injector.resolve(); 22 | }); 23 | 24 | tearDown(() { 25 | todoBloc?.close(); 26 | Injector.container.clear(); 27 | }); 28 | 29 | testWidgets('should add todo', (WidgetTester tester) async { 30 | // when 31 | when(todoBloc.state).thenAnswer((_) => LoadedTodos(todos: [todoEntity1])); 32 | final gotoTodoList = 33 | find.byKey(const ValueKey(DashboardConstants.elevatedButtonKey)); 34 | final button = find.byKey(CreateTodoConstants.createTodoButton); 35 | final widget = find.byKey(CreateTodoConstants.descriptionFieldKey); 36 | final gotoTodoCreate = find.byKey(TodoListConstants.createTodoIcon); 37 | await tester.pumpWidget(App()); 38 | 39 | expect(gotoTodoList, findsOneWidget); 40 | await tester.tap(gotoTodoList); 41 | await tester.pumpAndSettle(); 42 | 43 | expect(gotoTodoCreate, findsOneWidget); 44 | await tester.tap(gotoTodoCreate); 45 | await tester.pumpAndSettle(); 46 | 47 | await tester.enterText(widget, 'write testcase'); 48 | await tester.tap(button); 49 | 50 | // Then 51 | verify(todoBloc.add(AddTodo(description: 'write testcase'))).called(1); 52 | }); 53 | } 54 | --------------------------------------------------------------------------------