YoullNetwork
The dual-transport networking layer providing GraphQL and REST APIs, token management, caching, and data models for the entire Youll platform.
Why YoullNetwork
Every screen in the platform needs data from the backend: content feeds, user profiles, journey progress, event schedules, meal plans, and more. YoullNetwork is the single module that handles all of these network operations. It sits at the bottom of the dependency graph as a leaf module with zero Bricks dependencies. Every other module depends on it, but it depends on nothing from Bricks.
YoullNetwork provides two transport layers: Apollo GraphQL for complex, structured data queries and REST for simpler operations like user registration, content likes, and file handling. Both transports share the same token management, error handling, and configuration. API classes choose which transport to use per method, and consumers never need to know or care which one is being used under the hood.
The module also owns the platform's data models. Raw API responses are parsed into internal response models, then transformed into public domain models (prefixed BN* or YN*) that every other module consumes. This two-tier model system keeps parsing logic isolated from business logic.
Architecture Overview
All network operations flow through BricksNetwork.shared, which is created once during the bootstrap sequence via Config.setup(). The dual-transport architecture looks like this:
BricksNetwork.shared
├── Apollo GraphQL
│ ├── ApolloMission (query/mutation wrapper)
│ ├── LandingModule (JSON parsing bridge)
│ ├── Response Models (DecodableResponse protocol)
│ └── 55 queries, 23 mutations, 22+ fragments
│
└── REST (async/await HTTP methods)
├── get / post / patch / put / delete
└── URL construction + token injectionWhen each transport is used:
| Transport | Used For | Examples |
|---|---|---|
| GraphQL | Complex data queries, feed sections, content with relationships, batch operations | Content queries, home/explore feeds, journeys, events, meals, playlists, search, paywall config, quiz, activity, explore tags |
| REST | Simple CRUD, user management, file-related operations, tracking | User registration/profile, likes/dislikes, badge earning, timer sessions, tracking sessions, promo offers, account deletion, vector search |
Some API classes use both transports. For example, ContentAPI uses GraphQL for content queries but REST for likes, timer sessions, and vector search.
BricksNetwork Entry Point
BricksNetwork is the central networking client. It is created via a static factory method and accessed as a singleton:
// BricksNetwork.swift
public class BricksNetwork: @unchecked Sendable {
public nonisolated(unsafe) static var shared: BricksNetwork?
let environment: Environment
lazy var apollo: ApolloClient = makeApolloClient()
lazy var cachePolicy: CachePolicy = .fetchIgnoringCacheCompletely
weak var dataSource: BricksNetworkDataSource?
var queue = DispatchQueue.global(qos: .userInitiated)
public var extraLang: String = ""
public var coreDataStore: CoreDataStore
public static func buildNetwork(
environment: Environment,
dataSource: BricksNetworkDataSource? = nil,
extraLang: String? = nil
) {
BricksNetwork.shared = BricksNetwork(
environment: environment,
dataSource: dataSource,
extraLang: extraLang
)
}
}Environment
The Environment struct holds the two endpoint URLs:
public struct Environment {
let endpointURL: URL // GraphQL (Hasura) endpoint
let restURL: URL? // REST API base URL
public init(apiUrl: URL, restUrl: URL? = nil)
}The customer app provides these URLs through its Keys struct during the bootstrap sequence. See Bootstrap Sequence for the full initialization flow.
BricksNetworkDataSource Protocol
The BricksNetworkDataSource protocol is the contract between the networking layer and the authentication system. The customer app (via Config) implements this protocol to provide token management:
public protocol BricksNetworkDataSource: AnyObject {
typealias Completion = (_ token: String?) -> Void
var isLoggedIn: Bool { get }
var accessToken: String? { get }
var currentUserInfoHeaders: UserInfoHeaders { get }
func requiresTokenForNetworkRequest(operation: String) -> Bool
func refreshToken(completion: Completion?)
}| Property/Method | Purpose |
|---|---|
isLoggedIn | Whether a user is currently authenticated |
accessToken | The current JWT access token (nil if not logged in) |
currentUserInfoHeaders | Headers containing user ID, device ID, and role |
requiresTokenForNetworkRequest(operation:) | Per-operation decision: does this request need authentication? |
refreshToken(completion:) | Called when the token is expired; must provide a fresh token via the callback |
requiresTokenForNetworkRequest is what enables anonymous browsing. When it returns false, the request proceeds without a bearer token. The interceptor still sends x-hasura-role: guest headers so the backend knows the request is unauthenticated. When it returns true and no token is available, the request fails with missingAccessToken.
Token Management
YoullNetwork handles JWT token validation and refresh transparently. Consumers never need to manage tokens directly.
JWT Validation
The String.isValidJWTToken extension decodes the JWT payload and checks the exp claim:
// Utils/String+Token.swift
extension String {
var isValidJWTToken: Bool {
// Decode base64url payload, extract "exp" claim, compare to Date()
}
}If the token is expired, the interceptor automatically triggers a refresh before proceeding with the request.
GraphQL Token Flow
The UserManagementInterceptor is an Apollo interceptor that runs before every GraphQL request:
class UserManagementInterceptor: ApolloInterceptor {
enum UserError: Error {
case noUserLoggedIn
case refreshTokenFailed
}
func interceptAsync<Operation: GraphQLOperation>(
chain: RequestChain,
request: HTTPRequest<Operation>,
response: HTTPResponse<Operation>?,
completion: @escaping (Result<GraphQLResult<Operation.Data>, Error>) -> Void
) {
// 1. Check if this operation requires a token
if dataSource?.requiresTokenForNetworkRequest(operation: Operation.operationName) == false {
// Add token if available (optional), add user info headers, proceed
chain.proceedAsync(...)
return
}
// 2. Token required but missing -> error
guard let token = dataSource?.accessToken else {
chain.handleErrorAsync(UserError.noUserLoggedIn, ...)
return
}
// 3. Token valid -> add and proceed
if token.isValidJWTToken {
addTokenAndProceed(token, ...)
} else {
// 4. Token expired -> refresh, then retry
refreshTokenAndRetry(...)
}
}
}REST Token Flow
REST methods use the same BricksNetworkDataSource but handle token refresh inline via async/await:
private func createURLRequest(token: String?, path: String, ...) async throws -> URLRequest? {
var accessToken = token ?? dataSource?.accessToken
// If token required but missing, throw
if accessToken == nil && requiresToken {
throw NetworkError.missingAccessToken
}
// If token expired and required, refresh via async continuation
if let isValidToken = accessToken?.isValidJWTToken, !isValidToken && requiresToken {
accessToken = await refreshToken()
}
// Build request with token and headers
...
}Request Headers
Each transport sends a different set of headers:
| Header | GraphQL | REST |
|---|---|---|
Authorization: Bearer {token} | Yes (via interceptor) | Yes (via createURLRequest) |
x-hasura-role: user/guest | Yes | No |
x-hasura-user-id: {id} | Yes (if logged in) | No |
device-id: {deviceID} | Yes | No |
X-Youll-Platform: ios | No | Yes |
X-Youll-App-Version: {version} | No | Yes |
X-Youll-User-Id: {id} | No | Yes |
Content-Type: application/json | No (Apollo handles it) | Yes |
The UserInfoHeaders struct constructs the GraphQL headers. It automatically sets the role to "user" or "guest" based on whether a user ID is present:
public struct UserInfoHeaders {
let userID: String?
let deviceID: String
let role: String // "user" if userID != nil, "guest" otherwise
let appVersion: String?
public func getUserInfoHeaders() -> [String: String] {
var headers = ["x-hasura-role": role, "device-id": deviceID]
if let userID { headers["x-hasura-user-id"] = userID }
return headers
}
}GraphQL Layer
The GraphQL layer wraps Apollo iOS (v1.15.0+) with two internal classes: ApolloMission for executing operations and LandingModule for parsing responses.
ApolloMission
ApolloMission provides static methods that wrap Apollo's fetch and perform calls, returning Combine Future publishers:
final class ApolloMission {
typealias Response<T> = Future<T, Error>
// Query returning array of Decodable items
static func launch<Q: Query, T: Decodable>(
query: Q,
into network: BricksNetwork,
cachePolicy: CachePolicy? = nil
) -> Response<[T]>
// Query returning array via DecodableResponse transformation
static func launch<Q: Query, T: DecodableResponse>(
query: Q,
into network: BricksNetwork,
cachePolicy: CachePolicy? = nil,
landingModel: T.Type
) -> Response<[T.Model]>
// Combined query returning single Decodable object
static func launch<Q: Query, T: Decodable>(
combinedQuery: Q,
into network: BricksNetwork
) -> Response<T>
// Void mutation (fire-and-forget)
static func execute<M: Mutation>(
mutation: M,
into network: BricksNetwork
) -> Response<Void>
// Mutation returning array
static func execute<M: Mutation, T: Decodable>(
mutation: M,
into network: BricksNetwork
) -> Response<[T]>
}The void mutation variant (execute(mutation:into:) -> Response<Void>) always returns .success(()) without checking the GraphQL response for errors. Mutations like SetViewedContentMutation and DismissHomeSliceMutation will appear to succeed even if the server returned an error. Keep this in mind when debugging mutation failures.
LandingModule
LandingModule bridges Apollo's raw GraphQLResult to the application's Decodable response models. It extracts Apollo's DataDict, converts it to a standard JSON dictionary, then decodes it:
final class LandingModule<T: Decodable> {
static func parseQuery<Q: Query>(_ query: Q, result: QResult<Q>) -> Output<[T]> {
// 1. Check for GraphQL errors
if let error = resultData.errors?.first {
return .failure(NetworkError.operationFailed)
}
// 2. Convert Apollo DataDict to JSON
guard let json = resultData.data?.__data.convertToJSON() else {
return .failure(NetworkError.invalidResponse)
}
// 3. Decode JSON to [T]
return decodeObjects(from: json)
}
}The DataDict.convertToJSON() extension recursively converts Apollo's internal data representation to standard [String: AnyHashable] dictionaries that JSONDecoder can handle.
DecodableResponse Protocol
The DecodableResponse protocol defines the transformation contract between response models and domain models:
protocol DecodableResponse where Self: Decodable {
associatedtype Model: Any
func model() -> Model
}Response models conform to this protocol to provide a model() method that creates the corresponding public domain model. The Array extension provides mapToModels() for batch transformation:
extension Array where Element: DecodableResponse {
func mapToModels() -> [Element.Model] {
return self.map { $0.model() }
}
}GraphQL Operations Catalog
The auto-generated GraphQL/ package contains all operations against the Hasura backend:
| Type | Count | Examples |
|---|---|---|
| Queries | 55 | GetContentQuery, GetHomeScreenSectionsQuery, GetJourneyQuery, GetAllEventsQuery, ApplySearchQuery, GetPaywallConfigQuery |
| Mutations | 23 | SetViewedContentMutation, CreatePlaylistMutation, InsertQuizAnswersMutation, RegisterTransactionMutation, SetEventResponseMutation |
| Fragments | 22+ | ContentFields, AssetFields, CategoryFields, JourneyField, PaywallConfigFields, MealContentFields |
Operations are auto-generated from .graphql files in Resources/GraphQL/ against the schema in Resources/Schema/Youll.graphqls. The schema uses a @cached directive for caching hints.
REST Layer
The REST layer provides typed async/await HTTP methods directly on BricksNetwork:
extension BricksNetwork {
// With return value
public func get<T: Codable>(path: String, queryParams: [String: String]? = nil) async throws -> T
public func post<T: Codable>(token: String? = nil, path: String, body: Data? = nil) async throws -> T
public func patch<T: Codable>(path: String, body: Data? = nil) async throws -> T
public func put<T: Codable>(path: String, queryParams: [String: String], body: Data?) async throws -> T
public func delete<T: Codable>(path: String) async throws -> T
// Void variants (no return value)
public func get(path: String, queryParams: [String: String]? = nil) async throws
public func post(token: String? = nil, path: String, body: Data? = nil) async throws
public func patch(path: String, body: Data? = nil) async throws
public func delete(path: String) async throws
}All REST methods follow the same pattern: construct a URLRequest via createURLRequest (which handles token injection and headers), execute via URLSession.shared, validate the HTTP status code (200-299), and decode the response.
For empty responses, the EmptyResponse struct is used as a passthrough type:
public struct EmptyResponse: Codable { }API Classes Catalog
All 18 API classes extend ApiClient and provide static methods. The ApiClient base class defines a single type alias:
public class ApiClient {
public typealias Publisher<T> = AnyPublisher<T, Error>
}| API Class | Domain | Transport | Key Methods |
|---|---|---|---|
| ContentAPI | Content, categories, subcategories, favorites, reviews | Mixed | getCategories, getContent, getFavoriteContent, likeContent (REST), sendTimerSession (REST), getContentsVectorSearch (REST) |
| UserAPI | User profile, registration, badges, notifications | Mixed | registerNewUser (REST), getProfile (REST), getEarnedBadges (REST), deleteUserAccount (REST), updateProfilePicture (REST) |
| FeedAPI | Home, explore, and search feeds | GraphQL | getHomeFeedSections, getExploreFeedSections, getSearchFeed, dismissHomeSliceMutation |
| JourneyAPI | Linear and timeline journeys | GraphQL | getJourney, getLinearJourney, setActiveLinearJourney, restartJourney |
| ActivitiesAPI | Workouts, activity programs, feedback | GraphQL | getActivityPageSlices, setWorkoutCompleted, getNextWorkout, updateActivityFeedback |
| ExploreAPI | Explore tags, tagged items | GraphQL | getExploreTags, getExploreTag, getExploreTaggedItems |
| EventsAPI | Events management | GraphQL | getAllEvents, getEventById, getUserEvents, setEventResponse |
| LibraryAPI | Playlists management | GraphQL | getPlaylists, createPlaylist, addContentToPlaylist, deletePlaylist |
| MealsAPI | Meal programs, details | GraphQL | getMeals, getMealDetailCards, markMealAsChecked |
| SearchAPI | Search operations | GraphQL | getSearchFilters, applySearch |
| QuizAPI | Quiz operations | GraphQL | getQuiz, insertQuizAnswers, saveQuizAnswers |
| PaymentsAPI | Transaction registration | GraphQL | registerTransaction |
| SettingsAPI | App configuration queries | GraphQL | getPlayerConfig, getOnboardingConfig, getQuizConfig |
| ConfigAPI | App config, social proof | GraphQL | getAppConfig, getSocialProofDetails |
| FacilitatorsAPI | Facilitator details | GraphQL | getFacilitatorByID, getFacilitatorsByIDs |
| PromoOffersAPI | Promotional offers | REST | getPromoOffers |
| WidgetsAPI | Widget configuration | GraphQL | getWidgetConfig |
UserAPI holds a static currentUser publisher (CurrentValueSubject<YNProfile?, Never>) that bridges the network layer to the service layer. When getProfile returns, it updates UserAPI.currentUser, which UserService in Core subscribes to. See Communication Patterns for the full subscription chain.
Data Models
YoullNetwork uses a three-tier model pipeline that separates parsing concerns from the public API surface:
graph LR
A["GraphQL Response<br/>(Apollo DataDict)"] --> B["Response Model<br/>(internal, Decodable)"]
B --> C["Domain Model<br/>(public, BN*/YN*)"]
D["REST Response<br/>(JSON)"] --> CTier 1: Response Models (Internal)
67 files in Network/API Response Models/. These are internal Decodable structs that mirror the GraphQL schema structure. They conform to DecodableResponse to provide a model() transformation:
// Network/API Response Models/ContentResponse.swift
class ContentResponse: Decodable {
let id: String
let title: String
let body: String?
let asset: AssetResponse
let thumbnail: AssetResponse?
let isLiked: Bool
let isLocked: Bool
// ... more fields
}
extension ContentResponse: DecodableResponse {
typealias Model = BNItem
func model() -> BNItem {
let content = BNContent(
id: id,
title: extraLanguages?.first?.title ?? title,
// ... maps all fields to the domain model
)
return .content(content)
}
}Tier 2: Intermediate Models (Factory)
4 files that handle complex transformations between response models and domain models when simple one-to-one mapping is not sufficient:
HomeFeedSection+Factorytransforms feed response into section modelsExploreFeedSection+Factorytransforms explore response into section modelsEnvoyURLwraps URL handling for share link generationFeedTagrepresents tags in the feed context
Tier 3: Public Domain Models
55 files in Public/Models/. These are the models consumed by Core, Client, and all other modules:
| Prefix | Purpose | Examples |
|---|---|---|
BN* | General domain models | BNContent, BNCategory, BNSubcategory, BNJourney, BNEvent, BNPlaylist, BNHomeFeedSection, BNWorkoutModel |
YN* | User and profile models | YNProfile, YNBadge, YNProfileNotifications |
REST responses decode directly into domain models (Tier 3) since they are already flat JSON structures. GraphQL responses go through all three tiers.
Combine and async/await Patterns
YoullNetwork exposes APIs using both Combine publishers and async/await. Both patterns coexist in the codebase:
Combine pattern (used by most GraphQL-based methods):
// Returns AnyPublisher<[BNCategory], Error>
public static func getCategories(from network: BricksNetwork) -> Publisher<[BNCategory]> {
let query = GetContentCategoriesQuery(extraLang: network.extraLang)
return ApolloMission.launch(query: query, into: network, landingModel: CategoryResponse.self)
.map({ $0.mapToCategory() })
.eraseToAnyPublisher()
}async/await pattern (used by all REST methods and newer APIs):
// async throws -> YNProfile
public static func getProfile(stats: [String], from network: BricksNetwork) async throws -> YNProfile {
let path = "/users/me"
let profile: YNProfile = try await network.get(path: path, queryParams: [...])
currentUser.value = profile
return profile
}Dual signatures (some methods offer both):
// Combine version
public static func getProfile(stats: [String], from network: BricksNetwork) -> Publisher<YNProfile>
// async/await version
public static func getProfile(stats: [String], from network: BricksNetwork) async throws -> YNProfileThe Combine versions of dual-signature methods typically wrap the async version in a Future:
public static func getProfile(stats: [String], from network: BricksNetwork) -> Publisher<YNProfile> {
return Future<YNProfile, Error> { promise in
Task { @MainActor in
do {
let profile = try await getProfile(stats: stats, from: network)
promise(.success(profile))
} catch {
promise(.failure(error))
}
}
}.eraseToAnyPublisher()
}All Combine publishers use Never or Error as the failure type. Subscribers typically use .sink with [weak self] and store subscriptions in a Set<AnyCancellable>.
Error Handling
NetworkError
The NetworkError enum covers all failure cases:
public enum NetworkError: Error {
case operationFailed // GraphQL returned errors in the response
case invalidRequest // Could not construct the request (missing REST URL, etc.)
case invalidResponse // HTTP status code outside 200-299
case invalidData // JSON decoding failed
case missingAccessToken // Token required but not available
case emptyResponse // Empty data received (unused in practice)
}Error Propagation by Transport
| Transport | Error Source | Handling |
|---|---|---|
| GraphQL | resultData.errors?.first | LandingModule checks for GraphQL errors before parsing. Returns .failure(NetworkError.operationFailed) |
| GraphQL | Apollo network failure | Propagated as-is through the Result.failure path |
| GraphQL | Token missing/expired | UserManagementInterceptor throws UserError.noUserLoggedIn or UserError.refreshTokenFailed |
| REST | HTTP status outside 200-299 | executeRequest throws NetworkError.invalidResponse |
| REST | JSON decode failure | JSONDecoder.decode throws, propagated via async/await |
| REST | Token missing | createURLRequest throws NetworkError.missingAccessToken |
Caching and Persistence
Apollo SQLite Cache
YoullNetwork initializes an Apollo SQLiteNormalizedCache backed by a SQLite database:
let sqliteFileURL = documentsURL.appendingPathComponent("test_apollo_db.sqlite")
if let sqliteCache = try? SQLiteNormalizedCache(fileURL: sqliteFileURL) {
store = ApolloStore(cache: sqliteCache)
} else {
store = ApolloStore() // Falls back to in-memory cache
}The default cache policy is .fetchIgnoringCacheCompletely, which means the Apollo cache is not used for reads by default. The SQLite database exists and stores normalized data, but queries bypass it unless they explicitly pass a different cache policy. Currently only PaymentsAPI.getSuperPaywallConfig uses .returnCacheDataAndFetch. Per-query overrides are supported via the cachePolicy parameter on ApolloMission.launch.
CoreData Persistence
CoreDataStore provides a general-purpose CoreData stack for local persistence:
public class CoreDataStore {
public func fetch<T>(_ type: T.Type, predicate: NSPredicate?, ...) -> [T]?
public func saveInBackground(task: @escaping (NSManagedObjectContext) -> Void)
public func deleteAll()
}The DataApiCache utility uses CoreData to cache user profiles. When UserAPI.getProfile returns, it stores the profile via DataApiCache.save(object:in:). On next launch, UserService loads the cached profile immediately via DataApiCache.get(network:) before making a network request.
Cache Clearing
BricksNetwork.clearAppCache() calls coreDataStore.deleteAll() to wipe all locally persisted data. This is typically called during logout.
NetworkReachability
NetworkReachability is a singleton that monitors network connectivity and exposes the status via a Combine publisher:
public class NetworkReachability {
public var reachablePublisher = CurrentValueSubject<Bool, Never>(false)
public var reachable: Bool { reachablePublisher.value }
nonisolated(unsafe) public static let shared = NetworkReachability()
}It uses an internal Reachability class (bundled third-party library) to detect WiFi and cellular connectivity changes. The publisher emits true when any connection is available and false when offline.
Services and ViewModels subscribe to NetworkReachability.shared.reachablePublisher to show/hide offline indicators or defer network operations. See Communication Patterns for how this publisher fits into the platform's reactive state system.
Configuration
Environment URLs
The customer app provides two URLs via BricksNetwork.Environment:
endpointURL(required): The Hasura GraphQL endpointrestURL(optional): The REST API base URL
Localization via extraLang
BricksNetwork.extraLang is a language code string set at initialization and passed to nearly every GraphQL content query:
let query = GetContentCategoriesQuery(extraLang: network.extraLang)This parameter tells the backend to return content in the specified language when translations are available. If not set (empty string), the backend returns content in the default language.
Cache Policy Overrides
Individual queries can override the default cache policy:
ApolloMission.launch(query: query, into: network, cachePolicy: .returnCacheDataAndFetch)Available Apollo cache policies include .fetchIgnoringCacheCompletely (default), .returnCacheDataAndFetch, .returnCacheDataElseFetch, and .returnCacheDataDontFetch.
What's Next
For Agents
Key Files
| Concept | File Path |
|---|---|
| Package manifest | Bricks/modules/YoullNetwork/Package.swift |
| Main entry point | Bricks/modules/YoullNetwork/Network/Sources/BricksNetwork.swift |
| Data source protocol | Bricks/modules/YoullNetwork/Network/Sources/BricksNetworkDataSource.swift |
| Token interceptor | Bricks/modules/YoullNetwork/Network/Sources/Network/UserManagementInterceptor.swift |
| Network errors | Bricks/modules/YoullNetwork/Network/Sources/NetworkError.swift |
| GraphQL query wrapper | Bricks/modules/YoullNetwork/Network/Sources/Apollo/ApolloMission+Query.swift |
| GraphQL mutation wrapper | Bricks/modules/YoullNetwork/Network/Sources/Apollo/ApolloMission+Mutation.swift |
| GraphQL parsing | Bricks/modules/YoullNetwork/Network/Sources/Apollo/LandingModule+Query.swift |
| DecodableResponse protocol | Bricks/modules/YoullNetwork/Network/Sources/Apollo/DecodableResponse.swift |
| Reachability | Bricks/modules/YoullNetwork/Network/Sources/Public/NetworkReachability.swift |
| API classes | Bricks/modules/YoullNetwork/Network/Sources/Public/Data Task/*.swift |
| Response models | Bricks/modules/YoullNetwork/Network/Sources/Network/API Response Models/*.swift |
| Public domain models | Bricks/modules/YoullNetwork/Network/Sources/Public/Models/*.swift |
| CoreData store | Bricks/modules/YoullNetwork/Network/Sources/CoreData/CoreDataStore.swift |
| JWT validation | Bricks/modules/YoullNetwork/Network/Sources/Utils/String+Token.swift |
| GraphQL schema | Bricks/modules/YoullNetwork/Resources/Schema/Youll.graphqls |
| GraphQL operations | Bricks/modules/YoullNetwork/Resources/GraphQL/*.graphql |
Reading Order
Package.swiftfor dependencies (Apollo, ApolloSQLite, local GraphQL package)BricksNetwork.swiftfor the singleton, environment setup, and REST methodsBricksNetworkDataSource.swiftfor the auth contractUserManagementInterceptor.swiftfor the GraphQL token flowApolloMission+Query.swiftandApolloMission+Mutation.swiftfor the Apollo wrapperLandingModule+Query.swiftandLandingModule+Decode.swiftfor JSON parsingDecodableResponse.swiftfor the model transformation protocol- Any API class (e.g.,
ContentAPI.swift) for usage patterns - The corresponding response model (e.g.,
ContentResponse.swift) for the transformation pipeline
Tips
- To find which API class handles a feature: search for the feature name (e.g., "journey", "meal", "event") in
Public/Data Task/. - To trace a query to its response model: look at the
landingModel:parameter in theApolloMission.launchcall. That type'smodel()method shows the transformation. - To add a new GraphQL query: add the
.graphqlfile toResources/GraphQL/, run Apollo codegen, then call it viaApolloMission.launchin the appropriate API class. - To add a new REST endpoint: call
network.get/post/patch/deletedirectly in the API class. REST methods are all onBricksNetwork. - To understand the auth flow for a request: check
requiresTokenForNetworkRequestin the customer app'sConfigclass. Operations listed there require a valid JWT. - GraphQL operations are in
GraphQL/Sources/Operations/(auto-generated). Queries, mutations, and fragments are in separate subdirectories. extraLangis passed to almost every content query. If content is returning in the wrong language, check thatBricksNetwork.extraLangis set correctly during initialization.
Explore
Content discovery screen where users browse by categories, subcategories, facilitators, and tags. Supports two operational modes, generic tag-based browsing and a custom home-like feed.
Core
The foundation layer of the Youll platform. Contains infrastructure (settings, feature flags, theming, deeplinking, services, storage) and screens for all core product modules (Home, Explore, Search, Library, Journeys, Events, Activity, Meals, Quiz).