This is a funny ios application that creates a lottery between defined players. It demonestrates how we can achieve clean architecture in an iOS app.
"Clean Architecture", is a word you must have heard many times so far, in articles, job descriptions, in books, and many places. It's like a magic word that glues the app architecture with a clean way to define our units. In this article, we try to get down from dozens of high-spectrum concepts to real implementation that we can use in our daily life to have more extensible apps with a proper architecture.
Let's first talk about the specification of the app we want to demonstrate the clean architecture in it.
As a user of the application:
- I can tap on the "Start the Lottery" button.
- Then it will show me the loading indicator while the app fetches the data from the web services.
- Then I can see the random name in the lottery list flipping
- After 10 rounds the screen stops and draws the last name candidate for winning the lottery.
- Unless the user is not "void" the drawn name is the winner. If the user is "void" the lottery doesn't have any winner!
The data used for this application comes from two different endpoints.
The user list can be fetched via the Users API: http://novinfard.com/API/SimpleLottery/users.json
{
"items": [
{
"userId": 10,
"name": "Jack Alexy",
"username": "JAlex",
"regDate": "2020-10-19"
},
{
"userId": 22,
"name": "Mr. Hashemi",
"username": "Hashaaa",
"regDate": "2019-08-19"
},
{
"userId": 4,
"name": "Mack",
"username": "mackMack13",
"regDate": "2018-10-02"
},
{
"userId": 18,
"name": "Pack",
"username": "packPackDom",
"regDate": "2018-04-12"
},
{
"userId": 182,
"name": "Mohsen",
"username": "AghaMohsen",
"regDate": "2020-04-12"
}
]
}
It contains the user id, full name, username, and registration date of the user.
The other endpoint gives us the list of user ids who have been participated in the lottery: http://novinfard.com/API/SimpleLottery/usersInLottery.json
{
"lotteryList": [
{
"userId": 10,
"void": false
},
{
"userId": 22,
"void": false
},
{
"userId": 4,
"void": false
},
{
"userId": 182,
"void": true
}
]
}
The void is a boolean indicator for "void" users, if the name is drawn for them, the lottery has no winner.
The flow of the app is demonstrated in the following diagram:
As you can see user first the landing view (1) and by pressing the button the game starts. It firstly displays the loading view (2) when it tries to fetch the data from the two mentioned APIs and merge it into a list of lottery player data that the app expects to handle. In the next stage, the name of the player in the lottery appears on-screen randomly in 10 rounds (3). Finally, the last name will be drawn as the winner of the lottery if the choice is not a "void" one (4).
The proposed clean architecture in this article is derived from Uncle Bob's view on the software architecture [1] with modifying points for mobile applications' usability [2].
The Repository is the most underlying component in the clean architecture. It encapsulates and abstracts the critical logic required for accessing most underlying elements like database, network, third-party libraries, and iOS SDK frameworks and shared data and service elements (such as Facade and Service design pattern interfaces). In some articles, they call this layer "Entity" as well.
The business logic of the app is mainly represented in Use Case. It maps the raw data coming from Repository to app domain data structures. It knows how, when, and which repositories should combine to construct elements meaningful to the app. All changes in the containing repositories should get observed and propagate to the upper-level component, Presenter.
You may have heard about Interactor in VIPER and RIBs and VIP architecture. It encapsulates the business logic for the specific view or a group of views. This is a responsibility that Use Case does in the proposed architecture and refers to the almost same thing.
The main responsibility of the Presenter is handling changes that occur to the view state. It observes the data changes in the underlying layer, Use Case, and maps it to corresponding view models. In addition, it gets the user action from the upstream layer, View, and responds to these user requests.
An important note is that the presenter is UI agnostic. Thus, we shouldn't see any dependency on UI frameworks such as UIKit or SwiftUI in the presenter layer.
All UI components that we use in the app are kept in the View layer. They are free from any business logic. They also constructed in the way that the Presenter, the underlying layer instructed them to in the view model. Moreover, all user actions in the view should transfer to the presenter and the view itself should not decide on any reaction and response in these cases.
The clean architecture layers of the app has been visualised in the below diagram:
[1] https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html
[2] https://crosp.net/blog/software-architecture/clean-architecture-part-2-the-clean-architecture/
Soheil Novinfard - www.novinfard.com
This project is licensed under the MIT License.