So far using TCA I've been able to create dependencies in our app fairly easily.
I created an ApiClient
which can fetch from our back end. It doesn't have any generic constraint (it just returns (Data, URLResponse)
. This means I have one ApiClient for the whole app which is good. I then extend this ApiClient to add a generic function which fetches the (Data, URLResponse)
and then decodes the data into a generic Decodable
type.
I also created a DataRepo
which does have a generic constraint as it creates a subscribable cache from an endpoint and has a generic constraint on its Resource
type to know what to decode when it gets new values. This means I can have a DataRepo for each Resource which is fine as there are a limited number of them.
However... with this current problem I'm a bit stuck. I'm exploring using the ApolloClient for the first time and trying to work out how I can create a @Dependency for it to use within TCA.
Ideally at the call site it would look something like this...
// inside the reducer
case .someAction:
return .run { send in
do {
let result = try await graphQLClient.fetch(SomeGraphQLQuery())
send(.someSuccessAction(result))
} catch {
send(.someErrorAction)
}
}
However, I'm having difficulty getting this to work.
The problem is that the ApolloClient
Documentation conforms to a protocol that uses generic functions.
In the case above the underlying ApolloClient
function looks like this...
func fetch<Query>(
query: Query,
cachePolicy: CachePolicy,
contextIdentifier: UUID?,
queue: DispatchQueue,
resultHandler: GraphQLResultHandler<Query.Data>?
) -> Cancellable where Query : GraphQLQuery
I'd like to do two things to this...
- Make it async await (which is the easy part).
- Wrap this in a struct like @Dependency so I can inject it and mock it etc... like every other dependency we have. (This is where I'm struggling).
The problem is that the generic constraint on the function defines both the input to the function Query
and the output of the function Query.Data
. I'm just not sure how to turn this into a Dependency without making the Type
of the dependency generically constrained.
For instance, I could do this...
struct GraphQLClient<Query: GraphQLQuery> {
let fetch: @Sendable (Query) async throws -> Query.Data
}
But now that means every query in the app will have its own separate GraphQLClient and they would all have to be defined as separate dependencies.
I have tried to use some of the new existential types to try and get around this. But being honest, I don't fully know how those work or what they're for yet.
I wonder if I could just create the GraphQLClient
using an internal non-generic function (somehow) and then extend it (a bit like the ApiClient above) to add a public generic function to it?
Sorry for the rambling post. I hope I managed to get my problem across. Any advice or help would be greatly appreciated.
Thanks