Giter Site home page Giter Site logo

AnyGraphQLQuery? about apollo-ios HOT 10 CLOSED

apollographql avatar apollographql commented on July 23, 2024
AnyGraphQLQuery?

from apollo-ios.

Comments (10)

martijnwalraven avatar martijnwalraven commented on July 23, 2024 1

This ends up feeling like a bit of a hack, but I think I got something working...

  let fetch: (ApolloClient, @escaping (Any?, Error?) -> Void) -> Void
  let resultType: Any.Type
  
  init<Query: GraphQLQuery>(query: Query) {
    fetch = { client, completionHandler in
      client.fetch(query: query) { result, error in
        completionHandler(result, error)
      }
    }
    resultType = GraphQLResult<Query.Data>.self
  }
}

let action = FetchQueryAction(query: HeroNameQuery())

action.fetch(client) { result, error in  
  switch action.resultType {
  case is GraphQLResult<HeroNameQuery.Data>.Type:
    let hero = (result as! GraphQLResult<HeroNameQuery.Data>).data?.hero
    hero?.name
  default:
    fatalError("Unknown result type")
  }
}

from apollo-ios.

fruitcoder avatar fruitcoder commented on July 23, 2024

I also tried type erasure to create an AnyGraphQLQuery but failed due to the same restriction. You obviously cannot stub the static var

from apollo-ios.

martijnwalraven avatar martijnwalraven commented on July 23, 2024

I'd love to help, but I'd like to better understand your use case to see if there might be a better way to achieve what you want. What would you want use a AnyGraphQLQuery for? It seems its use would be limited because it would still need to be parameterized by a specific Data. (Which isn't a problem for something like AnyIterator because the type is general, but here Data is bound to a specific query anyway.)

I've run into the same issue myself in working on upcoming support for dynamic operations and a more flexible network layer. One solution I've been thinking about is to create a GraphQLRequest protocol with operationDefinition etc. and use that in the network layer. Because GraphQLOperation would adopt the protocol, that means you can then assign operations to a variable of type GraphQLRequest (or collections of that type, etc.).

The default implementation of the protocol on GraphQLOperation could delegate operationDefinition etc. to the static version. I'd like to keep information that isn't specific to an operation use static to avoid reinitializing it for each individual query/mutation. (That is more important with some of the new information we're adding for the revised execution algorithm, see #63).

from apollo-ios.

nuke-dash avatar nuke-dash commented on July 23, 2024

Thank for the comment.

I'll try to explain my use case.
I'm using ReactiveReSwift in other words redux-flow with observables.
In it I want to create a MiddleWare that handles all FetchAction and will fire a success or failure actions depending on what happened.
This way the View layer only needs to fire that action to the Store.
The results will then be handled by the reducers.
The problem I'm currently having is that in order to create a MiddleWare you need to add closures to a MiddleWare class which cannot be parameterized.
Thus I would need to add a case for every query I have in my app.
In order to avoid adding a case for every query AnyGraphQLQuery would be very welcome.
My code looks something like this.

Middleware<MainState>()
    .sideEffect { _, dispatch, action in
        switch action {
        case let action as FetchAction<VerticalTvGuideQuery>:
                    GraphQLMiddleWare.fetch(mediaProvider: mediaProvider, dispatch: dispatch, query: action.query, disposeBag: action.disposeBag)
        default:
            break
        }
    }
}

private static func fetch<Q: GraphQLQuery>(mediaProvider: MediaProvider, dispatch: @escaping Middleware<MainState>.DispatchFunction, query: Q, disposeBag: DisposeBag) {
    mediaProvider.fetch(query).subscribe(
        onNext: { data in
            let finishAction = FetchFinishAction<Q>(disposeBag:disposeBag, data: data)
            dispatch(finishAction)
    },
        onError: { error in
            let failureAction = FetchFailureAction(disposeBag:disposeBag, error: error)
            dispatch(failureAction)
    }) // onComplete or onDisposed
        .addDisposableTo(disposeBag)
 }

In the View layer something like this happens.

let fetchAction = FetchAction(disposeBag: self.disposeBag, query: VerticalTvGuideQuery())
store.dispatch(fetchAction)

The success and failure actions will be handles in the Reducer that is responsible for the screen that fired the FetchAction.

from apollo-ios.

martijnwalraven avatar martijnwalraven commented on July 23, 2024

I don't think AnyGraphQLQuery would help here, because it would still have to be parameterized by the Data type from the particular query it wraps. So in this case, you'd have AnyGraphQLQuery< VerticalTvGuideQuery.Data> instead of VerticalTvGuideQuery, and you would run into the same issues.

This is similar to how other type erased wrappers like AnyIterator work. Although it does hide the specific iterator type, it is still parameterized by the element type. So AnyIterator<String> is different from AnyIterator<Int>, and you can't refer to an AnyIterator without specifying the type.

If you want access to the typed data, the query-specific type information will have to come from somewhere. You may be able to define your own non-generic wrapper, but then you'll have to force cast the result somewhere else in your code in order to use it:

struct FetchQueryAction {
  let fetch: (ApolloClient, @escaping (Any?, Error?) -> Void) -> Void
  
  init<Query: GraphQLQuery>(query: Query) {
    fetch = { client, completionHandler in
      client.fetch(query: query) { result, error in
        completionHandler(result, error)
      }
    }
  }
}

let action = FetchQueryAction(query: HeroNameQuery())
action.fetch(client) { result, error in
  (result as! GraphQLResult<HeroNameQuery.Data>).data?.hero?.name
}

from apollo-ios.

nuke-dash avatar nuke-dash commented on July 23, 2024

You are correct as far as I understand it.
But I'm still trying to wrap my head around it so excuse me if I'm asking the obvious.

To come back to the original reason why I wanted AnyGraphQLQuery: how I can reduce the switch I'm currently using which has (will have) a case for every different query?
e.g.

switch action {
        case let action as FetchAction<Query1>:
                    GraphQLMiddleWare.fetch(mediaProvider: mediaProvider, dispatch: dispatch, query: action.query, disposeBag: action.disposeBag)
        case let action as FetchAction<Query2>:
                    GraphQLMiddleWare.fetch(mediaProvider: mediaProvider, dispatch: dispatch, query: action.query, disposeBag: action.disposeBag)
        case let action as FetchAction<Query3>:
                    GraphQLMiddleWare.fetch(mediaProvider: mediaProvider, dispatch: dispatch, query: action.query, disposeBag: action.disposeBag)
        default:
            break
        }

Also that the results needs to be cast somewhere else is what I'm currently doing with Reducers.
As they will process the finish or failure result.
For example:

static func createViewModelReducer(mediaProvider: MediaProvider) -> Reducer<VerticalTVGuideState> {
    return { action, state in
        switch action {
        case let action as FetchFinishAction<VerticalTvGuideQuery>:
            var state = state
            state.queryData = Box(action.data)
            state.error = nil
            return state
        case let action as FetchFailureAction:
            var state = state
            state.queryData = nil
            state.error = Box(action.error)
            return state
        default:
            return state
        }
    }
}

from apollo-ios.

martijnwalraven avatar martijnwalraven commented on July 23, 2024

Then again, if you have to hard code your queries anyway, a cleaner solution might be to use an enum:

enum FetchQueryAction {
  case heroName(HeroNameQuery)
  case heroAndFriendsNames(HeroAndFriendsNamesQuery)
}

from apollo-ios.

designatednerd avatar designatednerd commented on July 23, 2024

@Nuke- is this still an issue you need help with? I'm trying to clean up old issues and if there's a particular solution you used here, it'd probably be helpful to share with the community before I close this out. Thanks!

from apollo-ios.

nuke-dash avatar nuke-dash commented on July 23, 2024

@designatednerd no longer an issue. I settled on using a giant switch for all my queries and mutation.

from apollo-ios.

designatednerd avatar designatednerd commented on July 23, 2024

switch ftw! 😃

from apollo-ios.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.