How GraphQL Queries Are Used Alongside REST API Calls in GSY GitHub App Compose
The GSY GitHub App Compose project strategically combines Apollo Client for GraphQL queries to fetch complex repository details with a Retrofit-based REST client for list operations, enabling efficient data fetching while maintaining compatibility with GitHub's existing REST infrastructure.
The carguo/gsygithubappcompose repository demonstrates a pragmatic approach to modern Android networking by implementing both protocols simultaneously. This open-source GitHub client uses GraphQL queries alongside REST API calls to leverage the strengths of each technology, optimizing for performance where it matters while preserving simplicity for standard CRUD operations.
Architecture Overview
The codebase maintains two distinct networking layers that coexist through dependency injection, allowing repository classes to choose the appropriate protocol for each specific use case.
GraphQL Layer with Apollo Client
In core/network/src/main/java/com/shuyu/gsygithubappcompose/core/network/graphql/GraphQLProvider.kt, the application constructs a singleton Apollo Client configured for GitHub's GraphQL endpoint:
@Module
@InstallIn(SingletonComponent::class)
object GraphQLProvider {
@Provides @Singleton
fun provideApolloClient(tokenInterceptor: TokenInterceptor): ApolloClient {
val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val client = OkHttpClient.Builder()
.addInterceptor(logging)
.addInterceptor(tokenInterceptor)
.build()
return ApolloClient.Builder()
.serverUrl(NetworkConfig.BASE_GRAPHQL_URL) // https://api.github.com/graphql
.okHttpClient(client)
.build()
}
}
The GraphQLService.kt file provides thin wrapper functions around this client, exposing methods like getRepository that execute specific queries:
class GraphQLService @Inject constructor(
private val apolloClient: ApolloClient
) {
suspend fun getRepository(owner: String, name: String) =
apolloClient.query(GetRepositoryDetailQuery(owner, name)).execute()
}
REST Layer with Retrofit
Concurrently, core/network/src/main/java/com/shuyu/gsygithubappcompose/core/network/di/NetworkModule.kt provisions a Retrofit 2 instance for traditional REST operations:
@Provides @Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit = Retrofit.Builder()
.baseUrl(NetworkConfig.BASE_URL) // https://api.github.com/
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
@Provides @Singleton
fun provideGitHubApiService(retrofit: Retrofit): GitHubApiService =
retrofit.create(GitHubApiService::class.java)
The GitHubApiService.kt interface defines standard REST endpoints including searchRepositories, getUser, and starRepository, covering the majority of GitHub's public API surface.
Strategic Convergence in Repository Details
The repository-detail flow exemplifies how GraphQL queries complement REST API calls within the same data layer. In data/src/main/java/com/shuyu/gsygithubappcompose/data/repository/RepositoryRepository.kt, the application fetches detailed repository information using GraphQL while maintaining REST for other operations:
fun getRepositoryDetail(owner: String, name: String) = flow {
// Try DB cache first (unified approach)
val cached = repositoryDetailDao.getRepositoryDetail("$owner/$name")
if (cached != null) emit(RepositoryResult(Result.success(cached.toRepositoryDetailModel()), DataSource.CACHE, false))
// GraphQL call for fresh, rich data
val response = apolloClient.query(GetRepositoryDetailQuery(owner, name)).execute()
response.data?.repository?.let {
repositoryDetailDao.insert(it.toEntity())
emit(RepositoryResult(Result.success(it.toEntity().toRepositoryDetailModel()), DataSource.NETWORK, true))
} ?: emit(RepositoryResult(Result.failure(Throwable("Repository null")), DataSource.NETWORK, true))
}
This approach fetches nested repository data—including fields from multiple sub-objects—in a single request. While the same data could be retrieved via the REST endpoint GET repos/{owner}/{repo}, GraphQL is preferred here for its ability to return precisely the fields needed without multiple round trips.
Conversely, data-heavy list operations use REST exclusively:
// Trending repositories via REST
val response = apiService.searchRepositories(query, page = page)
repositoryDao.clearAndInsert(response.items.map { it.toEntity() })
Why Mix GraphQL and REST?
The hybrid architecture in gsygithubappcompose follows four strategic principles:
Selective Richness — GraphQL is employed where a single call replaces multiple REST requests, such as repository details requiring nested contributor, language, and release data.
Legacy Compatibility — Most GitHub public APIs remain REST-based. The existing GitHubApiService covers search, events, commits, issues, stars, and forks without requiring complex GraphQL query definitions.
Unified Caching — Both protocols feed into the same Room database cache. GraphQL responses transform into entities via repository.toEntity() and persist identically to REST responses, ensuring consistent offline behavior.
Gradual Migration — The mixed approach allows incremental GraphQL adoption without rewriting the entire networking stack, reducing risk while modernizing specific data flows.
Implementation Examples
Configuring the Apollo Client
The GraphQLProvider.kt file centralizes GraphQL configuration, attaching authentication interceptors shared with the REST client:
return ApolloClient.Builder()
.serverUrl(NetworkConfig.BASE_GRAPHQL_URL)
.okHttpClient(client) // Reuses OkHttp configuration
.build()
GraphQL Service Abstraction
The GraphQLService class encapsulates query execution logic, providing type-safe suspend functions that repository classes consume:
class GraphQLService @Inject constructor(
private val apolloClient: ApolloClient
) {
suspend fun getRepository(owner: String, name: String) =
apolloClient.query(GetRepositoryDetailQuery(owner, name)).execute()
suspend fun getTrendUser() =
apolloClient.query(GetTrendUserQuery()).execute()
}
Retrofit REST Setup
The NetworkModule.kt configures OkHttp interceptors for logging and authentication, then binds them to Retrofit:
@Provides @Singleton
fun provideOkHttpClient(tokenInterceptor: TokenInterceptor): OkHttpClient {
return OkHttpClient.Builder()
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
})
.addInterceptor(tokenInterceptor)
.build()
}
Unified Repository Pattern
Repository classes in RepositoryRepository.kt inject both services and select protocols based on data requirements:
- GraphQL: Complex objects with nested relationships (repository details)
- REST: List endpoints, pagination, and simple CRUD (trending repos, user events, stars)
Summary
- The
gsygithubappcomposeproject implements Apollo Client for GraphQL and Retrofit for REST within the same codebase. - GraphQL queries handle complex repository detail fetching in
RepositoryRepository.kt, reducing round trips for nested data. - REST API calls manage list operations, search, and standard GitHub endpoints through
GitHubApiService.kt. - Both protocols share OkHttp interceptors for authentication and logging, and both persist data to Room for unified offline caching.
- This hybrid approach maximizes data efficiency for specific use cases while maintaining compatibility with GitHub's extensive REST infrastructure.
Frequently Asked Questions
Why doesn't the app migrate completely to GraphQL?
The GitHub REST API remains the primary interface for most operations including search, events, and pagination. According to the source code, maintaining REST for these endpoints avoids the complexity of rewriting working code, while GraphQL is adopted selectively where it provides measurable performance benefits for complex data fetching.
How does the app maintain consistent authentication across both protocols?
Both the ApolloClient in GraphQLProvider.kt and the OkHttpClient in NetworkModule.kt share the same TokenInterceptor. This interceptor attaches the OAuth token to headers regardless of whether the request originates from a GraphQL query or REST call, ensuring seamless authentication across the hybrid stack.
Which specific operations use GraphQL versus REST?
The RepositoryRepository.kt file shows that GraphQL handles repository detail queries via GetRepositoryDetailQuery, while REST manages trending repositories (searchRepositories), user profiles, starred repos, and issue lists. The decision depends on whether the operation requires fetching nested relationships (GraphQL) or simple list pagination (REST).
How does the caching layer handle mixed protocol responses?
Both GraphQL and REST responses transform into identical Room entities before storage. For example, repository details fetched via GraphQL convert using repository.toEntity() and insert into repositoryDetailDao, just as REST responses do. This creates a unified cache layer that abstracts the data source from the UI layer.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →