如何在 android 中使用 GraphQl?

Posted

技术标签:

【中文标题】如何在 android 中使用 GraphQl?【英文标题】:How can I use from GraphQl in android? 【发布时间】:2017-09-03 16:31:46 【问题描述】:

我需要一个简单的例子来在android中使用GraphQl

我如何在android中使用GraphQl(教程)。

【问题讨论】:

【参考方案1】:

为了使用 GraphQL(通常),您需要两件事:

1。 GraphQL 服务器

有几种方法可以解决这个问题。当然,您可以简单地使用您喜欢的任何服务器端语言去implement one yourself。 其他(更快)方法是利用现有工具并使用 graphql-up 或 create-graphql-server 之类的服务甚至Graphcool 之类的服务生成 GraphQL API(免责声明:我为他们工作)。

2。一个 GraphQL 客户端库

虽然这不是绝对必要的,您也可以通过简单的 HTTP 与 GraphQL 服务器进行交互(在 POST 请求的主体中发送您的查询和突变),但使用需要重复工作的现有工具肯定是有益的比如缓存或 UI 集成。目前最受欢迎的 GraphQL 客户端之一是Apollo,他们也非常积极地致力于version for Android。但是,这还没有正式发布。因此,您现在要么必须使用他们现有的开发版本,要么选择使用纯 HTTP 的前一种方法。

【讨论】:

@nburk,非常感谢。 这是一个简单的 Instagram 示例(请参阅 GitHub 自述文件中 graphql-up 的数据模型):github.com/graphcool-examples/android-http-instagram-example Android 中使用 graphql 的示例应用:github.com/graphcool-examples/android-graphql【参考方案2】:

这是从客户端查询 GraphQl 的示例。在本例中,我使用的是 Retrofit 2:

// QueryHelper.java
// This line below is the simple format of Gql query
query = "querymename, location, majorOfInterest,profilePhotourl(size: 400) ";

//Post the query using Retrofit2
GqlRetrofitClient.getInstance(getContext()).fetchUserDetails(new GqlQueryRequest(queryUserDetails)).enqueue(new Callback<UserDetails>() 
  @Override
  public void onResponse(Call<UserDetails> call, Response<UserDetails> response) 
     //OnResponse do something();       
     

  @Override
  public void onFailure(Call<UserDetails> call, Throwable t) 
    Log.d(TAG, "Failed to fetch User details");
    
    );


//GqlClient.java
public class GqlRetrofitClient 
public static final String BASE_URL = BuildConfig.DOMAIN;
private static GqlRetrofitClient sInstance;
private GqlRetrofitService mGqlRetrofitService;

Gson gson = new GsonBuilder().create();

  private GqlRetrofitClient(final Context context) 
    // Network Interceptor for logging
    HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor();
    httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

    OkHttpClient okHttpClient = new OkHttpClient.Builder()
            .addNetworkInterceptor(new Interceptor() 
                @Override
                public Response intercept(Chain chain) throws IOException 
                    Request request = chain.request().newBuilder()
                            .addHeader("X-User-Token", "AUTH_TOKEN")
                            .addHeader("X-User_Email", "Email")
                            .addHeader("content-type", "application/json")
                            .build();
                    return chain.proceed(request);
                
            )
            .addInterceptor(httpLoggingInterceptor)
            .build();

    // Retrofit initialization
    final Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create(gson))
            .client(okHttpClient)
            .build();

    mGqlRetrofitService = retrofit.create(GqlRetrofitService.class);
 

  // Create an instance of GqlRetrofitClient to create retrofit service
  public static GqlRetrofitClient getInstance(Context context)
    if(sInstance == null)
        sInstance = new GqlRetrofitClient(context.getApplicationContext());
    
    return sInstance;
  

  // Method call to get User details
  public Call<UserDetails> fetchUserDetails(GqlQueryRequest queryUserDetails)
    return mGqlRetrofitService.getUserDetails(queryUserDetails);
  


//GqlRetrofitService.java
public interface GqlRetrofitService
  @POST("/api/graph.json")
  Call<UserDetails> getUserDetails(@Body GqlQueryRequest body);

【讨论】:

【参考方案3】:

我个人使用 Retrofit,我拿了这个 Link Credits 并改变了一些东西。

这是代码:

在文件“GraphQLConverter.java”中:

