├── .gitignore
├── .metadata
├── LICENSE
├── README.md
├── analysis_options.yaml
├── android
├── .gitignore
├── app
│ ├── build.gradle
│ └── src
│ │ ├── debug
│ │ └── AndroidManifest.xml
│ │ ├── main
│ │ ├── AndroidManifest.xml
│ │ ├── kotlin
│ │ │ └── com
│ │ │ │ └── iamageo
│ │ │ │ └── fluttermvvmtemplate
│ │ │ │ └── flutter_mvvm_template
│ │ │ │ └── MainActivity.kt
│ │ └── res
│ │ │ ├── drawable-v21
│ │ │ └── launch_background.xml
│ │ │ ├── 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-night
│ │ │ └── styles.xml
│ │ │ └── values
│ │ │ └── styles.xml
│ │ └── profile
│ │ └── AndroidManifest.xml
├── build.gradle
├── gradle.properties
├── gradle
│ └── wrapper
│ │ └── gradle-wrapper.properties
└── settings.gradle
├── ios
├── .gitignore
├── Flutter
│ ├── AppFrameworkInfo.plist
│ ├── Debug.xcconfig
│ └── Release.xcconfig
├── 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
├── main.dart
├── remote
│ ├── api
│ │ ├── api_endpoints.dart
│ │ └── http_manager.dart
│ ├── model
│ │ ├── base_model.dart
│ │ └── base_model.g.dart
│ ├── repository
│ │ ├── app_repository.dart
│ │ └── app_repository_impl.dart
│ └── response
│ │ ├── api_response.dart
│ │ └── api_response.freezed.dart
├── router
│ ├── screen_controller.dart
│ └── screen_name.dart
└── ui
│ ├── base_screen.dart
│ ├── controller
│ └── example_view_model.dart
│ ├── get
│ ├── binding
│ │ └── get_example_binding.dart
│ └── get_example_screen.dart
│ └── post
│ ├── binding
│ └── post_example_binding.dart
│ └── post_example_screen.dart
├── pubspec.lock
├── pubspec.yaml
└── test
├── example_view_model_test.dart
└── mocks.dart
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 | migrate_working_dir/
12 |
13 | # IntelliJ related
14 | *.iml
15 | *.ipr
16 | *.iws
17 | .idea/
18 |
19 | # The .vscode folder contains launch configuration and tasks you configure in
20 | # VS Code which you may wish to be included in version control, so this line
21 | # is commented out by default.
22 | #.vscode/
23 |
24 | # Flutter/Dart/Pub related
25 | **/doc/api/
26 | **/ios/Flutter/.last_build_id
27 | .dart_tool/
28 | .flutter-plugins
29 | .flutter-plugins-dependencies
30 | .packages
31 | .pub-cache/
32 | .pub/
33 | /build/
34 |
35 | # Symbolication related
36 | app.*.symbols
37 |
38 | # Obfuscation related
39 | app.*.map.json
40 |
41 | # Android Studio will place build artifacts here
42 | /android/app/debug
43 | /android/app/profile
44 | /android/app/release
45 |
--------------------------------------------------------------------------------
/.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.
5 |
6 | version:
7 | revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
8 | channel: stable
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
17 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
18 | - platform: android
19 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
20 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
21 | - platform: ios
22 | create_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
23 | base_revision: 2ad6cd72c040113b47ee9055e722606a490ef0da
24 |
25 | # User provided section
26 |
27 | # List of Local paths (relative to this file) that should be
28 | # ignored by the migrate tool.
29 | #
30 | # Files that are not part of the templates will be ignored by default.
31 | unmanaged_files:
32 | - 'lib/main.dart'
33 | - 'ios/Runner.xcodeproj/project.pbxproj'
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright 2023 Geovani Amaral
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## MVVM with Repository pattern using GetX in Flutter 🚀
2 | This project implements the MVVM (Model-View-ViewModel) architecture with the Repository pattern in Flutter, using the GetX library for state management and dependency injection.
3 |
4 | ## About the architecture 🏗️
5 | The MVVM architecture was chosen for its numerous advantages such as better separation of responsibilities, increased testability, and less coupling between UI logic and business logic.
6 |
7 | The main components of this architecture are:
8 |
9 | * Model: Represents the data and state of the app.
10 | * View: Is the visual representation of the user interface, and doesn't contain any business logic.
11 | * ViewModel: Is an abstraction of the View that exposes relevant data streams to the UI. It communicates with the Model to perform tasks and update the View's state.
12 |
13 | The Repository pattern is an integral part of this architecture. The repository is an abstraction layer between the data sources (such as network APIs, local databases, etc.) and the rest of the app. It allows the data sources to be changed without affecting the rest of the app's code.
14 |
15 |
16 |
17 |
18 |
19 | ## About GetX 📦
20 | GetX is an efficient and easy to use state management, navigation, and dependency injection library for Flutter. In the context of this architecture, GetX is used to bind the ViewModels to Widgets in a reactive way, making it easy to update the UI whenever the ViewModel's state changes.
21 |
22 | ## About Freezed 🧊
23 | In our Flutter MVVM template, we utilize the freezed library for creating immutable, safe, and more expressive classes. Freezed allows us to work with design patterns such as Union/Sealed classes, generating necessary boilerplate code for copyWith, hashCode, toString, and more.
24 |
25 | ### Key Advantages:
26 |
27 | * Simplifies the creation of immutable data models.
28 | * Supports union/sealed classes, making state management easier.
29 | * Integrates with json_serializable for efficient JSON conversions.
30 |
31 | ```flutter
32 | @freezed
33 | class MyModel with _$MyModel {
34 | const factory MyModel({
35 | required String id,
36 | required String name,
37 | }) = _MyModel;
38 |
39 | factory MyModel.fromJson(Map json) => _$MyModelFromJson(json);
40 | }
41 | ```
42 |
43 | ## About Json Annotation 📝
44 | Json_annotation is used in conjunction with freezed to provide a powerful and flexible way to handle JSON serialization and deserialization. This allows our data models to efficiently communicate with REST APIs by converting JSON data to and from our Dart models.
45 |
46 | ### How We Use It:
47 |
48 | * We define our data models with json_serializable annotations.
49 | * The necessary code for serializing/deserializing is automatically generated.
50 | * We integrate this code with freezed for a seamless development experience.
51 |
52 | ```flutter
53 | @JsonSerializable()
54 | class UserModel {
55 | String name;
56 | String email;
57 |
58 | UserModel({required this name, required this email});
59 |
60 | factory UserModel.fromJson(Map json) => _$UserModelFromJson(json);
61 | Map toJson() => _$UserModelToJson(this);
62 | }
63 | ```
64 |
65 | ## How to use this project 🛠️
66 | * Clone the repository
67 | * Clone this repository to your local machine.
68 | * Install the dependencies
69 | * Run the app
70 |
71 | ## Basic Project Structure 📂
72 | The project is structured as follows:
73 |
74 | ```
75 | lib
76 | │ main.dart
77 | └───ui
78 | │ └───controller
79 | │ │ example_view_model.dart
80 | │ └───get
81 | │ └───binding
82 | │ │ get_example_screen.dart
83 | │ └───post
84 | │ └───binding
85 | │ │ post_example_screen.dart
86 | └───remote
87 | │ └───api
88 | │ │ api_endpoints.dart
89 | │ │ http_manager.dart
90 | │ └───model
91 | │ │ base_model.dart
92 | │ │ base_model.g.dart
93 | │ └───repository
94 | │ │ app_repository.dart
95 | │ │ app_repository_impl.dart
96 | │ └───response
97 | │ │ api_response.dart
98 | │ │ api_response.freezed.dart
99 | │ │ ...
100 | └───router
101 | │ │ screen_controller.dart
102 | │ │ screen_name.dart
103 |
104 | ```
105 |
106 | * lib - Contains the main source code of the app.
107 | * models - Contains data models.
108 | * ui - Contains user interface files.
109 | * controller - Contains GetXController (reference to viewModel).
110 | * remote - Contains API related files.
111 | * api - Contains all API-related classes.
112 | * model - Contains data models.
113 | * repository - Contains the Repository classes.
114 | * response - Contains response classes used for handling API responses.
115 | * router - Class organizes and centralizes the definitions of app screens, including their routes and bindings in GetX.
116 |
117 | ## Unit Tests 🧪
118 | To ensure code quality and maintainability, we have added unit tests that cover the ExampleViewModel (and, consequently, the usage of ApiResponse.when(...)). Below are the steps and examples:
119 |
120 | ```yml
121 | dev_dependencies:
122 | flutter_test:
123 | sdk: flutter
124 |
125 | # For generating and using mocks
126 | mockito: ^5.4.6
127 | build_runner: ^2.4.15
128 |
129 | # For serialization (already included in the template)
130 | json_serializable: ^6.9.5
131 | freezed: ^3.0.6
132 |
133 | flutter_lints: ^2.0.0
134 | ```
135 |
136 | ### Preparing the Mocks
137 | Inside the test/ directory, create the file test/mocks.dart (if it doesn’t exist) with exactly the following content:
138 |
139 | ```dart
140 | // test/mocks.dart
141 |
142 | import 'package:mockito/annotations.dart';
143 | import 'package:flutter_mvvm_template/remote/repository/app_repository.dart';
144 | import 'package:flutter_mvvm_template/remote/api/http_manager.dart';
145 |
146 | @GenerateMocks([
147 | AppRepository,
148 | HttpManager, // include this if you want to test AppRepositoryImpl
149 | ])
150 | void main() {}
151 | ```
152 |
153 | In the terminal, clean up old artifacts and generate the mocks:
154 |
155 | ```bash
156 | flutter pub run build_runner clean
157 | dart run build_runner build --delete-conflicting-outputs
158 | ```
159 |
160 | ### Writing Tests for the ViewModel
161 |
162 | ```dart
163 | // test/example_view_model_test.dart
164 |
165 | import 'package:flutter_test/flutter_test.dart';
166 | import 'package:mockito/mockito.dart';
167 | import 'package:flutter_mvvm_template/remote/model/base_model.dart';
168 | import 'package:flutter_mvvm_template/remote/response/api_response.dart';
169 | import 'package:flutter_mvvm_template/remote/repository/app_repository.dart';
170 | import 'package:flutter_mvvm_template/ui/controller/example_view_model.dart';
171 |
172 | // Import the generated mocks
173 | import 'mocks.mocks.dart';
174 |
175 | void main() {
176 | late MockAppRepository mockRepository;
177 | late ExampleViewModel viewModel;
178 |
179 | setUp(() {
180 | // 1) Create the mock instance
181 | mockRepository = MockAppRepository();
182 |
183 | // 2) Inject it into the ViewModel
184 | viewModel = ExampleViewModel(api: mockRepository);
185 |
186 | // 3) Initial state (optional)
187 | viewModel.getResponse.value = ApiResponse.loading();
188 | });
189 |
190 | test('getPosts returns Success and when() works correctly', () async {
191 | // 1) Fake data
192 | final fakeList = [
193 | BaseModel(id: 1, title: 'Title 1', body: 'Body 1'),
194 | BaseModel(id: 2, title: 'Title 2', body: 'Body 2'),
195 | ];
196 |
197 | // 2) Configure the mock to return ApiResponse.success
198 | when(mockRepository.getDataExample())
199 | .thenAnswer((_) async => ApiResponse.success(data: fakeList));
200 |
201 | // 3) Execute the ViewModel method
202 | await viewModel.getPosts();
203 |
204 | // 4) Get the final state
205 | final state = viewModel.getResponse.value;
206 |
207 | // 5) Use when(...) to obtain a test String
208 | final text = state.when(
209 | loading: () => 'loading',
210 | success: (data) => 'success with ${data.length}',
211 | error: (msg) => 'error: $msg',
212 | );
213 |
214 | // 6) Verify the result of when()
215 | expect(text, 'success with 2');
216 | });
217 |
218 | test('getPosts returns Error and when() handles the error correctly', () async {
219 | const errorMessage = 'Network failure';
220 |
221 | // 1) Configure the mock to return ApiResponse.error
222 | when(mockRepository.getDataExample())
223 | .thenAnswer((_) async => ApiResponse.error(message: errorMessage));
224 |
225 | // 2) Execute
226 | await viewModel.getPosts();
227 |
228 | // 3) Get the final state
229 | final state = viewModel.getResponse.value;
230 |
231 | // 4) Use when(...) to extract the error message
232 | final text = state.when(
233 | loading: () => 'loading',
234 | success: (_) => 'should not happen',
235 | error: (msg) => 'error: $msg',
236 | );
237 |
238 | // 5) Verify the result of when()
239 | expect(text, 'error: Network failure');
240 | });
241 | }
242 | ```
243 |
244 | ### Run all tests
245 |
246 | ```bash
247 | flutter test
248 | ```
249 |
250 | You should see something like:
251 |
252 | ```bash
253 | 00:00 +2: test getPosts returns Success ...
254 | 00:00 +3: test getPosts returns Error ...
255 | ```
256 |
257 | ## Note 📝
258 | A different approach is to create a GetX controller for each screen and inject this controller into the binding. In my template, I opted to create only one controller and use it for multiple screens to simplify the structure.
259 |
260 | I hope you find this project useful as a starting point for building Flutter apps using the MVVM architecture, Repository pattern, and GetX! 😃
261 |
262 | ## License
263 | ```
264 | Copyright 2023 Geovani Amaral
265 |
266 | Licensed under the Apache License, Version 2.0 (the "License");
267 | you may not use this file except in compliance with the License.
268 | You may obtain a copy of the License at
269 |
270 | http://www.apache.org/licenses/LICENSE-2.0
271 |
272 | Unless required by applicable law or agreed to in writing, software
273 | distributed under the License is distributed on an "AS IS" BASIS,
274 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
275 | See the License for the specific language governing permissions and
276 | limitations under the License.
277 |
278 | ```
279 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | # This file configures the analyzer, which statically analyzes Dart code to
2 | # check for errors, warnings, and lints.
3 | #
4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled
5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
6 | # invoked from the command line by running `flutter analyze`.
7 |
8 | # The following line activates a set of recommended lints for Flutter apps,
9 | # packages, and plugins designed to encourage good coding practices.
10 | include: package:flutter_lints/flutter.yaml
11 |
12 | linter:
13 | # The lint rules applied to this project can be customized in the
14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml`
15 | # included above or to enable additional rules. A list of all available lints
16 | # and their documentation is published at
17 | # https://dart-lang.github.io/linter/lints/index.html.
18 | #
19 | # Instead of disabling a lint rule for the entire project in the
20 | # section below, it can also be suppressed for a single line of code
21 | # or a specific dart file by using the `// ignore: name_of_lint` and
22 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file
23 | # producing the lint.
24 | rules:
25 | # avoid_print: false # Uncomment to disable the `avoid_print` rule
26 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
27 |
28 | # Additional information about this file can be found at
29 | # https://dart.dev/guides/language/analysis-options
30 |
--------------------------------------------------------------------------------
/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 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "kotlin-android"
4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
5 | id "dev.flutter.flutter-gradle-plugin"
6 | }
7 |
8 | android {
9 | namespace = "com.iamageo.fluttermvvmtemplate.flutter_mvvm_template"
10 | compileSdkVersion flutter.compileSdkVersion
11 | ndkVersion flutter.ndkVersion
12 |
13 | compileOptions {
14 | sourceCompatibility JavaVersion.VERSION_17
15 | targetCompatibility JavaVersion.VERSION_17
16 | }
17 |
18 | kotlinOptions {
19 | jvmTarget = JavaVersion.VERSION_17
20 | }
21 |
22 | sourceSets {
23 | main.java.srcDirs += 'src/main/kotlin'
24 | }
25 |
26 | defaultConfig {
27 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
28 | applicationId "com.iamageo.fluttermvvmtemplate.flutter_mvvm_template"
29 | // You can update the following values to match your application needs.
30 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
31 | minSdkVersion flutter.minSdkVersion
32 | targetSdkVersion flutter.targetSdkVersion
33 | versionCode flutter.versionCode
34 | versionName flutter.versionName
35 | }
36 |
37 | buildTypes {
38 | release {
39 | // TODO: Add your own signing config for the release build.
40 | // Signing with the debug keys for now, so `flutter run --release` works.
41 | signingConfig signingConfigs.debug
42 | }
43 | }
44 | }
45 |
46 | flutter {
47 | source '../..'
48 | }
49 |
--------------------------------------------------------------------------------
/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
11 |
19 |
23 |
27 |
28 |
29 |
30 |
31 |
32 |
34 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/android/app/src/main/kotlin/com/iamageo/fluttermvvmtemplate/flutter_mvvm_template/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.iamageo.fluttermvvmtemplate.flutter_mvvm_template
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | allprojects {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | }
6 | }
7 |
8 | rootProject.buildDir = '../build'
9 | subprojects {
10 | project.buildDir = "${rootProject.buildDir}/${project.name}"
11 | }
12 | subprojects {
13 | project.evaluationDependsOn(':app')
14 | }
15 |
16 | tasks.register("clean", Delete) {
17 | delete rootProject.buildDir
18 | }
19 |
--------------------------------------------------------------------------------
/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip
6 |
--------------------------------------------------------------------------------
/android/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | def flutterSdkPath = {
3 | def properties = new Properties()
4 | file("local.properties").withInputStream { properties.load(it) }
5 | def flutterSdkPath = properties.getProperty("flutter.sdk")
6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
7 | return flutterSdkPath
8 | }()
9 |
10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
11 |
12 | repositories {
13 | google()
14 | mavenCentral()
15 | gradlePluginPortal()
16 | }
17 | }
18 |
19 | plugins {
20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0"
21 | id "com.android.application" version "8.3.2" apply false
22 | id "org.jetbrains.kotlin.android" version "1.8.22" apply false
23 | }
24 |
25 | include ":app"
--------------------------------------------------------------------------------
/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
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 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1300;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | alwaysOutOfDate = 1;
175 | buildActionMask = 2147483647;
176 | files = (
177 | );
178 | inputPaths = (
179 | );
180 | name = "Thin Binary";
181 | outputPaths = (
182 | );
183 | runOnlyForDeploymentPostprocessing = 0;
184 | shellPath = /bin/sh;
185 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
186 | };
187 | 9740EEB61CF901F6004384FC /* Run Script */ = {
188 | isa = PBXShellScriptBuildPhase;
189 | alwaysOutOfDate = 1;
190 | buildActionMask = 2147483647;
191 | files = (
192 | );
193 | inputPaths = (
194 | );
195 | name = "Run Script";
196 | outputPaths = (
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | shellPath = /bin/sh;
200 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
201 | };
202 | /* End PBXShellScriptBuildPhase section */
203 |
204 | /* Begin PBXSourcesBuildPhase section */
205 | 97C146EA1CF9000F007C117D /* Sources */ = {
206 | isa = PBXSourcesBuildPhase;
207 | buildActionMask = 2147483647;
208 | files = (
209 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
210 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
211 | );
212 | runOnlyForDeploymentPostprocessing = 0;
213 | };
214 | /* End PBXSourcesBuildPhase section */
215 |
216 | /* Begin PBXVariantGroup section */
217 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
218 | isa = PBXVariantGroup;
219 | children = (
220 | 97C146FB1CF9000F007C117D /* Base */,
221 | );
222 | name = Main.storyboard;
223 | sourceTree = "";
224 | };
225 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
226 | isa = PBXVariantGroup;
227 | children = (
228 | 97C147001CF9000F007C117D /* Base */,
229 | );
230 | name = LaunchScreen.storyboard;
231 | sourceTree = "";
232 | };
233 | /* End PBXVariantGroup section */
234 |
235 | /* Begin XCBuildConfiguration section */
236 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
237 | isa = XCBuildConfiguration;
238 | buildSettings = {
239 | ALWAYS_SEARCH_USER_PATHS = NO;
240 | CLANG_ANALYZER_NONNULL = YES;
241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
242 | CLANG_CXX_LIBRARY = "libc++";
243 | CLANG_ENABLE_MODULES = YES;
244 | CLANG_ENABLE_OBJC_ARC = YES;
245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
246 | CLANG_WARN_BOOL_CONVERSION = YES;
247 | CLANG_WARN_COMMA = YES;
248 | CLANG_WARN_CONSTANT_CONVERSION = YES;
249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
251 | CLANG_WARN_EMPTY_BODY = YES;
252 | CLANG_WARN_ENUM_CONVERSION = YES;
253 | CLANG_WARN_INFINITE_RECURSION = YES;
254 | CLANG_WARN_INT_CONVERSION = YES;
255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260 | CLANG_WARN_STRICT_PROTOTYPES = YES;
261 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
262 | CLANG_WARN_UNREACHABLE_CODE = YES;
263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
265 | COPY_PHASE_STRIP = NO;
266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
267 | ENABLE_NS_ASSERTIONS = NO;
268 | ENABLE_STRICT_OBJC_MSGSEND = YES;
269 | GCC_C_LANGUAGE_STANDARD = gnu99;
270 | GCC_NO_COMMON_BLOCKS = YES;
271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
273 | GCC_WARN_UNDECLARED_SELECTOR = YES;
274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
275 | GCC_WARN_UNUSED_FUNCTION = YES;
276 | GCC_WARN_UNUSED_VARIABLE = YES;
277 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
278 | MTL_ENABLE_DEBUG_INFO = NO;
279 | SDKROOT = iphoneos;
280 | SUPPORTED_PLATFORMS = iphoneos;
281 | TARGETED_DEVICE_FAMILY = "1,2";
282 | VALIDATE_PRODUCT = YES;
283 | };
284 | name = Profile;
285 | };
286 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
287 | isa = XCBuildConfiguration;
288 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
289 | buildSettings = {
290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
291 | CLANG_ENABLE_MODULES = YES;
292 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
293 | DEVELOPMENT_TEAM = 434X5NLQGF;
294 | ENABLE_BITCODE = NO;
295 | INFOPLIST_FILE = Runner/Info.plist;
296 | LD_RUNPATH_SEARCH_PATHS = (
297 | "$(inherited)",
298 | "@executable_path/Frameworks",
299 | );
300 | PRODUCT_BUNDLE_IDENTIFIER = com.iamageo.fluttermvvmtemplate.flutterMvvmTemplate;
301 | PRODUCT_NAME = "$(TARGET_NAME)";
302 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
303 | SWIFT_VERSION = 5.0;
304 | VERSIONING_SYSTEM = "apple-generic";
305 | };
306 | name = Profile;
307 | };
308 | 97C147031CF9000F007C117D /* Debug */ = {
309 | isa = XCBuildConfiguration;
310 | buildSettings = {
311 | ALWAYS_SEARCH_USER_PATHS = NO;
312 | CLANG_ANALYZER_NONNULL = YES;
313 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
314 | CLANG_CXX_LIBRARY = "libc++";
315 | CLANG_ENABLE_MODULES = YES;
316 | CLANG_ENABLE_OBJC_ARC = YES;
317 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
318 | CLANG_WARN_BOOL_CONVERSION = YES;
319 | CLANG_WARN_COMMA = YES;
320 | CLANG_WARN_CONSTANT_CONVERSION = YES;
321 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
322 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
323 | CLANG_WARN_EMPTY_BODY = YES;
324 | CLANG_WARN_ENUM_CONVERSION = YES;
325 | CLANG_WARN_INFINITE_RECURSION = YES;
326 | CLANG_WARN_INT_CONVERSION = YES;
327 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
328 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
329 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
330 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
331 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
332 | CLANG_WARN_STRICT_PROTOTYPES = YES;
333 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
334 | CLANG_WARN_UNREACHABLE_CODE = YES;
335 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
336 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
337 | COPY_PHASE_STRIP = NO;
338 | DEBUG_INFORMATION_FORMAT = dwarf;
339 | ENABLE_STRICT_OBJC_MSGSEND = YES;
340 | ENABLE_TESTABILITY = YES;
341 | GCC_C_LANGUAGE_STANDARD = gnu99;
342 | GCC_DYNAMIC_NO_PIC = NO;
343 | GCC_NO_COMMON_BLOCKS = YES;
344 | GCC_OPTIMIZATION_LEVEL = 0;
345 | GCC_PREPROCESSOR_DEFINITIONS = (
346 | "DEBUG=1",
347 | "$(inherited)",
348 | );
349 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
350 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
351 | GCC_WARN_UNDECLARED_SELECTOR = YES;
352 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
353 | GCC_WARN_UNUSED_FUNCTION = YES;
354 | GCC_WARN_UNUSED_VARIABLE = YES;
355 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
356 | MTL_ENABLE_DEBUG_INFO = YES;
357 | ONLY_ACTIVE_ARCH = YES;
358 | SDKROOT = iphoneos;
359 | TARGETED_DEVICE_FAMILY = "1,2";
360 | };
361 | name = Debug;
362 | };
363 | 97C147041CF9000F007C117D /* Release */ = {
364 | isa = XCBuildConfiguration;
365 | buildSettings = {
366 | ALWAYS_SEARCH_USER_PATHS = NO;
367 | CLANG_ANALYZER_NONNULL = YES;
368 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
369 | CLANG_CXX_LIBRARY = "libc++";
370 | CLANG_ENABLE_MODULES = YES;
371 | CLANG_ENABLE_OBJC_ARC = YES;
372 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
373 | CLANG_WARN_BOOL_CONVERSION = YES;
374 | CLANG_WARN_COMMA = YES;
375 | CLANG_WARN_CONSTANT_CONVERSION = YES;
376 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
377 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
378 | CLANG_WARN_EMPTY_BODY = YES;
379 | CLANG_WARN_ENUM_CONVERSION = YES;
380 | CLANG_WARN_INFINITE_RECURSION = YES;
381 | CLANG_WARN_INT_CONVERSION = YES;
382 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
383 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
384 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
385 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
386 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
387 | CLANG_WARN_STRICT_PROTOTYPES = YES;
388 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
389 | CLANG_WARN_UNREACHABLE_CODE = YES;
390 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
391 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
392 | COPY_PHASE_STRIP = NO;
393 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
394 | ENABLE_NS_ASSERTIONS = NO;
395 | ENABLE_STRICT_OBJC_MSGSEND = YES;
396 | GCC_C_LANGUAGE_STANDARD = gnu99;
397 | GCC_NO_COMMON_BLOCKS = YES;
398 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
399 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
400 | GCC_WARN_UNDECLARED_SELECTOR = YES;
401 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
402 | GCC_WARN_UNUSED_FUNCTION = YES;
403 | GCC_WARN_UNUSED_VARIABLE = YES;
404 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
405 | MTL_ENABLE_DEBUG_INFO = NO;
406 | SDKROOT = iphoneos;
407 | SUPPORTED_PLATFORMS = iphoneos;
408 | SWIFT_COMPILATION_MODE = wholemodule;
409 | SWIFT_OPTIMIZATION_LEVEL = "-O";
410 | TARGETED_DEVICE_FAMILY = "1,2";
411 | VALIDATE_PRODUCT = YES;
412 | };
413 | name = Release;
414 | };
415 | 97C147061CF9000F007C117D /* Debug */ = {
416 | isa = XCBuildConfiguration;
417 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
418 | buildSettings = {
419 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
420 | CLANG_ENABLE_MODULES = YES;
421 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
422 | DEVELOPMENT_TEAM = 434X5NLQGF;
423 | ENABLE_BITCODE = NO;
424 | INFOPLIST_FILE = Runner/Info.plist;
425 | LD_RUNPATH_SEARCH_PATHS = (
426 | "$(inherited)",
427 | "@executable_path/Frameworks",
428 | );
429 | PRODUCT_BUNDLE_IDENTIFIER = com.iamageo.fluttermvvmtemplate.flutterMvvmTemplate;
430 | PRODUCT_NAME = "$(TARGET_NAME)";
431 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
432 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
433 | SWIFT_VERSION = 5.0;
434 | VERSIONING_SYSTEM = "apple-generic";
435 | };
436 | name = Debug;
437 | };
438 | 97C147071CF9000F007C117D /* Release */ = {
439 | isa = XCBuildConfiguration;
440 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
441 | buildSettings = {
442 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
443 | CLANG_ENABLE_MODULES = YES;
444 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
445 | DEVELOPMENT_TEAM = 434X5NLQGF;
446 | ENABLE_BITCODE = NO;
447 | INFOPLIST_FILE = Runner/Info.plist;
448 | LD_RUNPATH_SEARCH_PATHS = (
449 | "$(inherited)",
450 | "@executable_path/Frameworks",
451 | );
452 | PRODUCT_BUNDLE_IDENTIFIER = com.iamageo.fluttermvvmtemplate.flutterMvvmTemplate;
453 | PRODUCT_NAME = "$(TARGET_NAME)";
454 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
455 | SWIFT_VERSION = 5.0;
456 | VERSIONING_SYSTEM = "apple-generic";
457 | };
458 | name = Release;
459 | };
460 | /* End XCBuildConfiguration section */
461 |
462 | /* Begin XCConfigurationList section */
463 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
464 | isa = XCConfigurationList;
465 | buildConfigurations = (
466 | 97C147031CF9000F007C117D /* Debug */,
467 | 97C147041CF9000F007C117D /* Release */,
468 | 249021D3217E4FDB00AE95B9 /* Profile */,
469 | );
470 | defaultConfigurationIsVisible = 0;
471 | defaultConfigurationName = Release;
472 | };
473 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
474 | isa = XCConfigurationList;
475 | buildConfigurations = (
476 | 97C147061CF9000F007C117D /* Debug */,
477 | 97C147071CF9000F007C117D /* Release */,
478 | 249021D4217E4FDB00AE95B9 /* Profile */,
479 | );
480 | defaultConfigurationIsVisible = 0;
481 | defaultConfigurationName = Release;
482 | };
483 | /* End XCConfigurationList section */
484 | };
485 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
486 | }
487 |
--------------------------------------------------------------------------------
/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 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iamageo/flutter_mvvm_template/940fe965c0945a16df732f6b8db6c57f93e4c443/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 | CFBundleDisplayName
8 | Flutter Mvvm Template
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | flutter_mvvm_template
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_mvvm_template/router/screen_controller.dart';
3 | import 'package:flutter_mvvm_template/router/screen_name.dart';
4 | import 'package:flutter_mvvm_template/ui/base_screen.dart';
5 | import 'package:get/get.dart';
6 |
7 | void main() {
8 | runApp(const MyApp());
9 | }
10 |
11 | class MyApp extends StatelessWidget {
12 | const MyApp({super.key});
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return GetMaterialApp(
17 | debugShowCheckedModeBanner: false,
18 | title: 'Flutter MVVM Template',
19 | theme: ThemeData(
20 | primarySwatch: Colors.blue,
21 | ),
22 | initialRoute: ScreensNames.home,
23 | getPages: AppScreens.screens,
24 | home: const BaseScreen(),
25 | );
26 | }
27 | }
--------------------------------------------------------------------------------
/lib/remote/api/api_endpoints.dart:
--------------------------------------------------------------------------------
1 | class ApiEndPoints {
2 |
3 | final String getData = "https://jsonplaceholder.typicode.com/posts";
4 | final String postData = "https://jsonplaceholder.typicode.com/posts";
5 |
6 | }
--------------------------------------------------------------------------------
/lib/remote/api/http_manager.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 |
3 | abstract class HttpMethods {
4 | static const String post = "POST";
5 | static const String get = "GET";
6 | static const String put = "PUT";
7 | static const String patch = "PATCH";
8 | static const String delete = "DELETE";
9 | }
10 |
11 | class HttpManager {
12 | Future restRequest(
13 | {required String url,
14 | required String method,
15 | Map? headers,
16 | Map? body}) async {
17 | final headersDefault = headers?.cast() ?? {}
18 | ..addAll({
19 | 'content-type': 'application/json',
20 | 'accept': 'application/json'
21 | });
22 |
23 | Dio dio = Dio();
24 |
25 | try {
26 | Response response = await dio.request(url,
27 | options: Options(method: method, headers: headersDefault),
28 | data: body);
29 | return response.data;
30 | } on DioException catch (e) {
31 | return e.response?.data ?? {'error' : e};
32 | } catch (e) {
33 | return {'error': e};
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/lib/remote/model/base_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'base_model.g.dart';
4 |
5 | @JsonSerializable()
6 | class BaseModel {
7 | int? id;
8 | String? title;
9 | String? body;
10 | int? userId;
11 |
12 | BaseModel({this.id, this.title, this.body, this.userId});
13 |
14 | factory BaseModel.fromJson(Map json) => _$BaseModelFromJson(json);
15 |
16 | Map toJson() => _$BaseModelToJson(this);
17 |
18 | }
--------------------------------------------------------------------------------
/lib/remote/model/base_model.g.dart:
--------------------------------------------------------------------------------
1 | // GENERATED CODE - DO NOT MODIFY BY HAND
2 |
3 | part of 'base_model.dart';
4 |
5 | // **************************************************************************
6 | // JsonSerializableGenerator
7 | // **************************************************************************
8 |
9 | BaseModel _$BaseModelFromJson(Map json) => BaseModel(
10 | id: (json['id'] as num?)?.toInt(),
11 | title: json['title'] as String?,
12 | body: json['body'] as String?,
13 | userId: (json['userId'] as num?)?.toInt(),
14 | );
15 |
16 | Map _$BaseModelToJson(BaseModel instance) => {
17 | 'id': instance.id,
18 | 'title': instance.title,
19 | 'body': instance.body,
20 | 'userId': instance.userId,
21 | };
22 |
--------------------------------------------------------------------------------
/lib/remote/repository/app_repository.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_mvvm_template/remote/model/base_model.dart';
2 | import 'package:flutter_mvvm_template/remote/response/api_response.dart';
3 |
4 | abstract class AppRepository {
5 | Future>> getDataExample();
6 | Future> postDataExample(BaseModel baseModel);
7 | }
--------------------------------------------------------------------------------
/lib/remote/repository/app_repository_impl.dart:
--------------------------------------------------------------------------------
1 | import 'package:dio/dio.dart';
2 | import 'package:flutter_mvvm_template/remote/api/http_manager.dart';
3 | import 'package:flutter_mvvm_template/remote/response/api_response.dart';
4 |
5 | import '../api/api_endpoints.dart';
6 | import '../model/base_model.dart';
7 | import 'app_repository.dart';
8 |
9 | class AppRepositoryImpl implements AppRepository {
10 | final _httpManager = HttpManager();
11 |
12 | @override
13 | Future>> getDataExample() async {
14 | try {
15 | dynamic response = await _httpManager.restRequest(
16 | url: ApiEndPoints().getData, method: HttpMethods.get);
17 |
18 | if (response != null) {
19 | List jsonData = List.from(response.map((item) => BaseModel.fromJson(item)));
20 | return ApiResponse.success(data: jsonData);
21 | } else {
22 | return ApiResponse.error(message: "Ocorreu um erro");
23 | }
24 | } catch (e) {
25 | return ApiResponse.error(message: e.toString());
26 | }
27 | }
28 |
29 | @override
30 | Future> postDataExample(BaseModel model) async {
31 | try {
32 | dynamic response = await _httpManager.restRequest(
33 | url: ApiEndPoints().postData,
34 | body: model.toJson(),
35 | method: HttpMethods.post);
36 |
37 | if (response != null) {
38 | BaseModel jsonData = BaseModel.fromJson(response);
39 | return ApiResponse.success(data: jsonData);
40 | } else {
41 | return ApiResponse.error(message: "Ocorreu um erro");
42 | }
43 | } catch (e) {
44 | print(e);
45 | return ApiResponse.error(message: e.toString());
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/remote/response/api_response.dart:
--------------------------------------------------------------------------------
1 | import 'package:freezed_annotation/freezed_annotation.dart';
2 |
3 | part 'api_response.freezed.dart';
4 |
5 | @freezed
6 | class ApiResponse with _$ApiResponse {
7 | const factory ApiResponse.loading() = Loading;
8 | const factory ApiResponse.success({ required T data }) = Success;
9 | const factory ApiResponse.error({ required String message }) = Error;
10 | }
11 |
12 | extension ApiResponseWhen on ApiResponse {
13 | R when({
14 | required R Function() loading,
15 | required R Function(T data) success,
16 | required R Function(String message) error,
17 | }) {
18 | if (this is Loading) {
19 | return loading();
20 | }
21 | if (this is Success) {
22 | return success((this as Success).data);
23 | }
24 | if (this is Error) {
25 | return error((this as Error).message);
26 | }
27 | throw StateError('Tipo de ApiResponse inesperado');
28 | }
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/lib/remote/response/api_response.freezed.dart:
--------------------------------------------------------------------------------
1 | // dart format width=80
2 | // coverage:ignore-file
3 | // GENERATED CODE - DO NOT MODIFY BY HAND
4 | // ignore_for_file: type=lint
5 | // ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
6 |
7 | part of 'api_response.dart';
8 |
9 | // **************************************************************************
10 | // FreezedGenerator
11 | // **************************************************************************
12 |
13 | // dart format off
14 | T _$identity(T value) => value;
15 | /// @nodoc
16 | mixin _$ApiResponse {
17 |
18 |
19 |
20 |
21 |
22 | @override
23 | bool operator ==(Object other) {
24 | return identical(this, other) || (other.runtimeType == runtimeType&&other is ApiResponse);
25 | }
26 |
27 |
28 | @override
29 | int get hashCode => runtimeType.hashCode;
30 |
31 | @override
32 | String toString() {
33 | return 'ApiResponse<$T>()';
34 | }
35 |
36 |
37 | }
38 |
39 | /// @nodoc
40 | class $ApiResponseCopyWith {
41 | $ApiResponseCopyWith(ApiResponse _, $Res Function(ApiResponse) __);
42 | }
43 |
44 |
45 | /// @nodoc
46 |
47 |
48 | class Loading implements ApiResponse {
49 | const Loading();
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | @override
58 | bool operator ==(Object other) {
59 | return identical(this, other) || (other.runtimeType == runtimeType&&other is Loading);
60 | }
61 |
62 |
63 | @override
64 | int get hashCode => runtimeType.hashCode;
65 |
66 | @override
67 | String toString() {
68 | return 'ApiResponse<$T>.loading()';
69 | }
70 |
71 |
72 | }
73 |
74 |
75 |
76 |
77 | /// @nodoc
78 |
79 |
80 | class Success implements ApiResponse {
81 | const Success({required this.data});
82 |
83 |
84 | final T data;
85 |
86 | /// Create a copy of ApiResponse
87 | /// with the given fields replaced by the non-null parameter values.
88 | @JsonKey(includeFromJson: false, includeToJson: false)
89 | @pragma('vm:prefer-inline')
90 | $SuccessCopyWith> get copyWith => _$SuccessCopyWithImpl>(this, _$identity);
91 |
92 |
93 |
94 | @override
95 | bool operator ==(Object other) {
96 | return identical(this, other) || (other.runtimeType == runtimeType&&other is Success&&const DeepCollectionEquality().equals(other.data, data));
97 | }
98 |
99 |
100 | @override
101 | int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(data));
102 |
103 | @override
104 | String toString() {
105 | return 'ApiResponse<$T>.success(data: $data)';
106 | }
107 |
108 |
109 | }
110 |
111 | /// @nodoc
112 | abstract mixin class $SuccessCopyWith implements $ApiResponseCopyWith {
113 | factory $SuccessCopyWith(Success value, $Res Function(Success) _then) = _$SuccessCopyWithImpl;
114 | @useResult
115 | $Res call({
116 | T data
117 | });
118 |
119 |
120 |
121 |
122 | }
123 | /// @nodoc
124 | class _$SuccessCopyWithImpl
125 | implements $SuccessCopyWith {
126 | _$SuccessCopyWithImpl(this._self, this._then);
127 |
128 | final Success _self;
129 | final $Res Function(Success) _then;
130 |
131 | /// Create a copy of ApiResponse
132 | /// with the given fields replaced by the non-null parameter values.
133 | @pragma('vm:prefer-inline') $Res call({Object? data = freezed,}) {
134 | return _then(Success(
135 | data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
136 | as T,
137 | ));
138 | }
139 |
140 |
141 | }
142 |
143 | /// @nodoc
144 |
145 |
146 | class Error implements ApiResponse {
147 | const Error({required this.message});
148 |
149 |
150 | final String message;
151 |
152 | /// Create a copy of ApiResponse
153 | /// with the given fields replaced by the non-null parameter values.
154 | @JsonKey(includeFromJson: false, includeToJson: false)
155 | @pragma('vm:prefer-inline')
156 | $ErrorCopyWith> get copyWith => _$ErrorCopyWithImpl>(this, _$identity);
157 |
158 |
159 |
160 | @override
161 | bool operator ==(Object other) {
162 | return identical(this, other) || (other.runtimeType == runtimeType&&other is Error&&(identical(other.message, message) || other.message == message));
163 | }
164 |
165 |
166 | @override
167 | int get hashCode => Object.hash(runtimeType,message);
168 |
169 | @override
170 | String toString() {
171 | return 'ApiResponse<$T>.error(message: $message)';
172 | }
173 |
174 |
175 | }
176 |
177 | /// @nodoc
178 | abstract mixin class $ErrorCopyWith implements $ApiResponseCopyWith {
179 | factory $ErrorCopyWith(Error value, $Res Function(Error) _then) = _$ErrorCopyWithImpl;
180 | @useResult
181 | $Res call({
182 | String message
183 | });
184 |
185 |
186 |
187 |
188 | }
189 | /// @nodoc
190 | class _$ErrorCopyWithImpl
191 | implements $ErrorCopyWith {
192 | _$ErrorCopyWithImpl(this._self, this._then);
193 |
194 | final Error _self;
195 | final $Res Function(Error) _then;
196 |
197 | /// Create a copy of ApiResponse
198 | /// with the given fields replaced by the non-null parameter values.
199 | @pragma('vm:prefer-inline') $Res call({Object? message = null,}) {
200 | return _then(Error(
201 | message: null == message ? _self.message : message // ignore: cast_nullable_to_non_nullable
202 | as String,
203 | ));
204 | }
205 |
206 |
207 | }
208 |
209 | // dart format on
210 |
--------------------------------------------------------------------------------
/lib/router/screen_controller.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_mvvm_template/router/screen_name.dart';
2 | import 'package:flutter_mvvm_template/ui/base_screen.dart';
3 | import 'package:flutter_mvvm_template/ui/get/binding/get_example_binding.dart';
4 | import 'package:flutter_mvvm_template/ui/get/get_example_screen.dart';
5 | import 'package:flutter_mvvm_template/ui/post/binding/post_example_binding.dart';
6 | import 'package:flutter_mvvm_template/ui/post/post_example_screen.dart';
7 | import 'package:get/get_navigation/src/routes/get_route.dart';
8 |
9 | abstract class AppScreens {
10 |
11 | static final screens = [
12 | GetPage(name: ScreensNames.home, page: () => const BaseScreen()),
13 | GetPage(name: ScreensNames.getExample, page: () => const GetExampleScreen(), bindings: [GetExampleBinding()]),
14 | GetPage(name: ScreensNames.postExample, page: () => const PostExampleScreen(), bindings: [PostExampleBinding()]),
15 | ];
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/lib/router/screen_name.dart:
--------------------------------------------------------------------------------
1 | abstract class ScreensNames {
2 |
3 | static const String home = "/home";
4 | static const String getExample = "/getExample";
5 | static const String postExample = "/postExample";
6 |
7 | }
--------------------------------------------------------------------------------
/lib/ui/base_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_mvvm_template/router/screen_name.dart';
3 | import 'package:get/get.dart';
4 |
5 | class BaseScreen extends StatefulWidget {
6 | const BaseScreen({super.key});
7 |
8 | @override
9 | State createState() => _BaseScreenState();
10 | }
11 |
12 | class _BaseScreenState extends State {
13 | @override
14 | Widget build(BuildContext context) {
15 | return Scaffold(
16 | appBar: AppBar(
17 | title: const Text("MVVM Flutter Template"),
18 | ),
19 | body: Padding(
20 | padding: const EdgeInsets.all(8.0),
21 | child: Column(
22 | mainAxisAlignment: MainAxisAlignment.center,
23 | crossAxisAlignment: CrossAxisAlignment.stretch,
24 | children: [
25 | Expanded(
26 | child: Column(
27 | mainAxisAlignment: MainAxisAlignment.center,
28 | crossAxisAlignment: CrossAxisAlignment.stretch,
29 | children: [
30 | ElevatedButton(
31 | onPressed: () {
32 | Get.toNamed(ScreensNames.getExample);
33 | },
34 | child: const Text("GET"),
35 | ),
36 | ElevatedButton(
37 | onPressed: () {
38 | Get.toNamed(ScreensNames.postExample);
39 | },
40 | child: const Text("POST"),
41 | ),
42 | ],
43 | ),
44 | ),
45 | const Padding(
46 | padding: EdgeInsets.all(8.0),
47 | child: Column(
48 | crossAxisAlignment: CrossAxisAlignment.center,
49 | children: [
50 | Text("v - 1.0.0"),
51 | Text("Made with ❤️ by iamageo"),
52 | ],
53 | ),
54 | )
55 | ],
56 | ),
57 | ),
58 | );
59 | }
60 | }
61 |
62 |
--------------------------------------------------------------------------------
/lib/ui/controller/example_view_model.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_mvvm_template/remote/repository/app_repository.dart';
2 | import 'package:flutter_mvvm_template/remote/repository/app_repository_impl.dart';
3 | import 'package:get/get.dart';
4 |
5 | import '../../remote/model/base_model.dart';
6 | import '../../remote/response/api_response.dart';
7 |
8 | class ExampleViewModel extends GetxController {
9 |
10 | final AppRepository _api;
11 |
12 | ExampleViewModel({required AppRepository api}) : _api = api;
13 |
14 | Rx>> getResponse = const ApiResponse>.loading().obs;
15 | Rx> postResponse = ApiResponse.loading().obs;
16 |
17 | Future getPosts() async {
18 |
19 | ApiResponse> result = await _api.getDataExample();
20 |
21 | result.when(
22 | loading: () {
23 | getResponse.value = const ApiResponse.loading();
24 | },
25 | success: (list) {
26 | getResponse.value = ApiResponse.success(data: list);
27 | update();
28 | },
29 | error: (message) {
30 | getResponse.value = ApiResponse.error(message: message);
31 | },
32 | );
33 | }
34 |
35 | Future sendPost({required BaseModel model}) async {
36 | postResponse.value = ApiResponse.loading();
37 |
38 | ApiResponse result = await _api.postDataExample(model);
39 |
40 | result.when(
41 | loading: () {
42 | postResponse.value = const ApiResponse.loading();
43 | },
44 | success: (list) {
45 | postResponse.value = ApiResponse.success(data: list);
46 | update();
47 | },
48 | error: (message) {
49 | postResponse.value = ApiResponse.error(message: message);
50 | },
51 | );
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/lib/ui/get/binding/get_example_binding.dart:
--------------------------------------------------------------------------------
1 | import 'package:get/get.dart';
2 |
3 | import '../../../remote/repository/app_repository.dart';
4 | import '../../../remote/repository/app_repository_impl.dart';
5 | import '../../controller/example_view_model.dart';
6 |
7 | class GetExampleBinding extends Bindings {
8 | @override
9 | void dependencies() {
10 | Get.put(AppRepositoryImpl());
11 | Get.put(ExampleViewModel(api: Get.find()));
12 | }
13 | }
--------------------------------------------------------------------------------
/lib/ui/get/get_example_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_mvvm_template/remote/model/base_model.dart';
3 | import 'package:flutter_mvvm_template/remote/response/api_response.dart';
4 | import 'package:get/get.dart';
5 | import 'package:shimmer/shimmer.dart';
6 |
7 | import '../controller/example_view_model.dart';
8 |
9 |
10 | class GetExampleScreen extends StatefulWidget {
11 | const GetExampleScreen({super.key});
12 |
13 | @override
14 | State createState() => _GetExampleScreenState();
15 | }
16 |
17 | class _GetExampleScreenState extends State {
18 | ExampleViewModel viewModel = Get.find();
19 |
20 | @override
21 | void initState() {
22 | viewModel.getPosts();
23 | super.initState();
24 | }
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Scaffold(
29 | appBar: AppBar(
30 | title: const Text("get example"),
31 | ),
32 | body: Column(
33 | mainAxisAlignment: MainAxisAlignment.center,
34 | children: [
35 | Obx(() {
36 | return viewModel.getResponse.value.when(
37 | loading: () => buildShimmerEffect(),
38 | success: (list) => buildUserList(list),
39 | error: (message) => buildUserError(message),
40 | );
41 | })
42 | ],
43 | ),
44 | );
45 | }
46 |
47 | Padding buildUserError(String message) {
48 | return Padding(
49 | padding: const EdgeInsets.symmetric(horizontal: 16.0),
50 | child: Column(
51 | crossAxisAlignment: CrossAxisAlignment.stretch,
52 | children: [
53 | Align(
54 | alignment: Alignment.center,
55 | child: Text('Ops, ocorreu um erro, tente novamente. ${message}')),
56 | ElevatedButton(
57 | onPressed: () {
58 | viewModel.getPosts();
59 | },
60 | child: const Text("Tentar novamente"))
61 | ],
62 | ),
63 | );
64 | }
65 |
66 | Expanded buildUserList(List list) {
67 | return Expanded(
68 | child: ListView.builder(
69 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
70 | itemCount: list.length,
71 | itemBuilder: (BuildContext context, int index) {
72 | return ExpansionTile(
73 | leading: const FlutterLogo(),
74 | title: Text(list[index].title!), children: [
75 | Text(list[index].body!, style: const TextStyle(),)
76 | ],);
77 | },
78 | ),
79 | );
80 | }
81 |
82 | Expanded buildShimmerEffect() {
83 | return Expanded(
84 | child: ListView.builder(
85 | padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
86 | itemCount: 20,
87 | itemBuilder: (BuildContext context, int index) {
88 | return ListTile(
89 | title: SizedBox(
90 | height: 30,
91 | child: Shimmer.fromColors(
92 | baseColor: Colors.grey[300]!,
93 | highlightColor: Colors.grey[100]!,
94 | child: Container(
95 | color: Colors.grey[300],
96 | width: double.infinity,
97 | child: const Text(
98 | '',
99 | textAlign: TextAlign.center,
100 | style: TextStyle(
101 | fontSize: 40.0,
102 | fontWeight: FontWeight.bold,
103 | ),
104 | ),
105 | ),
106 | ),
107 | ),
108 | );
109 | },
110 | ),
111 | );
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/lib/ui/post/binding/post_example_binding.dart:
--------------------------------------------------------------------------------
1 | import 'package:get/get.dart';
2 |
3 | import '../../../remote/repository/app_repository.dart';
4 | import '../../../remote/repository/app_repository_impl.dart';
5 | import '../../controller/example_view_model.dart';
6 |
7 | class PostExampleBinding extends Bindings {
8 | @override
9 | void dependencies() {
10 | Get.put(AppRepositoryImpl());
11 | Get.put(ExampleViewModel(api: Get.find()));
12 | }
13 | }
--------------------------------------------------------------------------------
/lib/ui/post/post_example_screen.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter/material.dart';
2 | import 'package:flutter_mvvm_template/remote/response/api_response.dart';
3 | import 'package:get/get.dart';
4 |
5 | import '../../remote/model/base_model.dart';
6 | import '../controller/example_view_model.dart';
7 |
8 | class PostExampleScreen extends StatefulWidget {
9 | const PostExampleScreen({super.key});
10 |
11 | @override
12 | State createState() => _PostExampleScreenState();
13 | }
14 |
15 | class _PostExampleScreenState extends State {
16 |
17 | ExampleViewModel viewModel = Get.find();
18 |
19 | TextEditingController userIdController = TextEditingController();
20 | TextEditingController idController = TextEditingController();
21 | TextEditingController titleController = TextEditingController();
22 | TextEditingController bodyController = TextEditingController();
23 |
24 | final formKey = GlobalKey();
25 |
26 | @override
27 | Widget build(BuildContext context) {
28 | return Scaffold(
29 | appBar: AppBar(
30 | title: const Text("post example"),
31 | ),
32 | body: Padding(
33 | padding: const EdgeInsets.all(8.0),
34 | child: Form(
35 | key: formKey,
36 | child: Column(
37 | crossAxisAlignment: CrossAxisAlignment.stretch,
38 | children: [
39 | TextFormField(
40 | validator: (s) {
41 | if (s!.isEmpty) {
42 | return "Preencha o campo";
43 | }
44 | return null;
45 | },
46 | keyboardType: TextInputType.number,
47 | controller: userIdController,
48 | decoration: const InputDecoration(
49 | hintText: "userId",
50 | ),
51 | ),
52 | TextFormField(
53 | validator: (s) {
54 | if (s!.isEmpty) {
55 | return "Preencha o campo";
56 | }
57 | return null;
58 | },
59 | controller: titleController,
60 | decoration: const InputDecoration(
61 | hintText: "title",
62 | ),
63 | ),
64 | TextFormField(
65 | validator: (s) {
66 | if (s!.isEmpty) {
67 | return "Preencha o campo";
68 | }
69 | return null;
70 | },
71 | controller: bodyController,
72 | decoration: const InputDecoration(
73 | hintText: "body",
74 | ),
75 | ),
76 | Padding(
77 | padding: const EdgeInsets.all(8.0),
78 | child: ElevatedButton(
79 | onPressed: () {
80 | if (formKey.currentState!.validate()) {
81 | viewModel.sendPost(
82 | model: BaseModel(
83 | userId: int.parse(userIdController.text),
84 | title: titleController.text.trim(),
85 | body: bodyController.text.trim()));
86 | }
87 | },
88 | child: const Text("ENVIAR")),
89 | ),
90 | Obx(() {
91 | return viewModel.postResponse.value.when(
92 | loading: () => Container(),
93 | success: (data) => buildPostSuccess(data),
94 | error: (message) => const Text(
95 | "Ocorreu algum erro ao realizar o post, tente novamente."),
96 | );
97 | })
98 | ],
99 | ),
100 | ),
101 | ),
102 | );
103 | }
104 |
105 | Widget buildPostSuccess(BaseModel baseModel) {
106 | return Padding(
107 | padding: const EdgeInsets.all(8.0),
108 | child: Column(
109 | mainAxisAlignment: MainAxisAlignment.start,
110 | crossAxisAlignment: CrossAxisAlignment.start,
111 | children: [
112 | const Text("Resposta do post: ", style: TextStyle(fontWeight: FontWeight.bold),),
113 | const SizedBox(height: 4,),
114 | Column(
115 | crossAxisAlignment: CrossAxisAlignment.start,
116 | children: [
117 | Text("userID: ${baseModel.userId}"),
118 | Text("ID do post: ${baseModel.id}"),
119 | Text("Título: ${baseModel.title}"),
120 | Text("Body: ${baseModel.body}"),
121 | ],
122 | ),
123 | ],
124 | ),
125 | );
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/pubspec.lock:
--------------------------------------------------------------------------------
1 | # Generated by pub
2 | # See https://dart.dev/tools/pub/glossary#lockfile
3 | packages:
4 | _fe_analyzer_shared:
5 | dependency: transitive
6 | description:
7 | name: _fe_analyzer_shared
8 | sha256: e55636ed79578b9abca5fecf9437947798f5ef7456308b5cb85720b793eac92f
9 | url: "https://pub.dev"
10 | source: hosted
11 | version: "82.0.0"
12 | analyzer:
13 | dependency: transitive
14 | description:
15 | name: analyzer
16 | sha256: "904ae5bb474d32c38fb9482e2d925d5454cda04ddd0e55d2e6826bc72f6ba8c0"
17 | url: "https://pub.dev"
18 | source: hosted
19 | version: "7.4.5"
20 | args:
21 | dependency: transitive
22 | description:
23 | name: args
24 | sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
25 | url: "https://pub.dev"
26 | source: hosted
27 | version: "2.7.0"
28 | async:
29 | dependency: transitive
30 | description:
31 | name: async
32 | sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
33 | url: "https://pub.dev"
34 | source: hosted
35 | version: "2.12.0"
36 | boolean_selector:
37 | dependency: transitive
38 | description:
39 | name: boolean_selector
40 | sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
41 | url: "https://pub.dev"
42 | source: hosted
43 | version: "2.1.2"
44 | build:
45 | dependency: transitive
46 | description:
47 | name: build
48 | sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
49 | url: "https://pub.dev"
50 | source: hosted
51 | version: "2.4.2"
52 | build_config:
53 | dependency: transitive
54 | description:
55 | name: build_config
56 | sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
57 | url: "https://pub.dev"
58 | source: hosted
59 | version: "1.1.2"
60 | build_daemon:
61 | dependency: transitive
62 | description:
63 | name: build_daemon
64 | sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa"
65 | url: "https://pub.dev"
66 | source: hosted
67 | version: "4.0.4"
68 | build_resolvers:
69 | dependency: transitive
70 | description:
71 | name: build_resolvers
72 | sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
73 | url: "https://pub.dev"
74 | source: hosted
75 | version: "2.4.4"
76 | build_runner:
77 | dependency: "direct dev"
78 | description:
79 | name: build_runner
80 | sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
81 | url: "https://pub.dev"
82 | source: hosted
83 | version: "2.4.15"
84 | build_runner_core:
85 | dependency: transitive
86 | description:
87 | name: build_runner_core
88 | sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
89 | url: "https://pub.dev"
90 | source: hosted
91 | version: "8.0.0"
92 | built_collection:
93 | dependency: transitive
94 | description:
95 | name: built_collection
96 | sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
97 | url: "https://pub.dev"
98 | source: hosted
99 | version: "5.1.1"
100 | built_value:
101 | dependency: transitive
102 | description:
103 | name: built_value
104 | sha256: "082001b5c3dc495d4a42f1d5789990505df20d8547d42507c29050af6933ee27"
105 | url: "https://pub.dev"
106 | source: hosted
107 | version: "8.10.1"
108 | characters:
109 | dependency: transitive
110 | description:
111 | name: characters
112 | sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
113 | url: "https://pub.dev"
114 | source: hosted
115 | version: "1.4.0"
116 | checked_yaml:
117 | dependency: transitive
118 | description:
119 | name: checked_yaml
120 | sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
121 | url: "https://pub.dev"
122 | source: hosted
123 | version: "2.0.3"
124 | clock:
125 | dependency: transitive
126 | description:
127 | name: clock
128 | sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
129 | url: "https://pub.dev"
130 | source: hosted
131 | version: "1.1.2"
132 | code_builder:
133 | dependency: transitive
134 | description:
135 | name: code_builder
136 | sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e"
137 | url: "https://pub.dev"
138 | source: hosted
139 | version: "4.10.1"
140 | collection:
141 | dependency: transitive
142 | description:
143 | name: collection
144 | sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
145 | url: "https://pub.dev"
146 | source: hosted
147 | version: "1.19.1"
148 | convert:
149 | dependency: transitive
150 | description:
151 | name: convert
152 | sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
153 | url: "https://pub.dev"
154 | source: hosted
155 | version: "3.1.2"
156 | crypto:
157 | dependency: transitive
158 | description:
159 | name: crypto
160 | sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855"
161 | url: "https://pub.dev"
162 | source: hosted
163 | version: "3.0.6"
164 | cupertino_icons:
165 | dependency: "direct main"
166 | description:
167 | name: cupertino_icons
168 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
169 | url: "https://pub.dev"
170 | source: hosted
171 | version: "1.0.8"
172 | dart_style:
173 | dependency: transitive
174 | description:
175 | name: dart_style
176 | sha256: "5b236382b47ee411741447c1f1e111459c941ea1b3f2b540dde54c210a3662af"
177 | url: "https://pub.dev"
178 | source: hosted
179 | version: "3.1.0"
180 | dio:
181 | dependency: "direct main"
182 | description:
183 | name: dio
184 | sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9"
185 | url: "https://pub.dev"
186 | source: hosted
187 | version: "5.8.0+1"
188 | dio_web_adapter:
189 | dependency: transitive
190 | description:
191 | name: dio_web_adapter
192 | sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78"
193 | url: "https://pub.dev"
194 | source: hosted
195 | version: "2.1.1"
196 | fake_async:
197 | dependency: transitive
198 | description:
199 | name: fake_async
200 | sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
201 | url: "https://pub.dev"
202 | source: hosted
203 | version: "1.3.2"
204 | file:
205 | dependency: transitive
206 | description:
207 | name: file
208 | sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
209 | url: "https://pub.dev"
210 | source: hosted
211 | version: "7.0.1"
212 | fixnum:
213 | dependency: transitive
214 | description:
215 | name: fixnum
216 | sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be
217 | url: "https://pub.dev"
218 | source: hosted
219 | version: "1.1.1"
220 | flutter:
221 | dependency: "direct main"
222 | description: flutter
223 | source: sdk
224 | version: "0.0.0"
225 | flutter_lints:
226 | dependency: "direct dev"
227 | description:
228 | name: flutter_lints
229 | sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04
230 | url: "https://pub.dev"
231 | source: hosted
232 | version: "2.0.3"
233 | flutter_test:
234 | dependency: "direct dev"
235 | description: flutter
236 | source: sdk
237 | version: "0.0.0"
238 | freezed:
239 | dependency: "direct dev"
240 | description:
241 | name: freezed
242 | sha256: "6022db4c7bfa626841b2a10f34dd1e1b68e8f8f9650db6112dcdeeca45ca793c"
243 | url: "https://pub.dev"
244 | source: hosted
245 | version: "3.0.6"
246 | freezed_annotation:
247 | dependency: "direct main"
248 | description:
249 | name: freezed_annotation
250 | sha256: c87ff004c8aa6af2d531668b46a4ea379f7191dc6dfa066acd53d506da6e044b
251 | url: "https://pub.dev"
252 | source: hosted
253 | version: "3.0.0"
254 | frontend_server_client:
255 | dependency: transitive
256 | description:
257 | name: frontend_server_client
258 | sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
259 | url: "https://pub.dev"
260 | source: hosted
261 | version: "4.0.0"
262 | get:
263 | dependency: "direct main"
264 | description:
265 | name: get
266 | sha256: c79eeb4339f1f3deffd9ec912f8a923834bec55f7b49c9e882b8fef2c139d425
267 | url: "https://pub.dev"
268 | source: hosted
269 | version: "4.7.2"
270 | glob:
271 | dependency: transitive
272 | description:
273 | name: glob
274 | sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de
275 | url: "https://pub.dev"
276 | source: hosted
277 | version: "2.1.3"
278 | graphs:
279 | dependency: transitive
280 | description:
281 | name: graphs
282 | sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0"
283 | url: "https://pub.dev"
284 | source: hosted
285 | version: "2.3.2"
286 | http:
287 | dependency: transitive
288 | description:
289 | name: http
290 | sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b"
291 | url: "https://pub.dev"
292 | source: hosted
293 | version: "1.4.0"
294 | http_multi_server:
295 | dependency: transitive
296 | description:
297 | name: http_multi_server
298 | sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8
299 | url: "https://pub.dev"
300 | source: hosted
301 | version: "3.2.2"
302 | http_parser:
303 | dependency: transitive
304 | description:
305 | name: http_parser
306 | sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
307 | url: "https://pub.dev"
308 | source: hosted
309 | version: "4.1.2"
310 | io:
311 | dependency: transitive
312 | description:
313 | name: io
314 | sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b
315 | url: "https://pub.dev"
316 | source: hosted
317 | version: "1.0.5"
318 | js:
319 | dependency: transitive
320 | description:
321 | name: js
322 | sha256: "53385261521cc4a0c4658fd0ad07a7d14591cf8fc33abbceae306ddb974888dc"
323 | url: "https://pub.dev"
324 | source: hosted
325 | version: "0.7.2"
326 | json_annotation:
327 | dependency: "direct main"
328 | description:
329 | name: json_annotation
330 | sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
331 | url: "https://pub.dev"
332 | source: hosted
333 | version: "4.9.0"
334 | json_serializable:
335 | dependency: "direct dev"
336 | description:
337 | name: json_serializable
338 | sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
339 | url: "https://pub.dev"
340 | source: hosted
341 | version: "6.9.5"
342 | leak_tracker:
343 | dependency: transitive
344 | description:
345 | name: leak_tracker
346 | sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
347 | url: "https://pub.dev"
348 | source: hosted
349 | version: "10.0.8"
350 | leak_tracker_flutter_testing:
351 | dependency: transitive
352 | description:
353 | name: leak_tracker_flutter_testing
354 | sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
355 | url: "https://pub.dev"
356 | source: hosted
357 | version: "3.0.9"
358 | leak_tracker_testing:
359 | dependency: transitive
360 | description:
361 | name: leak_tracker_testing
362 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
363 | url: "https://pub.dev"
364 | source: hosted
365 | version: "3.0.1"
366 | lints:
367 | dependency: transitive
368 | description:
369 | name: lints
370 | sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
371 | url: "https://pub.dev"
372 | source: hosted
373 | version: "2.1.1"
374 | logging:
375 | dependency: transitive
376 | description:
377 | name: logging
378 | sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
379 | url: "https://pub.dev"
380 | source: hosted
381 | version: "1.3.0"
382 | matcher:
383 | dependency: transitive
384 | description:
385 | name: matcher
386 | sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
387 | url: "https://pub.dev"
388 | source: hosted
389 | version: "0.12.17"
390 | material_color_utilities:
391 | dependency: transitive
392 | description:
393 | name: material_color_utilities
394 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
395 | url: "https://pub.dev"
396 | source: hosted
397 | version: "0.11.1"
398 | meta:
399 | dependency: transitive
400 | description:
401 | name: meta
402 | sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
403 | url: "https://pub.dev"
404 | source: hosted
405 | version: "1.16.0"
406 | mime:
407 | dependency: transitive
408 | description:
409 | name: mime
410 | sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6"
411 | url: "https://pub.dev"
412 | source: hosted
413 | version: "2.0.0"
414 | mockito:
415 | dependency: "direct dev"
416 | description:
417 | name: mockito
418 | sha256: "4546eac99e8967ea91bae633d2ca7698181d008e95fa4627330cf903d573277a"
419 | url: "https://pub.dev"
420 | source: hosted
421 | version: "5.4.6"
422 | package_config:
423 | dependency: transitive
424 | description:
425 | name: package_config
426 | sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc
427 | url: "https://pub.dev"
428 | source: hosted
429 | version: "2.2.0"
430 | path:
431 | dependency: transitive
432 | description:
433 | name: path
434 | sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
435 | url: "https://pub.dev"
436 | source: hosted
437 | version: "1.9.1"
438 | pool:
439 | dependency: transitive
440 | description:
441 | name: pool
442 | sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
443 | url: "https://pub.dev"
444 | source: hosted
445 | version: "1.5.1"
446 | pub_semver:
447 | dependency: transitive
448 | description:
449 | name: pub_semver
450 | sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585"
451 | url: "https://pub.dev"
452 | source: hosted
453 | version: "2.2.0"
454 | pubspec_parse:
455 | dependency: transitive
456 | description:
457 | name: pubspec_parse
458 | sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082"
459 | url: "https://pub.dev"
460 | source: hosted
461 | version: "1.5.0"
462 | shelf:
463 | dependency: transitive
464 | description:
465 | name: shelf
466 | sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12
467 | url: "https://pub.dev"
468 | source: hosted
469 | version: "1.4.2"
470 | shelf_web_socket:
471 | dependency: transitive
472 | description:
473 | name: shelf_web_socket
474 | sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925"
475 | url: "https://pub.dev"
476 | source: hosted
477 | version: "3.0.0"
478 | shimmer:
479 | dependency: "direct main"
480 | description:
481 | name: shimmer
482 | sha256: "5f88c883a22e9f9f299e5ba0e4f7e6054857224976a5d9f839d4ebdc94a14ac9"
483 | url: "https://pub.dev"
484 | source: hosted
485 | version: "3.0.0"
486 | sky_engine:
487 | dependency: transitive
488 | description: flutter
489 | source: sdk
490 | version: "0.0.0"
491 | source_gen:
492 | dependency: transitive
493 | description:
494 | name: source_gen
495 | sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
496 | url: "https://pub.dev"
497 | source: hosted
498 | version: "2.0.0"
499 | source_helper:
500 | dependency: transitive
501 | description:
502 | name: source_helper
503 | sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c"
504 | url: "https://pub.dev"
505 | source: hosted
506 | version: "1.3.5"
507 | source_span:
508 | dependency: transitive
509 | description:
510 | name: source_span
511 | sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
512 | url: "https://pub.dev"
513 | source: hosted
514 | version: "1.10.1"
515 | stack_trace:
516 | dependency: transitive
517 | description:
518 | name: stack_trace
519 | sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
520 | url: "https://pub.dev"
521 | source: hosted
522 | version: "1.12.1"
523 | stream_channel:
524 | dependency: transitive
525 | description:
526 | name: stream_channel
527 | sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
528 | url: "https://pub.dev"
529 | source: hosted
530 | version: "2.1.4"
531 | stream_transform:
532 | dependency: transitive
533 | description:
534 | name: stream_transform
535 | sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871
536 | url: "https://pub.dev"
537 | source: hosted
538 | version: "2.1.1"
539 | string_scanner:
540 | dependency: transitive
541 | description:
542 | name: string_scanner
543 | sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
544 | url: "https://pub.dev"
545 | source: hosted
546 | version: "1.4.1"
547 | term_glyph:
548 | dependency: transitive
549 | description:
550 | name: term_glyph
551 | sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
552 | url: "https://pub.dev"
553 | source: hosted
554 | version: "1.2.2"
555 | test_api:
556 | dependency: transitive
557 | description:
558 | name: test_api
559 | sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
560 | url: "https://pub.dev"
561 | source: hosted
562 | version: "0.7.4"
563 | timing:
564 | dependency: transitive
565 | description:
566 | name: timing
567 | sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe"
568 | url: "https://pub.dev"
569 | source: hosted
570 | version: "1.0.2"
571 | typed_data:
572 | dependency: transitive
573 | description:
574 | name: typed_data
575 | sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006
576 | url: "https://pub.dev"
577 | source: hosted
578 | version: "1.4.0"
579 | vector_math:
580 | dependency: transitive
581 | description:
582 | name: vector_math
583 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
584 | url: "https://pub.dev"
585 | source: hosted
586 | version: "2.1.4"
587 | vm_service:
588 | dependency: transitive
589 | description:
590 | name: vm_service
591 | sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
592 | url: "https://pub.dev"
593 | source: hosted
594 | version: "14.3.1"
595 | watcher:
596 | dependency: transitive
597 | description:
598 | name: watcher
599 | sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
600 | url: "https://pub.dev"
601 | source: hosted
602 | version: "1.1.1"
603 | web:
604 | dependency: transitive
605 | description:
606 | name: web
607 | sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
608 | url: "https://pub.dev"
609 | source: hosted
610 | version: "1.1.1"
611 | web_socket:
612 | dependency: transitive
613 | description:
614 | name: web_socket
615 | sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c"
616 | url: "https://pub.dev"
617 | source: hosted
618 | version: "1.0.1"
619 | web_socket_channel:
620 | dependency: transitive
621 | description:
622 | name: web_socket_channel
623 | sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8
624 | url: "https://pub.dev"
625 | source: hosted
626 | version: "3.0.3"
627 | yaml:
628 | dependency: transitive
629 | description:
630 | name: yaml
631 | sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce
632 | url: "https://pub.dev"
633 | source: hosted
634 | version: "3.1.3"
635 | sdks:
636 | dart: ">=3.7.2 <4.0.0"
637 | flutter: ">=3.18.0-18.0.pre.54"
638 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: flutter_mvvm_template
2 | description: A new Flutter project.
3 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev
4 |
5 | version: 1.0.0+1
6 |
7 | environment:
8 | sdk: ^3.7.2
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 |
14 | cupertino_icons: ^1.0.2
15 | get: ^4.7.2
16 | dio: ^5.8.0+1
17 | shimmer: ^3.0.0
18 | freezed_annotation: ^3.0.0
19 | json_annotation: ^4.9.0
20 |
21 |
22 | dev_dependencies:
23 | flutter_test:
24 | sdk: flutter
25 | mockito: ^5.4.6
26 | build_runner: ^2.4.15
27 | json_serializable: ^6.9.5
28 | freezed: ^3.0.6
29 |
30 | flutter_lints: ^2.0.0
31 |
32 | flutter:
33 | uses-material-design: true
34 |
35 | # To add assets to your application, add an assets section, like this:
36 | # assets:
37 | # - images/a_dot_burr.jpeg
38 | # - images/a_dot_ham.jpeg
39 |
40 | # An image asset can refer to one or more resolution-specific "variants", see
41 | # https://flutter.dev/assets-and-images/#resolution-aware
42 |
43 | # For details regarding adding assets from package dependencies, see
44 | # https://flutter.dev/assets-and-images/#from-packages
45 |
46 | # To add custom fonts to your application, add a fonts section here,
47 | # in this "flutter" section. Each entry in this list should have a
48 | # "family" key with the font family name, and a "fonts" key with a
49 | # list giving the asset and other descriptors for the font. For
50 | # example:
51 | # fonts:
52 | # - family: Schyler
53 | # fonts:
54 | # - asset: fonts/Schyler-Regular.ttf
55 | # - asset: fonts/Schyler-Italic.ttf
56 | # style: italic
57 | # - family: Trajan Pro
58 | # fonts:
59 | # - asset: fonts/TrajanPro.ttf
60 | # - asset: fonts/TrajanPro_Bold.ttf
61 | # weight: 700
62 | #
63 | # For details regarding fonts from package dependencies,
64 | # see https://flutter.dev/custom-fonts/#from-packages
65 |
--------------------------------------------------------------------------------
/test/example_view_model_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 | import 'package:mockito/mockito.dart';
3 | import 'package:flutter_mvvm_template/remote/model/base_model.dart';
4 | import 'package:flutter_mvvm_template/remote/response/api_response.dart';
5 | import 'package:flutter_mvvm_template/ui/controller/example_view_model.dart';
6 | import 'mocks.mocks.dart';
7 |
8 | void main() {
9 | late MockAppRepository mockRepository;
10 | late ExampleViewModel viewModel;
11 |
12 | setUp(() {
13 | mockRepository = MockAppRepository();
14 | viewModel = ExampleViewModel(api: mockRepository);
15 | viewModel.getResponse.value = const ApiResponse.loading();
16 | });
17 |
18 | test(
19 | 'Quando getDataExample retorna sucesso, getResponse.value deve ser Success com a lista',
20 | () async {
21 |
22 | final listaFake = [
23 | BaseModel(id: 1, title: 'Título 1', body: 'Corpo 1'),
24 | BaseModel(id: 2, title: 'Título 2', body: 'Corpo 2'),
25 | ];
26 |
27 | when(mockRepository.getDataExample())
28 | .thenAnswer((_) async => ApiResponse.success(data: listaFake));
29 |
30 | await viewModel.getPosts();
31 |
32 | final estado = viewModel.getResponse.value;
33 | expect(estado, isA>>());
34 |
35 | if (estado is Success>) {
36 | expect(estado.data, listaFake);
37 | expect(estado.data.length, 2);
38 | expect(estado.data[0].title, 'Título 1');
39 | }
40 | },
41 | );
42 |
43 | test(
44 | 'Quando getDataExample retorna erro, getResponse.value deve ser Error com mensagem',
45 | () async {
46 |
47 | const msgErro = 'Falha de rede';
48 |
49 | when(mockRepository.getDataExample())
50 | .thenAnswer((_) async => const ApiResponse.error(message: msgErro));
51 |
52 | await viewModel.getPosts();
53 |
54 | final estado = viewModel.getResponse.value;
55 | expect(estado, isA>>());
56 |
57 | if (estado is Error>) {
58 | expect(estado.message, msgErro);
59 | }
60 | },
61 | );
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/test/mocks.dart:
--------------------------------------------------------------------------------
1 | import 'package:mockito/annotations.dart';
2 | import 'package:flutter_mvvm_template/remote/repository/app_repository.dart';
3 |
4 | @GenerateMocks([
5 | AppRepository,
6 | ])
7 | void main() {}
--------------------------------------------------------------------------------