How to use struct in ios client

Hi, I am trying to code a small project for myself to learn Apollo iOS client but I am running into questions with the API.switft.
What I understand is that the file includes some reusable struct which maps my server answer.

My client does a simple login query to github, with the following:

    query Login {
      viewer {
        __typename
        login
        name
      }
    }

API.swift was generated using Apollo codegen. This part was easy. I can make a call, adding my GH token, and the call is successful.

    private func checkLogin() {
    Network.shared.apollo.fetch(query: LoginQuery()) { result in
        
      switch result {
      case .success(let graphQLResult):
          print(graphQLResult)
      case .failure(let error):
        print("Failure! Error: \(error)")
      }
    }
    }

This does not populate for free my struct LoginQuery. The option, simple, I could think of is

      case .success(let graphQLResult):
          if let currentUser: LoginQuery.Data.Viewer = graphQLResult.data?.viewer {
              print(currentUser.name!)
          }

But I do not understand if this is the intended way or best practice.

Thanks
LM

Hi - so you’ve got the basic idea but I want to talk a little about what’s happening here.

graphQLResult is a wrapper that contains optionals for both errors and the returned data. Just because you have an error doesn’t necessarily mean you don’t have partial data in GraphQL, so it’s not as simple as making it an enum the way Swift’s Result enum works.

The graphQLResult is generically typed to your query, so you don’t need to declare the type when unwrapping it, since Swift will automatically infer the type. We also don’t recommend force-unwrapping an optional property since that can cause a crash if it’s nil.

What that adds up to is a pretty similar-looking piece of code:

case .success(let graphQLResult):
 if let currentUser = graphQLResult.data?.viewer { // will be inferred to be type LoginQuery.Data.Viewer
  print(currentUser.name ?? "No name given")
 }

Hope that helps, let me know if there’s anything else we can clear up here!

1 Like

Thanks. I was mistaken by the example of the RocketReserver where, in LaunchesViewController.swift, I can see

var launches = [LaunchListQuery.Data.Launch.Launch]()

I tried to figure out how it was used, but I could not.

In part 4 of the tutorial, that eventually gets populated by the code added in the loadLaunches method. It’s set up as an empty array so that we don’t have to constantly be unwrapping the launches variable in all the tableview methods.

I got it, it worked. But, I might be wrong, it gets a bit lengthy when you end with a list of list… I am trying to make this query out from github:

query StarredRepos {
  viewer {
    starredRepositories (orderBy:{field: STARRED_AT,
      direction:DESC}) {
      totalCount
      nodes {
        owner {
         login
        }
        name
        languages(first: 1) {
          edges {
            node {
              name
            }
          }
        }
      }
    }
  }
}

I read all my starred repos, and for each one, I get the first language.

Using the same approach, I cut the first part of query and I created var starredRepos = [StarredReposQuery.Data.Viewer.StarredRepository.Node]()

This works great, the code gives me the name of each repo

let starredRepo = self.starredRepos[indexPath.row]
cell.textLabel?.text = starredRepo.name

Now, to access the nested part, it gets tricky. Not sure which approach you would suggest, but this is what I am trying with no luck:

// Inherit the current repo from above
let langEdge = starredRepo.languages?.edges

It corresponds to let langEdge: [StarredReposQuery.Data.Viewer.StarredRepository.Node.Language.Edge?]?

It produces a list of edges, but getting the node.name does not work easily. I tried to print it, and the list

Optional([Optional(GitHub_Reader.StarredReposQuery.Data.Viewer.StarredRepository.Node.Language.Edge(resultMap: 
  ["__typename": Optional("LanguageEdge"), 
   "node": Optional(
      ["name": Optional("Swift"), 
       "__typename": Optional("Language")])
]))
])

Now, I am quite sure I am getting mistaken with this. Surely, a workaround would be querying languages for each repo. Moreover, the implicit complexity of the relay cursor connection pagination.

Would you mind me asking how would unfold and read nested connections?

So each edge contains a node, so you’d need to select which edge you want to get the name of. If you just want the list of all the languages, you could do something like this:

// This is an array, so it should be named plurally rather than singularly
let langEdges = starredRepo.languages?.edges

// Get the node for each edge and map them into their own array
let langNodes = langEdges.map { $0.node }

// Now that you have an array of nodes, you can map that into an array of names
let langNames = langNodes.map { $0.name }

I will admit the relay-style edges and nodes can be kind of annoying to figure out if you’re not familiar with them, but if you’re not able to access a property you think you should be able to, I’ve found it’s always worth asking myself: “Where am I making assumptions about whether something is an array or a single item? Is that actually what’s happening here?” and that usually helps me figure out why something’s not unwrapping.

1 Like

Thanks for this. I am getting it now. The relay connection is hard sometimes. Also, the schema implementation is not always consistent. I was using GitHub for learning, as the GraphQL model is rich and known, but sometimes getting into it can be tricky. This query actually returns the same data, offering both edges and shortcuts:

{
  search(query:"A", type: REPOSITORY, first: 3) {
    repositoryCount
    nodes {
      ... on Repository {
        name
      }
    }
    edges {
      node {
        ... on Repository {
          name
        }
      }
    }
  }
}

Can’t say I disagree, but there are apparently some significant benefits (which I will admit I don’t fully understand, because I don’t know that they fully translate to mobile) if you’re using a relay-based frontend lib.

I am actually sold to relay connection style and cursor based pagination. Problem is:

  1. Many implements relay style, regardless they are using relay or not, and then provide inconsistent shortcuts to traverse connections and edges (actually unfolding them)
  2. Cursor based pagination is often compromised and designed as a blend of cursor and offset pagination that does not always work effectively and might require you, within the same schema or even the same query, to paginate inconsistently across nested lists.

These are a bit out of topic, but the significant take away is that the way the schema is designed easily affect the developer experience and make difficult to deal with server, regardless the quality and flexibility of your graphql client :grinning:.

Yeah, that’s fair - I think the biggest thing is consistency: Pick a pagination style and stick with it. Otherwise it’s just a nightmare for clients.