iOS - Use the generated models directly, or map them to models owned by application?

Hi All!

My team is just getting started on a new project in SwiftUI. Backend is GraphQL. We’re using Apollo on the iOS client.

Question we’ve been asking ourselves, and we’ve seen other projects do this in different ways, is do we use the Apollo generated models directly in our SwiftUI views, or do we map them to our own internally owned models? What’s the benefit of either/or?

The benefit we see of using the Apollo models directly is not maintaining the overhead of a “intermediary” model. We define our query and use it.

The benefit of using a model that is owned by the application is that it is extendable and initializable (ability to init models for previews and testing).

Each has its pros and cons, but wanted to see what others thoughts are on this!
Thanks!

Hi @samurai :wave:

I believe this comes down to what your needs are with the models, each project is different.

Question we’ve been asking ourselves, and we’ve seen other projects do this in different ways, is do we use the Apollo generated models directly in our SwiftUI views, or do we map them to our own internally owned models? What’s the benefit of either/or?

The benefit of using a model that is owned by the application is that it is extendable and initializable (ability to init models for previews and testing).

It’s worth asking what additional value would your own models give you?

The models generated by Apollo iOS are extensible and the new 1.0 test mocks make it easier to test those generated models. The generated models in 1.0 are immutable (a big difference from 0.x) and thus don’t come with property-based initializers. You could write your own but it’s going to be tedious and something you would need to maintain.

If you have properties that are client-side only and query-specific that might be a case for wrapping the generated models to add those properties. We do have plans to eventually support features like that but they’re not currently on our roadmap.

The benefit we see of using the Apollo models directly is not maintaining the overhead of a “intermediary” model. We define our query and use it.

The 1.0 models were designed to become the models you use and pass around your application to power the UI. They do have limitations depending on your needs though.

I’m also interested in this topic. How should we handle creating SwiftUI previews in our applications if we use the model directly - since the models are immutable as you’ve said, and no property initializers exist?

Hi,
I’m just adding my two cents / opinions here:

I started to use Apollo on 0.x and decided to map the apollo models to my own models. It is a bit of boiler plate code but at the end it’s just a thin layer of mapping that gives you abstraction.

When migrating from Apollo 0.x to 1.x, it was just a matter of changing my mappers without changing my logic code using my models models … which would have been painful.

Using directly the generated models can be faster to develop but you will need to depend on Apollo every where you use your models.

You might also run into some smaller inconveniences (such as How to generate snake_case property name to camelCase in Apollo version 1.0.3 cli auto generate? · Issue #2641 · apollographql/apollo-ios · GitHub) which you won’t have if you map to your own models

@calvincestari What do you mean by that?

I meant if you’re not adding any new stored properties then writing type extensions is possible, no need to maintain a new type for that simple use case.

I’ve built a few client apps against a GraphQL API and previously I’ve used the generated models directly, but this time around we are using a separate model for the UI. In my experience even the best graphs expose a more complex API than you want to consume from your UI (currentUser.friendsList.edges.map { $0.node }, anyone :wink: ). Here’s an example from our current project.

When rendering the AvatarImage we need several pieces of data:

fragment AvatarImageUser on User {
  givenName
  familyName
  displayName
  avatarURL
}

But to render the View we just need these:

struct AvatarImageUser {
  var initials: String
  var avatarURL: URL?
}

struct AvatarImage: View {
  var user: AvatarImageUser

  var body: some View {
    ZStack {
      // oversimplified...
      Text(user.initials)
      AsyncImage(user.avatarURL)
    }
  }
}

I’ve found that there tends to be quite a bit of code required to massage API response (even from a great GraphQL API) into a renderable state. As a bonus, this code is extremely easy to unit test, especially with Apollo’s generated test mocks.

extension AvatarImageUser {
  init(fragment: AvatarImageUserFragment) {
    avatarURL = fragment.avatarURL
    initials = // complicated code based on PersonNameComponentsFormatter...
  }
}

Also, it’s a great idea to pair Views, Models and Fragments together, that way when you reuse the View you can reuse the fragments and models too. Composition across the board. :smiley: