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.