I’m working on an iOS application where multiple pages (ex: Homepage, help page) display multiple banners. A banner is like a card with a message and a possible action/link.
For example, there’s an ActivatedMotorBanner
to show a banner if the user has a motor policy with an ACTIVATED status. Another example is a SuspendedPolicyBanner
that is to show when there is a policy SUSPENDED.
I want to implement that logic with clean code, which must be reusable; the same banner can appear on multiple pages.
The problem that I'm facing is that every banner has a different formatting; for example one banner has some parts of the message bold while another has a link to a specific app section.
Inside the domain/entity I have already that struct that represents the user contract.
struct Contract {
let name: String
let type: ContractType
}
enum ContractType {
case Suspended, Activated, Cancelled
}
I add inside the domain a struct for each type of banner I want to implement because every banner has different information. Each banner implements the Banner
protocol)
For example, the ActivatedMotorBanner
has that logic
protocol Banner {
var hasToShow: Bool
}
struct ActivatedMotorBanner: Banner {
let type: ContractType
let vehicleName: String
init(type: ContractType, vehicleName: String) {
self.type = type
self.vehicleName = vehicleName
}
var hasToShow: Bool {
type == .Activated
}
}
while the SuspendedPolicyBanner
has the logic that must be shown if the policy expire date is after today.
struct SuspendedPolicyBanner: Banner {
let expireDate: Date
init(expireDate: Date) {
self.expireDate = expireDate
}
var hasToShow: Bool {
expireDate > Date()
}
}
The first question is: Is it correct to have such information inside the domain/entity folder or that information must be inside the use case? I put inside the domain because every banner has different data
Next inside the USE CASE, I create the use cases for each type of banner. For example, I create the MotoryBannerUseCase
that handles all the motor banners (ActivatedMotorBanner
and others) and the PolicyUseCase
that handles all the banners for generic status of the contracts (for example all suspended contract or all the cancelled contract etc).
class MotoryBannerUseCase: BannerUseCase {
private let contracts: Contract
init(contracts: Contract) {
self.contracts = contracts
}
func execute() throws -> [Banner] {
contracts.flatmap { getBanners(for $0) }
}
// Here the use case know all the banner that handle and pass the data to the banner
// then we filter the banner
func getBanners(for contract: Contract) -> [Banner] {
let supportedBanners = [
ActivatedMotorBanner(type: contract.type),
// ... other motor banner ...
]
return supportedBanners.filter { $0.hasToShow() }
}
}
Next, inside the presentation layer (view model), I get all the banners from the use cases
func load() -> [BannerPresentationModel] {
let banners = MotoryBannerUseCase().execute()
banners.map { banner in
if let bannerMapper = banner as? BannerMapper {
return bannerMapper.map()
}
// Generic BannerMapper without formatting logic or textlink
return BannerMapper(.....)
}
/// ... other use cases ....
return banners
}
The problem here is that every banner has a different formatting logic (for example, some banners have the contract number in bold while others have some part of the text in bold). Moreover, I have some banners with links that can be clicked to navigate. In general I don't want to depend on the formatting logic because can change quickly.
I didn't understand how to format the presentation with clean code architecture. I understand that clean architecture wants to separate the display from implementation logic
In particular, I would like to separate the visualization logic from the model, so I create a BannerMapper
that maps the domain model to the presentation model.
The only solution I found is to have a protocol extension and that extension can live in the presentation module only. Like this but i don't think is scalable.
extension ActivatedMotorBanner: BannerMapper {
func map() -> BannerPresentationModel {
// make formatting logic with bold, fonts etc.
let formattedString = String.formatBold("The policy for the vehicle \(vehicleName) is active", text: "active")
return BannerPresentationModel(
message: formattedString,
link: "See details",
action: { ... }
)
}
}
protocol BannerMapper {
func map(_ banner: Banner) -> BannerPresentationModel
}
Do you have some better ideas to implement that?