To all who watched my original GetFluttered: MVC+S Architecture video, I want to thank you immensely for your patience.
Before making new GetFluttered videos, we wanted to implement:
- A means to provide written, supplemental info … Meta-Blog;
- A complex, production-level MVC+S example … PRAPARE app; &
- A means for others to join / collaborate … FlutterJuun Slack invite.
Given our healthcare focus, and specifically our interest in connecting FHIR to Flutter, our roadmap for GetFluttered videos will also include some FHIR-FLI content…but by no means is the GetFluttered series limited to health apps. Many of the topics covered can be applied to any Flutter app.
Here’s the primer on FHIR-FLI:
Finally, I plan to periodically update this architecture page so the Slack invite remains active (since Slack links now expire after 30 days)…but if I miss it, let me know in a comment on one of my YouTube videos. I’ll respond to your comment when the invite link is reactivated.
You should probably start by reading Ryan Edge’s post on Flutter State 5 Ways. Then read GSkinner’s article on MVC+S Architecture. Finally, check whatever latest discussions may exist in the Flutter community. As of Jan 2021, Simon Lightfoot’s video may be a good start…
When deciding state / architecture, consider:
- Do your classes follow the single responsibility principle?
- Are they testable?
- Are they modular?
- How difficult is this setup?
- Are others able to understand and build from it?
Ultimately, we decided upon the Get package to manage state and the GSkinner suggestion on MVC+S for our architecture. Because Get uses its controllers – not models – to manage state, we elected to make slight modifications from the original GSkinner approach.
Working with Streams
In the Architecture video, I particularly made a point of swapping between
int counter = 0; and
RxInt rxCounter = 0.obs; because I wanted you to be able to use this architecture with and without streams. GetX makes it extremely easy to work with streams and with minimal boilerplate, so you should feel comfortable swapping between the two.
Keep in mind that the
OBx methods require a stream to function properly. If it isn’t a stream, use
GetBuilder instead. Also, make sure to call
update(); in your controller if you need to trigger a UI refresh.
I was very intentional in my GetFluttered commit history to have each commit focus on a single objective in my video. Swapping between typical variables <–> streams is one example where the commit log can help. I plan to continue this format as best I can (and within reason).
Variable Names ± Streams
Initially, I was quite strict about updating all variable names to include ‘rx’ at the front if it referenced a stream…but as we built PRAPARE it became apparent that this level of control seemed more a hindrance than a benefit. Including ‘rx’ was useful in many cases, but should not be a set requirement. For complex data models, I would suggest you focus on variables and method names that are easy to read / understand, following patterns when it makes sense to do so.
In my opinion, learning about folder structure is best accomplished via example. Our video outlining the PRAPARE app as a complex example of the MVC+S architecture pattern should help. Keeping everything in one place, this is the general guideline we followed:
|/_internal||custom modifications, constants / enums, utility classes|
|/components||custom components / variations on Flutter widgets|
|/constants||local constants created for the app|
|/enums||predefined, named constants|
|/utils||local functions that do things like formatting|
|/api||optional API key location|
|<custom>.dart||private API keys|
|api_public.dart||public API keys (no gitignore)|
|api.dart||generic export file|
|/controllers||manages state of the model and resultant data|
|/commands||performs a specific global task (login, logout, change password)|
|../<custom>_command.dart||custom command class|
|../abstract_command.dart||abstract class for commonly used controllers, placeholder execute() method for commands|
|<custom>_controller.dart||custom controller, typically used for state management|
|/models||classes / objects created specifically for this app|
|<custom>_data.dart||custom data class|
|<custom>_model.dart||data model that typically modifies or shapes a data class|
|/routes||maps route to screen widgets|
|app_pages.dart||the directory of each page within an app|
|app_routes.dart||string route names used in the app|
|/services||interaction with the outside world (REST, FHIR, http, file storage)|
|/ui||essentially all things a user sees in the app|
|/styled_components||shared widgets that use a common design system / theme so that the app seems consistent across screens|
|/views||top level widgets that are loaded via a route|
|../../<screen_name>.dart||the screen widget, may optionally include ‘page’, ‘card’, or ‘panel’ at the end based on view type|
|../../<screen_name>_binding.dart||controllers/services that are loaded (or lazy-loaded) in a view|
|../../<screen_name>_controller.dart||the viewcontroller that only affects this screen widget|
|../../<screen_name>_test.dart||any relevant tests for the screen widget or its viewcontroller|
|icons.dart||icon asset locations|
|localization.dart||strings with multiple translations|
|strings.dart||strings used throughout an app|
|themes.dart||custom themes and font sizes|
|main.dart||the first file a Dart app runs|
I hope these written supplements will be useful as you design and build your own Flutter apps.
If there are any questions, or if you have any specific requests for GetFluttered topics to cover next, feel free to join us on the FlutterJuun Slack. A lot of GetFluttered content will build directly off of the work we already created in PRAPARE, and we have a lot of features to cover (themes, locales, databinding, serialization, etc.) in the short-term. That said, the path forward is pretty flexible and welcome to suggestions.
Keep learning, keep creating, and stay safe!