In previous articles, we discussed how to get started with and use subscriptions with GraphQL in iOS (and iPadOS, tvOS, and macOS) using Swift programming language.
This time I want to discuss some advanced topics using GraphQL with Apollo SDK and Swift: usage of GraphQL fragments and Swift scalar types; optionality with GraphQL and it’s pros and cons.
Using fragments
At first, what is a fragment in GraphQL? It is a reusable piece of the query. For instance, if you need the same field in multiple queries you can extract that into a reusable piece called GraphQL fragment.
query Users($id: userID) {
users(id $id) {
...UserDetails
followers {
...UserDetails
}
}
}
fragment UserDetails on User {
id
firstName
lastName
email
}
When you use fragments in your Swift project queries, Apollo iOS SDK generates separate result types. It is a good way to divide UI, for instance, UITableViewCell or UICollectionViewCell. This way a child view can be reused and only depends on the parent - UITableView or UICollectionView.
GraphQL scalar types in Swift
In GraphQL a scalar type is a field that has to resolve to some concrete type. In Swift language it can be Date
or enum
. Once you download schema JSON file you can see it like this:
"type": {
"kind": "SCALAR",
"name": "Date",
"ofType": null
}
Using Apollo code generation argument --passthroughCustomScalars
you can use your own types for custom scalars.
Swift Date
type from Foundation framework is a good example. If you want to convert GraphQL Date type to Swift using Apollo iOS SDK, just pass --passthroughCustomScalars
when generating the Swift code.
You can use custom formats for Date
like ISO8601
or use milliseconds. You just need to extend Date
type and conform to JSONDecodable
which requires implementing the initializing method.
extension Date: JSONDecodable {
public init(jsonValue value: JSONValue) throws {
guard let isoString = value as? String else {
throw JSONDecodingError.couldNotConvert(value: value, to: Date.self)
}
var tmpDate: Date?
if isoString.count == 17 {
tmpDate = DateFormatter.ISO8601short.date(from: isoString)
} else if isoString.count == 24 {
tmpDate = DateFormatter.ISO8601Milliseconds.date(from: isoString)
} else {
tmpDate = DateFormatter.ISO8601.date(from: isoString)
}
guard let date = tmpDate else {
throw JSONDecodingError.couldNotConvert(value: value, to: Date.self)
}
self = date
}
}
ISO8601short
, ISO8601Milliseconds
and ISO8601
declared in DateFormatter type extension as static instance properties.
Dealing with optionals with GraphQL
I think that optional type properties is one of the biggest downsides of the GraphQL with Apollo iOS SDK. It is so because in many cases GraphQL type field can not be specified, so it can be null.
When you are fetching queries with Apollo iOS SDK GraphQLResult
type has optional property data
. This property is a typed result data which means it has fetched type properties and those can be also optional.
This optional hell can be solved using extension for a specific type and add optional initializer:
extension User {
init?(_ user: UsersQuery.Data.User) {
guard let userID = Int(user.id) else {
return nil
}
self.id = userID
self.firstName = user.firstName
self.lastName = user.lastName
self.email = user.email
}
}
You can use this initializer when you fetch data and transform it from GraphQL types to your app models. By abstracting this with another layer you can hide it away and return, let’s say, a Swift Result type.
TL;DR
GraphQL fragments is a great way to extract some parts of the query code and reuse it in multiple places. Using Apollo iOS SDK it will generate Swift code for your apps and projects.
Converting types like GraphQL Date
to Swift Date
is a great hidden gem in Apollo iOS SDK. All you need to do is to add argument --passthroughCustomScalars
during code generation.
Dealing with optionals can be a pain with GraphQL in your Swift projects. One way to improve the code base is to use custom initializers for your models.