Skip to content

AntPRams/iMoji

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

iMoji

Exploring Skills: BlissApps evaluation project

Table of Contents

Demo

Getting Started

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.

Usage

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:

iMoji diagram

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.

Tests



Below, there’s a screenshot with the test coverage, the remaining tests are mainly related to UI objects. Check the Roadmap

Screenshot 2023-11-08 at 12 33 54

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

Roadmap

  • 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.

Final thoughts

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!

References

About

Tech challenge app

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages