5.1 KiB

Deep linking: Wikipedia

App

The ultimate purpose of this application is to open the Wikipedia app in the right location when a user tap on one of the locations listed in the Locations app.

Of course, to accomplish such goal the app therefore shows a list of locations (which are either fetched from a remote server or created by the user) and also, allows the user to add new location coordinates to this list by selecting them from a map.

Features

In its current state, the Locations app does:

  • fetch locations from a remote server;
  • handle loading, loaded and error states reactively when loading data;
  • add locations manually to the list by obtaining locations from map;
  • clean map of selected location if required;
  • open the Wikipedia app when location is selected from a list;

While the Wikipedia app does:

  • open a location in the right position on the map of Places screen from a deep link;

Design

Implementation

This application was built as a UIKit application given that the assignment explicitedly indicates the app should target the iOS platform (iPhone app only, in this particular case) and also, because it disallow the use of the SwiftUI framework. It is out of discussion that the UIKit framework has been battle-tested for over a decade now and Apple keeps updating and adding features to it on a regular basis. The imperative nature of the framework, which is based in implementing how the iOS platform UI components should work, is perfectly suitable the developer who want absolute control over the UI, at a cost of maintaining platform-specific, more complex codebases.

With regards to the choice of framework to built this app, it also comes the question of the type of architecture pattern to use in it: for this particular case, and given the limitations of how the view controllers have been defined in the Apple platforms, MVVM-C is the chosen architecrture as it facilitates the separation between logic and UI components while also, decoupling the navigation logic by using coordinates (and routers) from them.

Now that design patterns have been mentioned, in this exercise some well-known patterns are being used in some degree. For example, the Singleton pattern is used to initialise the PersistenceService service in the Persistence library. Both public and internal Interfaces that either describe an entity or how the entity should behave are used throughout this codebase, as this pattern is essential to create decoupled components that can be easily plugged as dependencies whenever needed as this forces the developer to think about (single) responsibilites and, as a consequence, these components are also easy to test in isolation as mocks, stubs and spies can be easily created out of them. Last, but definitely not least, the Use cases are a pattern from Android that basically execute a function based on some given input, and provides an output after that particular function is finished. This pattern is particularly useful to encapsulate in a simple way some certain logic from view models.

This application was built with scalability in terms of the codebase in mind, which tries to address how this codebase could grow in a controllable, organised manner. For this very reason, this application uses the Swift Package Manager (or simply SwiftPM) to define the Core, Dependency, Persistence and Remote libraries of the Library package, that the Locations target should use extensively. These libraries focus on a specific purpose, and they can be self-contained, like in the case of the Persistence library that contains its own CoreData data model definitons and respective assets inside. Packages could use 3rd party dependencies if needed. This approach forces the developer to think about actual separation of concerns, as the different dependencies are grouped as independent, reusable building blocks, and to move the code into the SPM packages out of the main app target, reducing compiling time and overall weight of the application.

As a (indirect) consequence for the use of SwiftPM packages, the dependency injection pattern comes into question. I implemented my own simple DI mechanism that uses extensively the dynamic property wrapper functionality in the last versions of the Swift language rather than using a 3rd party dependency for this case.