I found the concept of Orthogonality in The Pragmatic Programmer book. The authors encourage developers to create system that is orthogonal whenever possible. Simply put, orthogonality, as I understand it, means that if one part of the system is changed, other parts are not affected and do not need to be adjusted. One way to apply this is by ensuring that each component created has only one purpose. Even though this lead to having more smaller components, but it makes it easier for developers to work on each component. Changes are not a nightmare to implement because they do not disrupt other components. The system's units will be isolated and independent.
Unconsciously, my colleague and I applied this concept to an internal office product. We named it Time Tracker. It is a mobile application for recording employee task hours that synchronizes with Basecamp (the project management platform used by our office). We used Flutter to build this application. Flutter is a popular framework built on the Dart programming language, allowing us to create applications that can run on various platforms (cross-platform). Both Flutter and Dart are developed by Google. Flutter effectively abstracts the object-oriented programming features of Dart.
At that time, Flutter was new to me. I immediately sought out a comprehensive course to guide us in developing this application whenever we got stuck. It turned out to take quite some time to learn and experiment. Eventually, I told my colleague that we needed to decide how the application would be developed to ensure it would be maintainable in the future.
There were several architectural options, various state management libraries to choose from, and considerations for how the API would be consumed. In the end, I leaned towards the advice of an experienced Flutter developer. Thank you Andrea for your Dart and Flutter courses, this is the recipe that I used:
- Architecture: Riverpod
- State Management: Riverpod
- Important Dart Libraries: GoRouter, OAuth2, Sembast, stop_watch_timer, http
- Design Pattern: Repository
The main goal was Separation of Concerns: separating the user interface code from the business logic and separating the business logic from the data flow.
Minimum Viable Product
This application was required to record how long staff worked on a task. A stopwatch feature with start, pause, stop, and save functionalities was created to meet these expectations. The list of to-dos assigned to an employee in Basecamp would be displayed in the application if the employee logged in using their Basecamp account. Changes on to-do metadata in the application, such as the duration of the to-do, had to be persisted.
The flow is as follows: the user logs in with their Basecamp account, their list of to-dos is displayed, the user starts a stopwatch on a to-do, the stopwatch can be stopped and the elapsed time for that to-do is saved, accumulating the duration from previous ones.
Technical Implementation
This application is supported by a backend for accessing users' Basecamp resources.
The Basecamp API uses OAuth2 to obtain user authorization for accessing their related data. Therefore, the Time Tracker application will direct users to the Basecamp consent page in a browser for login and permission granting. The user will then be redirected back to the application with an Access Token.
The Access Token provided by Basecamp was sent from the Frontend to the Backend to be stored, so that this process didn't have to be repeated until the token needed to be refreshed. At that stage, the Backend had the authority to retrieve the user's To-do data. The Frontend requested the To-do data from the Backend through an API provided by the Backend. The Backend then supplied the requested list of To-dos. Where did this To-do data come from? It was periodically accessed from the Basecamp API by the Backend. Not all To-dos came from Basecamp; our database also stored new To-dos created by users through the Time Tracker application.
The To-do list provided by the Backend needed to be stored on the user's device to keep the number of requests efficient and to facilitate easy processing. This functionality was supported by Sembast.
When a user ran the Stopwatch on a To-do, the application saved each second locally. If the user wanted to switch To-dos or had finished and wanted to save it, the Frontend sent the metadata of that To-do to the Backend, ensuring it was persisted seamlessly.
The Hard Part
Not only user interface.
In my opinion, the challenging part of the MVP that I mentioned earlier is the retrieval and management of user to-do lists. This data originates from the backend's database and is also stored on the user's device in the form of a NoSQL-style database file (.db) that can be managed.
Data's Create, Read, Update, and Delete (CRUD) operations needed to be implemented using Sembast as its interface. There was synchronization between remote and local to-dos to ensure users always had their to-dos in the latest state (without losing progress). This synchronization had the potential to cause conflicts if both data sources underwent changes, which was very tricky.
Valuable Lessons
Coding requires dedication and patience, to help us with that, use the time to rest and think about the product. Understands acceptance criteria and the product owner's expectations for the application, then taking the time to describe how it works will help direct the implementation to be on target. Draw the flow if it is hard to imagine.
It is also crucial to maintain good and healthy communication with the team, especially fellow developers, in my case the scope of the product was large, because other features that we may not need. I learned that collaboration is a key.