Before we begin with this winding tale, you can experience Caelum right now by yourselves, here for iOS, and here for Android.
The heading here might not make sense, but stick with me here, and I promise it will by the end.
Caelum is a Flutter app I’ve been working on for the past month and a half, and is live, on both the Apple AppStore, and the Google Play Store, a fact I take great pride in, after all the bureaucratic hoops I had to jump through, to do it.
In this article, I’ll tell you all about my journey making this app, and let you in on some secrets along the way.
A few months ago, I was searching around for a good weather app, reading reviews, scrolling through all the offerings, but to no avail. I kept trying though, downloading and going though about 6, none of which I actually liked, and none of which seemed very functional, when suddenly it hit me: why didn’t I build one myself ?
I don’t usually spontaneously decide on making projects, preferring instead to think through about the various different aspects of what I want to tackle first, as I want to build beautiful and most importantly, functional software that can grow and that solves problems faced by people and benefits them, instead of desultory projects that I make and promptly never look at again.
This time, however, was different, because, after all, if I made something that solved a problem I was facing myself, well then there there would be always be at-least one person benefitting from it: me.
So, I drank some coffee and got to work researching the ins and outs of weather apps, how they work, why they work, what logic makes them tick, and most importantly, how I may make one myself.
Most of the research stage this time around was trivial, as I was set on using Flutter, a framework I knew lots about from past experiences, and one that fits this type of application perfectly (Flutter is best for apps, and a little less so for thing that require constant high amounts of screen redraws, such as games), and so I only had to focus on the code related to the functionality of this app itself, not the underlying technologies and frameworks that make it all possible.
A few days of intense research and preparation, followed by a few weeks of intense coding, followed by a few more days of asset creation and name brainstorming, and out popped Caelum, latin for weather.
Under The Hood
Caelum is a weather app, and as such is reliant on live data to function. It therefore includes network connectivity code, the first I’ve ever written actually, to dynamically request, obtain and process live weather data, including of course services for querying a plethora of API endpoints, error handling, connectivity checks, etc.
By far the network part of this project was the most complex, and therefore the most interesting. I learnt a ton about networks, network protocols, – on a tangent read up on IPV4 and IPV6 – IP addresses and how they actually work.
Furthermore, I wanted the app to automatically use the user’s current location for weather queries by default (a pretty standard feature to be honest), and so I had to actually leave the comfort of Flutter behind and dabble in a little native OS code to access the platform specific location APIs that give me information regarding the device’s current location, another first (and a new and exciting one at that!).
Other than that, designing the UI was relatively easier this time around, as I had decided to make mockups of the UI I wanted beforehand (more on this later), even though it was a time consuming task and something I had always neglected before, thinking it a waste of time.Still, I wanted the UI to be beautiful, and while it did take some minor and a few major redesigns to some of its components to really get it just right, I do believe the end result is quite worth the effort.
Having done the planning though, I can honestly say it saves much more time that it requires, making up for the effort required ten times over through the absence of a huge amount of headaches and confusion while actually coding the UI.
Although not entirely required, this app also rigorously follows the MVVM architecture pattern, aided by my own package on pub.dev.
This had two purposes, one to make any future feature updates to this app trivial to implement and connect to the rest of the app, and two, so that I could get some actual experience properly implementing an architecture in an app being made for production.
It also had the added (and unexpected) benefit of rigorously testing my own package, which did turn out some flaws in the code that I plan to fix in the next update for the package.
This was also the first time I had to work with APIs and therefore also JSON in my code, having to write code for JSON serialization and deserialization, and effectively handling the additional application complexity that entails.
Errors, Errors Everywhere
The network logic for this app also had another first for me to experience: error handling that was actually required and a necessity.
The projects I’ve worked till now, error handling was a nice addition, but if the code was right (and the amount of testing I do usually makes it pretty clear if it is or isn’t) the error handling logic would most likely never be triggered, but that isn’t the case for network code, where each network has entirely different bandwidth, speed, and security protocols, and of course, with my code having to deal with real world scenarios which are far from the ideal situation of an IDE: timeouts, no internet connection at all, lag, unheard of random, onetime errors, API endpoints being down, and a whole host of other errors which can and will occur randomly and regardless of how well I structured my code.
Error handling, therefore, took a major chunk of the time I spent developing this app, and in the end, I must say I’m quite proud of the result. The app has safeguards against timeouts and laggy internet, is capable of optimizing requests when it detects a cellular connection to preserve data, and caches the data it does receive in order to minimize bandwidth usage.
There is also a whole host of other scenarios, all of which have safeguards built for, including Socket exceptions and endpoints returning errors and errors querying the data itself.
The wide variety of possible errors and the multitude of ways they may be triggered made it quite difficult to properly test the code, especially when it came to edge cases, but through some tedious mocks and messing about with bandwidth throttlers, I did finally manage to overcome this problem.
This backend system, starting from user input and extending till a formatted request is sent to the server, a response is received, and that is parsed and checked for 6 different branches of errors, is quite understandably very complex, but also rigorous in its methods and fallbacks (The app may have bugs and may have glitches, but one thing I decided early on is that the app must not, cannot and will not break in the hands of users).
By the end, I therefore had a complex, rigorous, custom-built, efficient and most importantly, secure system for querying a plethora of API endpoints. So of course, the next natural step was ripping it out, generalizing some aspects and open sourcing it as a package on pub.dev. :-)
As I talk about in the next section, a lot of what I implemented in my own app was aided by others who came before me generously open sourcing their solutions and their complex systems for dealing with these problems, even when they could have easily kept it to themselves, and in some cases even profit greatly from having done so.
They however, did not do so, and the world is tangibly better for it, and I am standing here with a finished project because of it. This therefore, was my small way of saying thank you to them, and giving back to the community whose help I leveraged at every step.
On The Shoulders of Giants
Network code is usually tough, with a lot of boilerplate code and edge cases and errors and copious amounts of asynchronous logic needing to be handled to implement even trivial looking functionality, and so I was quite intimidated when I first started work on the internet dependent features in my app.
While this still remained for the most part true in my experience, as I soon came to realize, a lot of very talented people have faced these same problems I was now tackling in my app, and so I had access to their very generously open sourced solutions to these commonly faced problems, in the form of some quite high level (and therefore relatively easier to use) abstractions, and these greatly helped cushion my first foray into network code, reducing some of the boilerplate code required, and making the entire process a lot more intuitive.
Even though most if not all of the connectivity logic for this project is already complete, and even though it was quite frustrating at times, I must say I’ve had an overall positive experience writing it, and will probably integrate internet connectivity and network functionality in some form in at least a few of my other projects.
I also designed the backend system to request data from multiple API endpoints, and to average and smooth out the data it receives from them all before presenting it to the user, so that the data is (in theory) more accurate.
This system was, as I said before, incredibly complex, exacerbated by the fact that I was the one solely responsible for both designing it, finding its flaws, correcting them and then implementing the entire thing. However, I must say, it was also immensely fun to work on, as I had a clear goal, and could therefore accurately measure whether the code I had written was driving me closer toward or farther away from where I wanted to go, which made assessing the progress of the system if not its state an absolute breeze. Also I quite enjoy making these self-contained, blackbox-esque systems, so there’s that.
Other than these meaty network bits, the one other thing that took most of my time was designing an effective Low Data Mode, the complexity of which I took for granted at first. Turns out, turning working code that is relatively efficient into working code that is hyper efficient is not as straightforward as it seems. In hindsight though, yeah thats probably pretty accurate everywhere.
Now of course, I didn’t have to implement a low data mode, but part of my original vision for this app was people suing it in picnics and hikes, so if I could work extra to create a product that better fit that original vision…. well lets just say I was going to do it no matter what.
Regardless, after about an extra week of working on it, I had an effective solution, where at the flip of a switch the system switches from more complex, power hungry calculation methods (that give more accurate results) to obsessively streamlined, efficient computation methods that while quite a bit more roundabout and less accurate make up for it immensely in terms of bandwidth and battery usage, by my rough testing (about 350 ish tries) suggesting up-to a 24% reduction in bandwidth usage and up-to a 47% reduction in battery usage.
While the system could still use some polish and I’m pretty sure I could get the percentages higher, this seems good enough for a V1, and so thats where I’ve left it for now.
The rest of my code was relatively straightforward, with a small, key-value pair local database made to store user preferences (such as preferred units and network request settings) and such, and a huge focus towards designing a beautiful, intuitive and smooth UI, which I do believe I’ve been able to accomplish.
Overall this project was extremely interesting to work on, and taught me most about connectivity and network code, which is vital to how most things run in today’s interconnected world, and quite a bit about the vast array of benefits stemming from going into a project with proper planning, and I’m certain the skills and lessons I picked up along the way will be useful in future projects.
Behind The Scenes
Reading this article however, especially if you’re on the more experienced side, you may have been thinking that all of this was sounding a little bit too easy. Well, I am here to tell you, your suspicion was well founded.
And that brings me to secret numero uno. Caelum, is not the first app I’ve made. It’s not even the second. If I want to look good (and still be at least somewhat truthful) I’d say Caelum is the third app I’ve made, but technically and more accurately speaking, its more like the eighth.
Let me explain.
Where It began
I started on this journey, making apps, in the winter of ‘18. I had spent a lot of time, reading up on and comparing app frameworks, having known absolutely nothing about them before, meticulously documenting their features and trying to compare between several things neither of which I had any ideas about, and eventually (and quite luckily I might add) stumbling onto Flutter, and the language it used, Dart.
After quite a bit of deliberation, and a huge number of Youtube videos, I had picked Flutter from among its competitors (mainly React Native and Ionic, if I remember correctly) as the framework I wanted to learn and make my app in.
Going native was something I had long since discarded, so these 3 were the only real options I had, and as Flutter was what seemed most intuitive, and with it being backed by Google, that is what I decided on in the end.
I had started and finished several small coding projects before this, but the scale and complexity of what I was gearing up to tackle was really something I had had no prior experience with, and so it was quite akin to learning the basics of how to code all over again.
Beginning to learn how to code is the part I’ve come to realize is always the hardest, and predictably, I struggled, trying to learn on my own from an ever-growing web of resources, without any structure or guidance.
Building an app felt like such a distant thing back then (and it indeed would be, but perhaps not in the ways I most expected), that I naturally began to lose motivation, as the days passed without much progress.
In hindsight though, I realize I was making progress, at a moderate pace too, but without much in the way of visual feedback to mark it (I had decided to learn the language, Dart, first and then Flutter, the framework, resulting in less graphical output) my mind had started to experience Imposter Syndrome for the very first time, with absolutely nothing to even identify it, much less keep it in check.
Those first few months were rough, maybe even the roughest of them all (although that title certainly has no shortage of competitors to this day), and as the days started to turn into weeks and the weeks into months, and with winter vacation ending, progress became slow, and more sporadic, with me having no shortage of stuff vying for my time, from games and movies to distract from the frustration of not being able to code, to the much more important studies that I would have to resume once more as I started school again.
It took me quite a bit of time, coding on and off over the course of about half a year before I thought I had enough skills to tackle the app I had started learning all of this for in the first place. I had, by this time, also made about 4 other much smaller apps in various stages of completion, as learning projects.
So, with renewed enthusiasm stemming from my goal suddenly seeming so much more closer, I started working on that app. It failed.
Failure is much less advertised, but also a much more crucial learning process than many expect.
This was a lesson I had to learn the hard way, over and over again as I continued my journey.
But first, how my app, my beautiful, ambitious project in Dart and Flutter never saw the light of day:
It was about 4 months in, all of it filled with meticulous work, when I could no longer deny that the code I had written had gotten too complex and too large to maintain or work reasonably well on, that the number of bugs was increasing exponentially with every new line of code I wrote (a fault caused by my sore lack of any sort of planning or experience) and that, despite my best efforts and the very best of intentions, the project was sinking under the crushing weight of all the half-finished things I was tasking it with.
I had expected a lot of different things going in, from features too complex for me to implement to not having enough time to finish it, but the idea that the code was all there, most of it seemingly working, but the entire whole somehow still broken and becoming more and more unsustainable with each new line, and me having to let it go as a result, that, had never once occurred to me.
I had failed to see, didn’t want to see, what was happening, what I was doing, as I so desperately didn’t want the project to die. Even sitting there, looking at the rows upon rows of files, about 70,000 lines of code in all, I refused to cut my losses and move on.
Furthermore, the way I had written the code, the features weren’t modular (again, due to a lack of any real planning and the project not having any sort of structure behind it) , and so any hopes of me cutting out a minimum viable product out of this gigantic pile were also quite slim, with a gigantic amount of work having to be done to be able to extract anything remotely useable on its own out of the project, and even then the end product would most likely offer only very, very limited functionality.
To this day, however, I don’t fully accept that the project is a lost cause, and I like to tell myself I’m taking an extended hiatus and will eventually get back to it, cutting out most of it and starting again. And I might. But it’s not likely. I can say that, but I still can’t seem to let it go.
After that, I decided to focus on things other than coding for some time, and after taking quite a big break, and with renewed vigor, started working on other, smaller projects, including this website, a game (a project I’ve scrapped and restarted about 4 times already, but that’s a story for another day), another app, and a reinforcement learning project I’m quite excited about.
That app, alas, also failed, but thankfully not as spectacularly however, and quite early in development, as I realized functionality that I had thought easy to implement was in fact quite impossible without a fully fledged backend supporting it, something I not only couldn’t do, but also wasn’t worth it for the small scale of this project.
So why do I say all of this in the article for an app I’ve already released ?
It’s because, and I assure you it feels just as weird to say it as I imagine it must to hear it, I’ve come to be glad for the way I learned to code, including every single failure along the way.
Failure is highly stigmatized, but it is an inevitable and vital part of the learning process, and the more we learn to embrace it and learn from it, instead of fearing it and running from it, the better we become and the closer we get to our goals
It is no understatement to say that I learned more failing to make these apps then I’ve learned succeeding in many things in life. Doubly more so because I failed, as that resulted in a very healthy dose of humility and understanding about the consequences of my decisions to go along with all the new technical things I had learned throughout.
That first app taught me about state management, object oriented programming in practice, dependency injection, data persistence, data structures, asynchronous data query and handling, error handling and data sanitization, complex data manipulation, algorithms, software architecture paradigms (a wide variety of them too) and a whole host of other things, most of which I had known absolutely nothing about previously.
The second app, although a shorter journey, taught me about scalability and scaleable systems, about the benefits of designing software beforehand and not just making it bit by bit, on the spot, about backends and cloud backend communication, and most of all, how to have a rough estimate of the scale of the system you’re working with, and realizing when it was too big to handle for your team (or in my case, just me).
These lessons, learned incrementally and practically through projects I was extremely interested in, and reinforced by the trial by fire-esque environment in which I had to learn, have already served me well, across many other software projects using wildly different technologies, and even in other aspects of life.
Again, you may ask, why do I say all of this in the article for an app I’ve already released ?
Well I can finally answer. Because, when, a month and a half ago I fired up my editor and set up my environment and my IDE, by the time, the new default Flutter project that would become Caelum had loaded in, I was no stranger to the rows of buttons and tabs and columns of files and lines of code that greeted me.
Compared to those first few apps, I had a proper plan, had accounted (roughly) for unexpected delays, had a proper architecture for the app already in mind, along with a rough list of the dependencies required, a rough idea of the file and folder structure, mental models of some the classes and widgets that would form the base of the app and even pre-made mockups for how I wanted the app to look, something I had never done before and had come to regret and realize the importance of.
I also had a rigorously documented list of requirements and features for a minimum viable product and also the features I wanted to implement in subsequent updates after the initial launch, all of this done in an effort to curb feature creep and confusion down the road, both of which I had extensively faced before in the making of my previous apps, and as such was determined to prevent in this one.
My point being, the project may have been started from scratch and the total time till release been only a month and a half, but I was already decently experienced if not decently skilled when I started the project, and this is the case with many projects showcased across the internet.
And that brings me to my fourth and final secret:
People don’t usually showcase their failures, the half completed projects, the ideas that never passed the drawing board (neither do I, if I’m being honest) because, after all, what would be the point of that ?
But in doing so, newcomers facing their own half completed projects, their own ideas that don’t pass the drawing board feel a major disconnect from everyone else in their respective communities, suffering from unjustified and rampant Imposter Syndrome and an all encompassing feeling of not being “good enough”, as I did and still sometimes do.
This app was the culmination (and hopefully only the start) of more than a year and a half’s worth of hard work, with a healthy amount of failure sprinkled throughout, but usually not just any failure though, the good kind of failure, the kind you learn volumes from and that you’re glad for looking back.
It was also the first time I felt under control in a project. Of course there were unexpected bugs and unexpected problems cropping up from time to time (then again when is there not), but what surprised me was that each time I had a pretty good idea of why it was occurring (having experienced it many times before) and could jump into solving it quickly and efficiently, and without much stress or frustration.
And for most people on this path, or any other like it, that will more or less be the case, because, after all, the only real way to get really good at something is to be really bad at it first.
So, if you take anything from this article, take this:
Know that time you spend improving yourself is always invaluable, and know that time spent doing what you love is never wasted time
The failures will come and go, and if you have a good attitude and a curious mind, you will be stronger for it.
Embrace them as opportunities to learn and grow both yourself and your craft, and the challenges will transform from frustrating hindrances to interesting puzzles, the inevitable failures from somber experiences to treasure troves of knowledge you can call on in future endeavors.
And with that, I bid you farewell.
Thank you for reading, and have a lovely day!