4 min reading time
15 Feb 2021
SwiftUI & Combine with TDD
Building AI-Powered To-Do App - Part 1
If you have read my previous article or seen any of my recent posts, you will know I went all-in on TDD this year. I am loving the process and how much more confidence I have in my code since following TDD rigorously.
Rebuilding a Legacy App
As a part of mastering this process, I decided to rebuild one of my App's called Done. It's a very basic to-do app with an AI twist to it -> it analyses input to-do items and extracts verbs/nouns from them, searches the web for accompanying image and attaches the image to a to-do item. The idea is to have a more intuitive experience when listing your items, i.e. when you input "buy bananas" the App will search the web for an image of bananas and show it next to the To-Do item.
I have written the first iteration in Objective-C using Realm as a local persistence framework using MVVM UI design pattern.
Technologies I will use
This time I want to go step forward and write the App to work cross Apple platforms, from iOS, iPadOS to MacOS using Combine, SwiftUI, MVVM UI design pattern and modularised architecture using local storage with CoreData and CloudKit integration so all items can sync seamlessly between all User's devices. For the magical string interpretation, I will use Apple's native NaturalLanguage framework just the same as in the legacy version. I will build the whole App following the TDD process, with red-> green -> refactor development methodology.
Given this is a relatively simple App, I should be able to complete it within a few weeks and post articles with my progress on the way.
Before going ahead and writing code, I have spent some time designing the App architecture and making sure that the business logic is modularised and completely in-depended of UI.
Here is the architecture I came up with
Looking at a diagram like this might seem scary at first, but let's break it down and have a look at the individual modules (separated by double green lines).
At the top, you can see three main modules with its own interfaces (in red): LanguageRecognizer, Database, and ImageFinder. At the bottom is ItemListView as SwiftUI view with the interface called <Store>. In the App, the <Store> interface will be the only bridge between the App's business logic and the UI. I will implement <Store> interface inside ItemListViewModel. This separation will allow me to reuse all of the App's business logic and the view model, while using a custom UI for different platforms.
To declare interfaces(elements in red on the diagram), I will use protocols (protocols with just one method they could just as well be closures, but I will be using protocols here for consistency). The idea of having core modules separated using interfaces is that the protocols provide a contract/agreement/consistent interface between the App and the given module. However, the concrete implementation can be changed at any point and as long as I make sure to keep the interface the same the App will still work.
💡Spotlight: Database module
Looking LanguageRecognizer and ImageFinder modules, you can see they are both relatively simple with an interface, concrete implementation(in yellow on the diagram) and framework dependency(in blue). Next, having a look at the Database module, you can notice it is slightly more complex. <Database> interface has 3 different concrete implementations of it: LocalDatabase, RemoteDatabase and RemoteWithLocalFallbackDatabase (the combination of the two).
Using polymorphism (all three implementations having the same <Database> interface), I can now compose a concrete database in whichever way I like without breaking any other parts of the App. I will start with the LocalDatabase in CoreData, later expand the App with RemoteDatabase using CloudKit and compose the two in RemoteWithLocalFallbackDatabase. RemoteWithLocalFallbackDatabase will use RemoteDatabase to listen for record changes in CloudKit and whenever the network connection fails or returns an error, I will use the local LocalDatabase in CoreData as a fallback. The idea is to build App incrementally one component at a time, and by using polymorphism, I can recompose and improve the database module as I go.
Looking at the top right, I will create an ItemListBuilder that will compose all the modules together at the App start, inject necessary dependencies and show the ItemListView on screen.
Now, let the Done Remastered App building begin!
In the next article, I will build the LanguageRecognizer module of the App following the above diagram and using TDD, stay tuned!
I hope you enjoyed this article and learned something new on the way.