The following is the first few sections of a chapter from The Busy Coder's Guide to Android Development, plus headings for the remaining major sections, to give you an idea about the content of the chapter.


Arguments and Variables

The GraphQL that we have used for most of this book has been fairly simplistic. We ask for some fields, and we get those results. We have not even performed a mutation yet.

Partly, that is due to a lack of input.

The GraphQL operations that we have seen mostly have been just a tree of fields. The user provided no input for any of it.

However, we did see one operation where we provided some input:

query find($search: String!) {
  findTrips(searchFor: $search) {
    id title startTime priority duration
  }
}

Here, we are attempting to find trips based upon some supplied search criteria.

This input comes in the form of arguments (searchFor) and variables ($search). For any serious GraphQL work, you are going to use arguments and variables quite a bit. So, in this chapter, we will take a look at how these work, and along the way examine a new data type that we skipped over in the last chapter: the input object.

Arguments

When you read the word “field” in the previous chapter, you probably were thinking about fields the way normal programmers do.

GraphQL is not normal.

A normal programmer would think that fields are…, well, fields. They are data. So, for example, the Trip might be a Java POJO akin to the one that Apollo-Android code-generates for us, with String id and String title fields.

This is entirely possible with GraphQL. However, fields can also be more like methods or functions, taking arguments.

This is fairly easy to envision with root fields, as we have done that already in this book, with the findTrips() root field on the Query object:

query find {
  findTrips(searchFor: "ca") {
    id
    title
  }
}

Here, rather than retrieving all trips, we are finding trips that have ca somewhere in the title or notes. This makes findTrips() feel more like a Java method:

public class Query {
  List<Trip> findTrips(String searchFor) {
    // do cool stuff here to return results
  }
}

It is also possible to do this with nested fields, but that is a bit esoteric, so we will hold off on that topic until later in the book.

Argument Data Types and Input Objects

Most of the data types described in the preceding chapter are valid data types for arguments. Specifically, all of the scalars (numbers, strings, ID, enums) are fine, as are lists of those. And, arguments can be marked as non-null, indicating that the argument is required.

What is not supported are object types (e.g., Trip) or things closely tied to object types, like unions (e.g., Note) and interfaces (e.g., Plan). Those can serve as output, but not input.

To help make up the gap, GraphQL has the concept of an “input object”. This is another data type, closely resembling a regular object type. However, it only supports scalars or other input objects as fields.

The GraphQL schema shown earlier had one of these, one that we just ignored at the time:

  input TripInput {
    startTime: String!
    title: String!
    priority: Priority!
    duration: Int!
  }

TripInput contains a few of the fields from Trip. However, technically, Trip and TripInput are unrelated, in terms of the schema. TripInput is only used in the createTrip() field on the Mutation object:

  type Mutation {
    createTrip(trip: TripInput!): Trip!
  }

It so happens that createTrip() takes the data from the supplied TripInput and creates a Trip. However, that is business logic implemented in the JavaScript that handles operations with createTrip() — there is nothing in the schema itself that says that TripInput has anything to do with Trip.

Mostly, using input objects is a way to shrink the argument list for a field. createTrip() could just as easily request a bunch of arguments, reflecting the fields that we happen to define on TripInput, rather than accepting a TripInput. Particularly for arguments that are logically peers of one another — such as fields that all go directly into a new Trip – using an input object will be a useful design pattern, but it is merely a pattern, not a requirement.

Argument Patterns

A number of common Web service API patterns can be implemented via arguments on root fields, such as those outlined in the following sections. This is not a comprehensive list of such patterns, let alone all possible ways of using arguments, but they should give you an idea of the role that arguments play.

Searching and Sorting

The searchFor argument is a crude, but simple, way of expressing a search request. The client has no means of indicating where we should be using that search term, or even how the search term should be used. That is up to the server, and it is up to the developers of the server to document the rules. The searchFor value could be interpreted in its own language, akin to how search engines, SQLite’s FTS3/FTS4 tables, and the like use boolean algebra as part of interpreting what you supply as a search expression.

It is also possible to offer richer syntax for searching directly in the GraphQL itself. For example, we could have an input object that mirrors likely fields on Trip that allow us to provide search expressions for those specific fields (or null as a wildcard, accepting anything):

input TripSearchCriteria {
  title: String,
  notes: String,
  priority: Priority,
  minDuration: Int,
  maxDuration: Int
}

A searchTrips() field could take a TripSearchCriteria as input and apply the individual fields from those criteria (e.g., restricting results to trips with a duration in the specified range, if either or both of minDuration and maxDuration were supplied).

The expressive power available to the client is dictated by what the server wants to support. The graph.cool CRUD store offers a GraphQL interface to its database, one that code-generates a GraphQL schema supporting expansive searching/filtering criteria:

query find {
  findTrips(
    filter: {
      OR: [
        {
          AND: [
            {
              startDate_gte: "2017"
            },
            {
              title_starts_with: "Vacation"
            }
          ]
        },
        {
          priority: "OMG"
        }
      ]
    }
  ) {
    id title
  }
}

Here, we would be retrieving any trip where the priority is OMG or the title starts with Vacation and is in 2017 or beyond.

Similarly, the GraphQL schema might support arguments for server-side sorting of the results (e.g., a sortBy argument taking the name of a field).

CRUD

With REST-style Web services, updates to resources are handled via particular HTTP actions (PUT, PATCH, etc.), with the content representing the update coming in the HTTP request body.

With GraphQL, updates to resources are handled by mutations, with the content representing the update coming in the arguments. For example, this could be in the form of an input object, so there is a single argument to the insert() mutation (with the content to be inserted), or two arguments to an replace() (with the second being the unique identifier of the content to be replaced).

We will see some CRUD later in this chapter.

Paging

Many Web service APIs work off of a “page-at-a-time” metaphor, as asking for the entire data set might be too much for either the client or the server. Classic implementations include:

Using Arguments in Android

The Trips/CW/SearchArgs sample project adds a SearchView to the DynamicList sample from the chapter on dynamic GraphQL. When the user searches on something, we will use findTrips() to find the subset of trips that match the search expression, then show that subset in the list.

So, we have a menu resource with our SearchView:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

  <item
    android:id="@+id/search"
    android:actionViewClass="android.widget.SearchView"
    android:icon="@drawable/ic_search_white_24dp"
    android:showAsAction="ifRoom|collapseActionView"
    android:title="@string/menu_search">
  </item>

</menu>

That, in turn, is set up in query mode, with the submit button enabled, in onCreateOptionsMenu() of our SimpleTripsFragment:

  @Override
  public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
    inflater.inflate(R.menu.actions, menu);

    search=menu.findItem(R.id.search);

    sv=(SearchView)search.getActionView();
    sv.setOnQueryTextListener(this);
    sv.setOnCloseListener(this);
    sv.setSubmitButtonEnabled(true);
    sv.setIconifiedByDefault(true);
    sv.setIconified(true);

    super.onCreateOptionsMenu(menu, inflater);
  }

When the user clicks that submit button in the SearchView, we ask the MainActivity to search for the requested search expression:

  @Override
  public boolean onQueryTextSubmit(String query) {
    ((MainActivity)getActivity()).searchFor(query);
    search.collapseActionView();

    return(true);
  }

That, in turn, creates a new SimpleTripsFragment, using a new searchFor() factory method, and adds that new fragment to the back stack:

  void searchFor(String search) {
    getFragmentManager().beginTransaction()
      .replace(android.R.id.content,
        SimpleTripsFragment.searchFor(search))
      .addToBackStack(null)
      .commit();
    updateTitle(search);
  }

searchFor() on SimpleTripsFragment just stuffs the search expression into the arguments Bundle:

  public static SimpleTripsFragment searchFor(String search) {
    SimpleTripsFragment result=new SimpleTripsFragment();
    Bundle args=new Bundle();

    args.putString(ARG_SEARCH, search);
    result.setArguments(args);

    return(result);
  }

In onCreate() of SimpleTripsFragment, we still set up our Observable. However, now, we see if we have a search expression, and we choose different sources of data based on that, calling either the original query() method or a new search() method:

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setRetainInstance(true);
    setHasOptionsMenu(true);

    final String searchFor=getSearchExpression();

    observable=Observable
      .defer(new Callable<ObservableSource<GraphQLResponse>>() {
        @Override
        public ObservableSource<GraphQLResponse> call() throws Exception {
          return(Observable.just(searchFor==null ? query() : search(searchFor)));
        }
      })
      .subscribeOn(Schedulers.io())
      .observeOn(AndroidSchedulers.mainThread())
      .cache();
  }

Using arguments without variables is… annoying, at best. We have to use string interpolation, replacing placeholders with our desired search expression, and hope for the best. That is only possible using dynamic GraphQL — code generators do not expose the GraphQL source for you to massage this way.

So, we have a SEARCH_DOCUMENT with our dynamic GraphQL, with a %s placeholder for the searchFor argument value:

  private static final String SEARCH_DOCUMENT=
    "{ findTrips(searchFor: \"%s\") { id title startTime priority duration creationTime } }";

search() then uses String.format() to add the search expression to the GraphQL document:

  private GraphQLResponse search(String search) throws IOException {
    HashMap<String, Object> payload=new HashMap<>();
    Gson gson=new Gson();
    String query=String.format(SEARCH_DOCUMENT, search);

    payload.put(QUERY, query);

    String body=gson.toJson(payload);
    Request request=new Request.Builder()
      .url(ENDPOINT)
      .post(RequestBody.create(MEDIA_TYPE_JSON, body))
      .build();
    Response okResponse=new OkHttpClient().newCall(request).execute();

    return(gson.fromJson(okResponse.body().charStream(), GraphQLResponse.class));
  }

The revised app now has a SearchView, initially collapsed into an icon:

SearchArgs Demo App, As Initially Launched
Figure 27: SearchArgs Demo App, As Initially Launched

Tapping the icon opens up the SearchView proper, where you can type in a search expression:

SearchArgs Demo App, with Search Expression
Figure 28: SearchArgs Demo App, with Search Expression

Submitting that SearchView (click the rightward-pointing caret) brings up a list of the search results:

SearchArgs Demo App, with Search Results
Figure 29: SearchArgs Demo App, with Search Results

Variables

Using arguments the way that we did in that sample app, all the time, would be aggravating. Yes, GraphQL is a string, and so we can splice in our dynamic data (e.g., what the user typed into the search field). But we also have to take into account formatting rules for that data. For example, the previous sample app should fail if you try searching on something with a quotation mark, because we are not doing anything to escape that quotation mark, and we wind up with broken GraphQL. While a “Little Bobby Tables”-style attack seems unlikely, it is better not to risk it at all.

With SQLite in Android, we can use parameters:

SELECT * FROM foo WHERE id=?

Here, ? gets replaced at runtime with the first element out of a parameters array that we provide in our rawQuery() call against a SQLiteDatabase. SQLite is responsible for handling formatting (e.g., wrapping our strings in quotes and escaping anything necessary). And, as a side-effect, we get some degree of protection against SQL injection attacks.

Similarly, we can use variables in GraphQL, to describe input to an operation, then apply that input. And, we get the same basic benefits as with SQLite parameters: automatic handling of any required escaping, and some measure of protection against what might be considered “GraphQL injection attacks”.

Declaring the Variables

In a GraphQL operation, after the operation name, you can have a comma-delimited list of variable declarations:

query find($search: String!) {
  findTrips(searchFor: $search) {
    id
    title
    startTime
    priority
    duration
    creationTime
  }
}

The syntax for a variable declaration is:

So, in the above sample, we have a single variable, named $search, that is a non-null String.

Applying the Variables

Then, anywhere in your GraphQL that you have arguments, rather than hard-coding a value, you can reference the variable. In the above sample, the searchFor argument in findTrips() is now $search instead of "ca" or some other fixed string.

The data type of the argument dictates the required data type of the variable. There is no format coercion in GraphQL, the way we see in some cases in Java.

For example, suppose we have this subtly-different GraphQL operation:

query find($search: String) {
  findTrips(searchFor: $search) {
    id
    title
    startTime
    priority
    duration
    creationTime
  }
}

This will give a syntax error in GraphiQL and would result in errors if we tried asking some server to execute it. Why? Because $search is declared as String, not String!. Even if the value we eventually use for this variable happens to be not null, that does not matter.

Hence, much of the time, you will find yourself working backwards:

Supplying Values for the Variables

When we send a GraphQL request to a server, we need to send three pieces of information:

That JSON object will have one field per variable:

{
  "searchFor": "ca"
}

However, the value of the field is dictated by the data type of the variable. So, a field could be a string, a number, a boolean, null, a nested JSON object (if the variable type is an input object), or an array of any of those things.

Servers may elect to do some type coercing, converting properties from the form you provided them into the form that the GraphQL schema called for. Do not assume that a server will do this. Instead, assume that the server will validate your request against the schema and return an error if you provide the wrong type (e.g., true instead of "true" for a string variable).

Typically, a complex GraphQL request is sent via an HTTP POST request, in which case the three pieces of the GraphQL request turn into fields of a larger JSON object that represents the request:

{
  "query": "query find($search: String) { findTrips(searchFor: $search) { id } }"
  "operationName": "find"
  "variables": {
    "searchFor": "ca"
  }
}

Variables in Android

The preview of this section was fed to a gremlin, after midnight.

A Little Bit of CRUD

The preview of this section apparently resembled a Pokémon.