This article is meant to be a starting point for people new to coding and trying their hand at Dart and Flutter, much as I was and did.
It discusses the core components of what comprises an app, and also tries to function as an appendix of sorts, giving a high level view of all the moving parts, but also providing the terms that can be used to easily find guides and detailed explanations of each part, for when you naturally stumble across the problem they are meant to solve, and need to find it again.
Most of these are given in italics.
This article will also be a part of a series, as the concepts it discusses are too big even for an introductory explanation to be given in one article.
So, without further ado, let’s get started!
Cogs in the machine
An app is a deceptively complex piece of software, especially for those just starting out. This is due to a variety of reasons, but without getting into the details, it is so because it requires a web of technologies working together to actually comprise an app, (not just your app framework, which is where Dart and Flutter come in) and when starting out all of this can feel extremely overwhelming.
My advice would be to take a deep breath, and realize that while it may seem complex now, consistent effort will go a long long way, in making it simpler, and no matter what, to never lose sight of the idea, passion or reason you set down this path in the first place.
Let that be your light in the dark, keeping you warm and guiding you to your destination.
So, firstly, an app comprises of several different parts that all work together and intertwine to make the final product.
This is not an exhaustive list, but the general, main parts of an app are:
- The UI
- The State Management solution
- The Dependency Injection solution
- The Local Data Persistence solution
- The Network and Connectivity code/combo/framework
- The Architecture Pattern
Note: The architecture isn’t really a component per se, but rather comprises of coding practices and such that hold together the rest of the components mentioned above. If we are to use the machine analogy, the architecture would be the lubricant used in-between the gears, or maybe the screw holding the gears in place, while the components are the gears themselves. Just as important, but not the same.
………and thats about it! Everything seems better when its listed out in an organized way, doesn’t it?
Now, lets provide some explanations and examples for each of these components
Perhaps the easiest part of the app amongst the six (at least in Flutter), the UI comprises of the layout of the application, the buttons, cards, sliders etc, along with their shapes, colors, position etc. It is of course made in Flutter using widgets, such as Containers, Columns, Rows etc.
Ohhh boy this is a biggie. State Management is a wildly, and I mean wildly heated topic in code in general, and no less in Flutter, with countless ways to manage it, and with people who swear by their method on each side.
Now before we get to a solution, it would be better to understand what exactly we are trying to solve.
The state of an app can be thought of as all of the data in an app that changes , and depending on which the UI also changes.
For example, in the default Counter app generated when a new Flutter app is created, the state would be the text that displays the count. It is data that changes, due to which the UI also changes.
Now this change can be triggered by anything from the user pressing a button to an automatic periodic refresh that pulled in new data from a database somewhere.
State is therefore data that changes during the lifecycle of an app, and since multiple parts of the app, located in various different, far away parts of the widget tree might need to share ( that is detect, read and most importantly react/change to ) the same data, it becomes important, and exponentially more cumbersome to pass data around, and react to it.
The purpose of state management in Flutter is therefore to keep track of when state changes, across the app, and to then notify all the widgets that use that state that they need to rebuild.
Most apps, big or small, require state management to a certain degree. As said before, state management is a problem that has a lot of solutions. Also, due to how interconnected state and its management solution is with nearly every other part of an app, it is also very closely tied to your approach to dependency injection, and the architecture pattern you implement, and affects them both.
Some of the more popular approaches to state management, separated by the scales of apps for which they are commonly used, are listed below:
For small-scale apps
For medium-sized apps
For large-scale apps
Note: The in-built approach to state management in Flutter is the Inherited Widget, but it is very low-level in nature, and as such has lots of boilerplate associated with it, along with being hard to understand. As such, it is not explicitly mentioned here.
It is however what Provider and some other state management approaches work with, under the hood.
Ahh yes, Dependency Injection. This is one of the components with the most amount of theory behind it, but in effect, is quite simple.
It is simply providing your classes/widgets/objects/instances with other classes/widgets/objects/instances that they use (and therefore, depend on).
In fact, you may have already been doing this, with the constructors of your classes/widgets/objects/instances.
Well, what problem does dependency injection solve?
Imagine you have your Widget tree, and you need to pass an object, of, say, type DeviceData, that contains the sizing informati]on and device orientation, from the topmost widget, to a widget 7 (widget) layers down.
This means that you would have to code in 7 separate constructors, with the widgets in the middle keeping the object solely to be able to pass it down to the next widget.
This is not a sign of clean, maintainable code.
Furthermore, when you get more advanced with a project, you might want to use abstractions more and more, so that you can switch out the concrete implementation of a class for one more suitable for testing, and that is simply not feasible here if you have to change 7 different constructors to switch out one implementation housed in one part of your code.
This is the problem dependency injection solves. The idea being you can declare all objects or dependencies elsewhere in your code, possibly even using abstractions instead of concrete implementations (so as to accommodate switching out for mock classes), and then access them wherever you need to use them, without the hassle of passing them through constructors etc.
Some of the more popular packages used for Dependency Injection are:
- GetIt ( Not strictly for dependency injection, but also a service locator )
- Provider ( Provider is useful for both dependency injection and state management )
The End (For now)
Well, I think I’ve been rambling on for long enough. As mentioned near the beginning, this article will be part of a series, so while this is the end of this article, it is the beginning of an adventure!
Thank you for reading this far, and I hope you learnt something new!
Some things have been oversimplified for the sake of newcomers, and some I may have gotten wrong myself.
If you have found incorrect information, please do not hesitate to contact me, and I will update this or any other article to reflect the correct information.
To err is human, after all