
# Graphaello Use GraphQL directly from your SwiftUI Views. Graphaello is a Code Generation Command Line Tool that allows you to use property wrappers in your SwiftUI Views, to use data from GraphQL. The main features of Graphaello are: - 🕓/❗️ Loading and Error State handling - 📝 Declarative - 🔐 Type-Safety - 🧰 Reusable Components - 📖 Paging Support - 🐦 Only write Swift (your Swift code is your GraphQL code) If you're looking for something like this, but for other platforms, Graphaello is heavily inspired by [Relay](https://github.com/facebook/relay). ## Examples Code snippets are cool, but how does this look in a real project? Here are some Example Apps that you can take a look at. | Countries | Music | CovidUI | | ------------- |-------------| ---------- | |  |  |  | | Simple Hello World App that displays information about a lot of different countries | More complex App that uses Paging and a lot of reusable components | Integrationg Test, displaying data from my own GraphQL API: CovidQL | Uses [Countries API](https://countries.trevorblades.com) | Uses [GraphBrains](https://github.com/exogen/graphbrainz) | Uses [CovidQL](https://github.com/nerdsupremacist/CovidQL) | [Repo](https://github.com/nerdsupremacist/graphaello-countries-example) | [Repo](https://github.com/nerdsupremacist/graphaello-music-example) | [Repo](https://github.com/nerdsupremacist/CovidUI) ## Tutorial This Readme is intended to document everything about Graphaello, from the CLI and API. However, for starting out that's not the best resource. I wrote a [tutorial](https://quintero.io/blog/Graphaello/) where I go into the benefits of Graphaello and how to build a simple App to browse movies with it. This post is intended even for people who are not familiar GraphQL at all. So if you're interested please do check it out [here](https://quintero.io/blog/Graphaello/).  ## TLDR? Let's cut the chase and go directly to our first example of a View using Graphaello: ```swift // Define a Cell struct CharacterCell: View { // Use the GraphQL Property Wrapper @GraphQL(StarWars.Person.name) var name: String? @GraphQL(StarWars.Person.homeworld.name) var home: String? var body: some View { HStack { name.map { Text($0).bold() } Spacer() home.map { Text($0) } } } } ``` This code tells Graphaello to: - Generate a GraphQL [Fragment](https://graphql.org/learn/queries/#fragments): A reusable definition of the `Person` type, made especially for your View: ```graphql fragment CharacterCell_Person on Person { name homeworld { name } } ``` - Create an initializer for your View using this type: ```swift let person: CharacterCell.Person = ... let view = CharacterCell(person: person) ``` **And did I mention it's all type safe?!?!** ```swift @GraphQL(StarWars.Person.name) var name: String? // works @GraphQL(StarWars.Person.name) var name: Bool // doesn't work ``` **AAAAaaaaand:** if it's a scalar then you don't even need to specify the type!! ```swift @GraphQL(StarWars.Person.name) var name // Swift knows it's a String? ``` ## Installation Please remember that Graphaello is in its early stages and is therefore not production ready. Use at your own caution. ### Via Homebrew Graphaello can be installed via Homebrew: ``` brew tap nerdsupremacist/tap brew install graphaello ``` ### From Source Or if you are one of those, you can install it directly from the source code. You do you! ``` git clone https://github.com/nerdsupremacist/Graphaello.git cd Graphaello sudo make install ``` ## Usage We will cover how to use Graphaello from two sides. - What can you do in your code, - and how to you use the Command Line Tool: ### Code Almost all examples will refer to the Star Wars API: https://swapi-graphql.netlify.com #### Views You very easily use information from a GraphQL API directly from your SwiftUI View: For example this `CharacterCell` displays a single Cell with a Person's Name and Home World ```swift struct CharacterCell: View { @GraphQL(StarWars.Person.name) var name: String? @GraphQL(StarWars.Person.homeworld.name) var home: String? var body: some View { HStack { name.map { Text($0).bold() } Spacer() home.map { Text($0) } } } } // Initializer is automatically created by Graphaello let view = CharacterCell(person: person) ``` #### Composing Views If your view has a sub view with it's own data, your view doesn't need to know the specifics of it, but only the fact that it needs to populate it: ```swift struct CharacterDetail: View { @GraphQL(StarWars.Person._fragment) var headerCell: CharacterCell.Person @GraphQL(StarWars.Person.eyes) var eyes: String? var body: some View { VStack { CharacterCell(person: headerCell) eyes.map { Text($0) } } } } let view = CharacterDetail(person: person) ``` #### Using Queries You can access any query fields of the API directly: ```swift struct FilmView { // .film refers to a field in the query @GraphQL(StarWars.film.title) var title: String? var body: String { title.map { Text($0) } } } let client = ApolloClient(url: ...) let api = StarWars(client: client) let view = api.filmView(id: ...) ``` #### Using Mutations All mutations can directly be used from `{API_NAME}.Mutation`. For this example we're using a TODO app since the Star Wars API doesn't support mutations: ```swift struct TodoCell: View { // _nonNull() is equivalent to ! @GraphQL(Todos.Todo.id._nonNull()) var id: String // _withDefault(FOO) is equivalent to ?? FOO @GraphQL(Todos.Todo.title._withDefault("")) var title: String @GraphQL(Todos.Todo.completed._withDefault(false)) var completed: Bool @GraphQL(Todos.Mutation.toggle.completed._withDefault(false)) var toggle: Toggle // Define a type name for your mutation var body: some View { HStack { Text(title) Spacer() Button(completed ? "Mark as not done" : "Mark as done") { toggle.commit(id: self.id) { completed in self.completed = completed } } ActivityIndicator().animated(toggle.isLoading) } } } ``` #### Using Paging If your API suppors Connections you can include paging in your App out of the box: ```swift struct CharacterList: View { @GraphQL(StarWars.allPeople._nonNull()) var characters: Paging