Exploring Skills: BlissApps evaluation project
This project was developed with Xcode 15.0.1 for iOS 17.
I've integrated some of the latest features of Swift 5.9, such as SwiftData
and the new Observation macros. This app was designed with reusability in mind, and above all, I aimed to create a project with a robust foundation.
This project was built using the MVVM architecture with a strong emphasis on dependency injection. All reusable objects implement an interface, allowing the use of mocks in tests or reusing similar objects for the same purposes. For instance:
protocol Service: AnyObject {
associatedtype DataType: Decodable
func fetchData(from endPoint: EndPoint) async throws -> DataType
func fetchImage(from url: URL) async throws -> Data
}
The example above is related to the interface of all services used in communication with the API layer. This approach allow us to maintain a generic interface for all API requests, like this:
class SomeService: Service {
// code…
}
let service = SomeService<SomeModel>()
The main goal was to keep all elements separate while making them as generic as possible. For example, the view is unaware of the existence of the repositories, the view only interacts with the ViewModel. Similarly, the ViewModel doesn't handle persistence; that responsibility is managed by the repository.
Here's a simple diagram depicting the app's layers and how data flows:
To store data, I decided to explore SwiftData
. Currently, SwiftData
works well wthin processes executed on the main thread, especially if implemented in the View layer. However, I aimed to separate concerns, so I'm using a PersistentDataSource
exposed to the @MainActor
. This allows me to execute instructions safely outside the view layer. Unfortunately, SwiftData
is not yet ready for production, particularly when working with more complex data structures.
On the other hand, SwiftData
is simpler and easier to use than CoreData
.
To create a repository, you only need to do this:
let container = try! ModelContainer(for: MediaItem.self)
self.modelContainer = container
self.modelContext = ModelContext(modelContainer)
And then expose the models to the @Model
macro. It's that simple! 💫
However, like SwiftUI, I'm concerned that we might have to wait a few more years (I hope not) before it can be used in production safely.
Below, there’s a screenshot with the test coverage, the remaining tests are mainly related to UI objects. Check the Roadmap
In some tests class's, MainViewModelTests
for instance, you’ll see a lot of:
try await Task.sleep(nanoseconds: 300_000_000)
This was necessary, for now, as there isn’t an easy way to test published properties in classes exposed to the @Observable
macro, as we had with @ObservableObject
and @Published
property wrapper. There is a solution, but to implement it, I had to pollute the viewModel properties with didSet
observers to track the values, or mock every viewModel with loads of boilerplate code. That's why I decided to use Task.sleep
.
You can read more here
- Accessibility: Nowadays, there’s no excuse in a production environment. It’s a fundamental aspect of good design, and it’s crucial to ensure that the apps we work on are accessible to everyone. I didn’t have time to do it, but it’s one aspect that I truly value.
- Logs: They are extremely helpful in a development environment, but this was one of those things that I left behind, and when I wanted to pick them up, well, it was too late.
- UI Tests: As I mentioned before, the app has an 74,5% test coverage, and the remaining tests are mainly related to the UI. It's very easy to test SwiftUI views with just a couple of lines of code. For example, if I wanted to test the error when tapping the search avatar button without text in the search field, I could do something like this:
func test_searchAvatar() throws {
//given
let originTextField = app.textFields[“avatarSearchField“]
let searchButton = app.buttons["searchButton"]
let alertView = app.alerts.firstMatch
//when
searchButton.tap()
//then
XCTAssertEqual(alertView.label, "Please perform a user search with results.”)
alertView.buttons.firstMatch.tap()
}
These are some of the improvements that I would have liked to implement before shipping the app to you, but I didn't want to keep you waiting.
I had a lot of fun developing this app, especially because in our profession, there are few opportunities to use the latest features, apart from our personal projects, but those are also scarce nowadays. Time is not our friend.
I could have used UIKit
and CoreData
to develop the app, but I wanted to take this opportunity to push my boundaries. It wasn’t an easy task, mainly due to the scarcity of documentation and the insufficiency or lack of context in the existing one. However, that's also part of the appeal – the challenge. 😊
I hope you have as much fun reviewing the project as I had while creating it. The business logic is well-documented to assist you in this process.
I’m always available for any clarification, please don’t hesitate to reach out if you need anything from my end.
Thank you for the opportunity, I look forward to speaking with you soon!