public class GraphQLConverter extends Converter.Factory 

    private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8");

    private GraphQueryProcessor graphprocessor;
    private final Gson mGson;

    private GraphQLConverter(Context context) 
        graphProcessor = new GraphQueryProcessor(context);
        mGson = new GsonBuilder()
                .enableComplexMapKeySerialization()
                .setLenient()
                .create();
    

    public static GraphQLConverter create(Context context) 
        return new GraphQLConverter(context);
    

    /** Override Converter.Factory Methods **/
    @Override
    public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) 
        return null;
    

    @Override
    public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) 
        if(type == QueryContainerBuilder.class)
            return new GraphRequestConverter(methodAnnotations);
         else 
            return null;
        
    

    /** RequestConverter Class **/
    private class GraphRequestConverter implements Converter<QueryContainerBuilder, RequestBody> 

        private Annotation[] mAnnotations;

        private GraphRequestConverter(Annotation[] annotations) 
            mAnnotations = annotations;
        

        @Override
        public RequestBody convert(@NonNull QueryContainerBuilder containerBuilder) 
            QueryContainerBuilder.QueryContainer queryContainer = containerBuilder
                    .setQuery(graphProcessor.getQuery(mAnnotations))
                    .build();
            return RequestBody.create(MEDIA_TYPE, mGson.toJson(queryContainer).getBytes());
        

    

在文件“GraphQuery.java”中:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GraphQuery 

    String value() default "";


在文件“GraphQueryProcessor.java”中:

class GraphQueryProcessor 

    private static final String TAG = GraphQueryProcessor.class.getSimpleName();
    // GraphQl Constants
    private static final String EXT_GRAPHQL = ".graphql";
    private static final String ROOT_FOLDER_GRAPHQL = "graphql";

    private final Map<String, String> mGraphQueries;
    private Context mContext;

    GraphQueryProcessor(Context context) 
        mGraphQueries = new WeakHashMap<>();
        mContext = context;
        populateGraphQueries(ROOT_FOLDER_GRAPHQL);
    

    /** Package-Private Methods **/
    String getQuery(Annotation[] annotations) 
        if(mGraphQueries == null || mGraphQueries.isEmpty())
            populateGraphQueries(ROOT_FOLDER_GRAPHQL);
        

        GraphQuery graphQuery = null;
        for (Annotation annotation : annotations) 
            if (annotation instanceof GraphQuery) 
                graphQuery = (GraphQuery) annotation;
                break;
            
        

        if (graphQuery != null) 
            String fileName = String.format("%s%s", graphQuery.value(), EXT_GRAPHQL);
            if (mGraphQueries != null && mGraphQueries.containsKey(fileName)) 
                return mGraphQueries.get(fileName);
            
        
        return null;
    

    /** Private Methods **/
    private void populateGraphQueries(@NonNull String path) 
        try 
            String[] paths = mContext.getAssets().list(path);
            if (paths != null && paths.length > 0x0) 
                for (String item : paths) 
                    String absolute = path + "/" + item;
                    if (!item.endsWith(EXT_GRAPHQL)) 
                        populateGraphQueries(absolute);
                     else 
                        mGraphQueries.put(item, getFileContents(mContext.getAssets().open(absolute)));
                    
                
            
         catch (IOException ioE) 
            BaseEnvironment.onExceptionLevelLow(TAG, ioE);
        
    

    private String getFileContents(InputStream inputStream) 
        StringBuilder queryBuffer = new StringBuilder();
        try 
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            for (String line; (line = bufferedReader.readLine()) != null; )
                queryBuffer.append(line);
            inputStreamReader.close();
            bufferedReader.close();
         catch (IOException e) 
            e.printStackTrace();
        
        return queryBuffer.toString();
    


在文件“QueryContainerBuilder.java”中:

public class QueryContainerBuilder 

    // Mask Types
    private static final byte MASK_REPLACE_QUERY_ARGUMENTS =    0b1;     // Invece di inviare il json con le variabili va a inserirle nella query i valori sostituendo i tipi degli argomenti.
    private static final byte MASK_REPLACE_EXPLICIT_QUOTES =    MASK_REPLACE_QUERY_ARGUMENTS << 0b1;     // Alle stringhe non vengono automaticamente messe le virgolette ma devono essere aggiunte nei valori passati per le variabili.
    private static final byte MASK_REPLACE_WITH_PLACEHOLDERS =  MASK_REPLACE_EXPLICIT_QUOTES << 0b1;     // Va a sostituire i placeholders "<key_var_name>" presenti nella query con i valori delle variabili.

    private QueryContainer mQueryContainer;
    private byte mMask;

    public QueryContainerBuilder() 
        mQueryContainer = new QueryContainer();
    

    /** Setter Methods **/
    public QueryContainerBuilder setQuery(String query) 
        mQueryContainer.setQuery(query);
        return this;
    

    public QueryContainerBuilder setReplaceQueryArguments()
        mMask = MASK_REPLACE_QUERY_ARGUMENTS;
        return this;
    

    public QueryContainerBuilder setReplaceExplicitQuotes()
        mMask =  MASK_REPLACE_QUERY_ARGUMENTS | MASK_REPLACE_EXPLICIT_QUOTES;
        return this;
    

    public QueryContainerBuilder setReplaceWithPlaceholders()
        mMask = MASK_REPLACE_QUERY_ARGUMENTS | MASK_REPLACE_WITH_PLACEHOLDERS;
        return this;
    

    /** Public Methods **/
    public QueryContainerBuilder putVariable(String key, Object value) 
        mQueryContainer.putVariable(key, value);
        return this;
    

    public boolean containsVariable(String key) 
        return mQueryContainer.containsVariable(key);
    

    /** Builder Methods **/
    public QueryContainer build() 
        if((mMask & MASK_REPLACE_QUERY_ARGUMENTS) != 0x0)
            if((mMask & MASK_REPLACE_WITH_PLACEHOLDERS) != 0x0)
                mQueryContainer.replaceVariablesPlaceholdersInQuery();
             else 
                mQueryContainer.replaceVariablesInQuery(mQueryContainer.mVariables, 0x0);
            
            mQueryContainer.mVariables = null;
        
        return mQueryContainer;
    

    /** Public Static Classes **/
    public class QueryContainer 

        @SerializedName("variables")
        private LinkedHashMap<String, Object> mVariables;
        @SerializedName("query")
        private String mQuery;

        QueryContainer() 
            mVariables = new LinkedHashMap<>();
        

        /** Private Methods **/
        private void setQuery(String query) 
            mQuery = query;
        

        private void putVariable(String key, Object value) 
            mVariables.put(key, value);
        

        private boolean containsVariable(String key) 
            return mVariables != null && mVariables.containsKey(key);
        

        private void replaceVariablesInQuery(LinkedHashMap<String, Object> map, int index)
            if(!TextUtils.isEmpty(mQuery) && map.size() > 0x0)
                List<String> keys = new ArrayList<>(map.keySet());
                for(String key : keys)
                    Object value = map.get(key);
                    if(value instanceof LinkedHashMap)
                        replaceVariablesInQuery((LinkedHashMap<String, Object>) value, index);
                     else 
                        int i = mQuery.indexOf(key + ":", index) + key.length() + 0x1;
                        int z;
                        if(keys.indexOf(key) < keys.size() - 0x1)
                            z = mQuery.indexOf(",", i);
                         else 
                            z = mQuery.indexOf(")", i);
                            int x = mQuery.substring(i, z).indexOf('');
                            if(x != -0x1)
                                if(mQuery.substring(i, i + 0x4).contains(""))
                                    x++;
                                
                                z -= ((z - i) - x);
                            
                        

                        String replace;
                        if((mMask & MASK_REPLACE_EXPLICIT_QUOTES) != 0x0)
                            replace = String.valueOf(value);
                         else 
                            replace = value instanceof String ?
                                    "\"" + value.toString() + "\"" : String.valueOf(value);
                        
                        String sub = mQuery.substring(i, z)
                                .replaceAll("[\\\\]?\\[", "\\\\\\[").replaceAll("[\\\\]?\\]", "\\\\\\]")
                                .replaceAll("[\\\\]?\\", "\\\\\\").replaceAll("[\\\\]?\\", "\\\\\\");
                        mQuery = mQuery.replaceFirst(sub.contains("") ? sub.replace("", "").trim() : sub.trim(), replace);
                        index = z + 0x1;
                    
                
            
        

        private void replaceVariablesPlaceholdersInQuery()
            if(!TextUtils.isEmpty(mQuery) && mVariables.size() > 0x0)
                for(String key : mVariables.keySet())
                    mQuery = mQuery.replaceFirst("\\<" + key + "\\>", mVariables.get(key) != null ? mVariables.get(key).toString() : "null");
                
                mVariables = null;
            
        

    


将查询放在“assets”文件夹中的“graphql”目录中,查询文件的扩展名为“.graphql”。您可以通过更改“GraphQueryProcessor”中的“EXT_GRAPHQL”或“ROOT_FOLDER_GRAPHQL”常量来更改扩展名或文件夹。您可以将这些格式用于查询:

query 
    myQuery(param1: <myParam1>) 
        ....
    

如果您使用这种格式,您需要在 QueryContainerBuilder 中使用 “MASK_REPLACE_WITH_PLACEHOLDERS”。您还需要将不带“<...>”的占位符的名称作为 HashMap 键传递,因此在本例中为“myParam1”。

其他格式只是常见的 GraphQL 查询,例如:

query ($p1: String!) 
    muQuery(p1: $id) 
        ...
    

使用这种格式,您可以使用正常的 QueryContainerBuilder 行为(未应用掩码,因此它将传递并生成“变量”json 对象。)“MASK_REPLACE_QUERY_ARGUMENTS”这将删除“$id”并放置值。

当您初始化 Retrofit 时,添加“GraphQLConverter”注意“ConvertFactories”顺序!您可以放置​​更多 ConvertFactory,但它们会消耗输入,因此如果在这种情况下您将“Gson”放在“GraphQL”之前,“GsonConverted”将消耗输入数据:

new Retrofit.Builder()
                .baseUrl(mBaseUrl)
                .addConverterFactory(GraphQLConverter.create(context))
                .addConverterFactory(GsonConverterFactory.create(gson))
                .client(getBaseHttpClient(interceptor))
                .build();

在您的 Retrofit API 中

@POST(AppConstants.SERVICE_GQL)
@GraphQuery(AppConstants.MY_GRAPHQL_QUERY_FILENAME)
fun callMyGraphQlQuery(@Body query: QueryContainerBuilder): Call<MyGraphQlResponse>

调用示例

val query = QueryContainerBuilder()
                .putVariable("myParam1", myValue)
                .setReplaceWithPlaceholders()
            createService(API::class.java).callMyGraphQlQuery(query)

val query = QueryContainerBuilder()
            .putVariable("p1", myValue)
            .setReplaceQueryArguments()
        createService(API::class.java).callMyGraphQlQuery(query)


val query = QueryContainerBuilder()
            .putVariable("p1", myValue)
        createService(API::class.java).callMyGraphQlQuery(query)

如果“MASK_REPLACE_QUERY_ARGUMENTS”工作正常,我只使用了 2/3 次,然后后端被更改并写得更好。

我做了这些案例(掩码)来处理查询,因为我在调用后端时遇到了这 3 个查询案例。 您可以通过添加另一个掩码和“QueryContainerBuilder”中的代码来添加其他查询处理行为。

如果有人使用此代码并对其进行更改以使其更好,请将更改写给我,以便我也更改库中的代码。

谢谢你, 祝你编码愉快:D

再见!

【讨论】:

【参考方案4】:

在你的清单中添加

<uses-permission android:name="android.permission.INTERNET"/>

你的依赖

// Kotlin Coroutines
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.4'

//OkHttp
implementation ("com.squareup.okhttp3:okhttp:3.12.12")
  force = true //API 19 support

implementation 'com.squareup.okhttp3:logging-interceptor:3.12.12'

//retrofit
implementation "com.squareup.retrofit2:retrofit:2.7.1"
implementation "com.squareup.retrofit2:converter-scalars:$2.7.1"

还有 Java 8 兼容性

android 

    ...
    compileOptions 
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    

    kotlinOptions 
        jvmTarget = "1.8"
    

有服务

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface GraphQLService 

    @Headers("Content-Type: application/json")
    @POST("/")
    suspend fun postDynamicQuery(@Body body: String): Response<String>

你可以创建一个对象

import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory

object GraphQLInstance 

    private const val BASE_URL: String = "http://192.155.1.55:2000/"

    val graphQLService: GraphQLService by lazy 
        Retrofit
            .Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(ScalarsConverterFactory.create())
            .build().create(GraphQLService::class.java)
    

在activity中你可以创建这个方法

private fun post(userId: String)
    val retrofit = GraphQLInstance.graphQLService
    val paramObject = JSONObject()
    paramObject.put("query", "query users(userid:$userId)username")
    GlobalScope.launch 
        try 
            val response = retrofit.postDynamicQuery(paramObject.toString())
            Log.e("response", response.body().toString())
        catch (e: java.lang.Exception)
            e.printStackTrace()
        
    

您可以查看the example in GitHub和my post

注意:如果需要突变应该改变这一行

paramObject.put("query", "query users(userid:$userId)username")

paramObject.put("query", "mutation users(userid:$userId)username")

【讨论】:

以上是关于如何在 android 中使用 GraphQl?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 android 客户端中使用 graphql 订阅

如何使用 amplify 将 GraphQL 更改从 Appsync 同步到 Android?

如何在 Kotlin Android 中读取 Apollo Client 响应/GraphQL 响应的响应数据

如何在 Android 中发送带有片段的 graphql 查询

如何在 GraphQL apollo-android 客户端中获取 HTTP 状态码

如何在 Android Studio 中从 Apollo Client (GraphQL) 导入生成的类