Skip to content

Akash-R97/Ricky-Morty

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Rick & Morty iOS

SwiftUI app for the Rick & Morty REST API. Built as a take-home; aimed at a Clean-Architecture layout that's easy to navigate.

Run it

brew install xcodegen
xcodegen generate
open RickAndMortyiOS.xcodeproj

Or from the command line:

xcodebuild -project RickAndMortyiOS.xcodeproj \
  -scheme RickAndMortyiOS \
  -destination 'platform=iOS Simulator,name=iPhone 16' \
  test

Xcode 16, iOS 17 simulator. The .xcodeproj is generated from project.yml and gitignored, so reviewers can read the target layout in one place and there are no merge conflicts on the project file.

No third-party dependencies. Networking, image loading, persistence, and the snapshot-style tests all use Apple frameworks (URLSession, NSCache, URLCache, SwiftData, ImageRenderer).

What's in it

Required

  • Paginated character list, infinite scroll (loads when within 5 rows of the end)
  • Pull-to-refresh
  • Search by name with 350 ms debounce
  • Detail screen with all character fields + the episodes they appear in (batched as /episode/[1,2,3])

Nice-to-haves (all in)

  • Favourites with SwiftData, exposed as an AsyncStream<Set<Int>> so both the list and detail screens stay in sync without polling
  • Offline cache: page 1 of the unfiltered list is written to Caches/CharacterCache/ and served as a fallback when the device is offline
  • Status filter chips (Alive / Dead / Unknown) combinable with search
  • Skeleton/shimmer loaders for the list and detail
  • Dark Mode via semantic colours
  • Render-smoke tests for the row and status badge (no third-party snapshot lib, no recorded PNGs to maintain)

Layout

App/                composition root + entry point
Domain/             pure Swift -- models, use cases, repository protocols, DomainError
Data/               URLSession, DTOs, mappers, repository impls, caches, SwiftData
Presentation/       SwiftUI views + @Observable view models, theme, components

Dependency rule is the usual: Presentation -> Domain <- Data. Domain only imports Foundation (for URL).

The composition root is AppDependencies. It builds the URLSession, repos, use cases, and SwiftData container once and exposes two factory methods (makeCharacterListViewModel, makeCharacterDetailViewModel) plus the image loader. No singletons in production. Tests assemble their own.

Everything that owns mutable state (network repo, episode memo, favourites, image loader, page cache) is an actor. View models are @MainActor @Observable.

HTTPClient is a one-method protocol that URLSession conforms to via an extension. Tests pass a MockHTTPClient instead.

Errors

All transport-level failures are translated to DomainError inside DefaultNetworkService. The presentation layer never sees a URLError, an HTTP status code, or a DecodingError. Mapping:

  • 404 -> .notFound (search shows the empty state, not a generic error)
  • 5xx -> .server(code)
  • offline / lost connection -> .offline (falls back to disk cache for page 1)
  • timeout / 408 -> .timedOut
  • decode failure -> .decoding

The list distinguishes "first load failed" (full-screen retry) from "next page failed" (footer message that doesn't blow away existing rows).

Tests

35 tests, all green out of the box. Run with xcodebuild ... test or Cmd-U in Xcode.

  • NetworkServiceTests -- decode + the five error-mapping paths
  • CharacterMapperTests -- DTO -> domain, including unknown enum values
  • FetchCharactersUseCaseTests -- forwarding, invalid page, error propagation
  • EpisodeRepositoryImplTests -- single/array decode, ordering, memoization
  • SwiftDataFavouritesRepositoryTests -- toggle, AsyncStream broadcast, persistence across instances
  • CharacterListViewModelTests -- load, pagination, debounced search, favourites
  • CharacterDetailViewModelTests -- load, error path, toggle, cancellation cleanup
  • CharacterRowSnapshotTests -- row + status-badge render-smoke

MockHTTPClient lets the network tests run real fixture JSON (see RickAndMortyiOSTests/Fixtures/) without hitting the network. The view-model tests use small actor mocks for the repositories.

Trade-offs / what I'd do with more time

  • iOS 17 deployment target. I leaned on @Observable + SwiftData, both 17-only. Spec said 16+. Easiest fix is an #available shim that falls back to ObservableObject + UserDefaults on iOS 16.
  • Render-smoke instead of pixel-diff snapshot tests. Skipped pulling in a snapshot library to keep the project dependency-free; the views, layout, and stub image loader still execute on every test run, just no byte comparison against checked-in PNGs. Would swap in a real diffing library if I had one approved.
  • No UI tests. Would add an XCUIApplication smoke test that boots the app against a stubbed URLProtocol and walks the list -> detail -> favourite flow.
  • Image loader is in-house. Small actor with request coalescing + NSCache + URLCache. Fine here; would benchmark against Nuke for a real product.
  • Favourites are id-only. A future "Favourites" tab would have to refetch. With more time I'd denormalise the relevant fields into the SwiftData model so it works fully offline.
  • Logging via os.Logger (AppLog.network etc.) is in; analytics/crash reporting/feature flags are not. The DI container is the seam for those.

About

iOS App using Ricky&Morty API

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages