Simple React Native Application with GraphQL Server for Maximo

In this tutorial, we will create the very rudimentary React Native application - it will display the list of the POs in the WAPPR status, and if the user taps on the PO in the list, it will open the page with the PO details.

Install Expo

Expo is probably the easiest way to get started with the React Native.  Follow the instructions on https://docs.expo.io/versions/v33.0.0/introduction/installation/ and install the expo-cli on your computer, and the Expo application on your mobile device.

Use the expo init command to create the new project Choose the bare project template, without the tab or drawer navigation.

Basic structure

You can see the full source on: https://bitbucket.org/maximoplusteam/graphql-native-demo.git

As already discussed, our application will have two pages: one for the list of POs, and one for the details. We obviously need one more page for the login. For navigation between these pages, we will use the standard React Navigation library.

yarn add react-navigation

Create the file Main.js, it contains the app navigation logic:

import React, { Component } from "react";
import {
  createSwitchNavigator,
  createStackNavigator,
  createAppContainer
} from "react-navigation";
import LoginScreen from "./Login.js";
import ListScreen from "./List.js";
import DetailsScreen from "./Details.js";
import NavigationService from "./NavigationService.js";

const AppStack = createStackNavigator({
  List: ListScreen,
  Details: DetailsScreen
});

const AuthStack = createStackNavigator({ Login: LoginScreen });

const AppContainer = createAppContainer(
  createSwitchNavigator(
    { App: AppStack, Auth: AuthStack },
    { initialRouteName: "App" }
  )
);

export default class App extends Component {
  render() {
    return (
      <AppContainer/>
    );
  }
}

In this phase, we will not introduce the GraphQL yet, but we need the dummy pages to check is our navigation working correctly or not.

List.js:

import React from "react";
import { View, Text, Button } from "react-native";

export default props => (
  <View>
    <Text>List</Text>
    <Button onPress={props.navigation.navigate("Details")} />
  </View>
);

Details.js:

import React from "react";
import { View, Text} from "react-native";

export default props => (
  <View>
    <Text>Details</Text>
  </View>
);

Login.js:

import React from "react";
import { View, Text } from "react-native";

export default props => (
  <View>
    <Text>Login</Text>
  </View>
);

Finally, edit the App.js that was created during the project init.

import React from "react";
import Main from "./Main";

export default function App() {
  return <Main/>;
}

Test the application by running the yarn start.

Adding the GraphQL logic

So far, our application didn't do anything except navigating the empty pages. We will use Apollo Client library to connect the app to the GraphQL server. We also need to install the react-native-elements library,  and use its ready-made components:

yarn add react-native-elements apollo-cache-memory apolo-client apollo-link apollo-link-context apollo-link-error apollo-link-http base-64 graphql graphql-tag react-apollo react-scripts

Apollo Client is using the mechanism called "Links" to communicate with the GraphQL servers. GraphQL server for Maximo uses the Basic HTTP authentication, and it expects the standard base64 encoded authorization token, together with the API request. Our login page will store the token in the AsyncStorage (React Native default storage mechanism), and the link will read it from there and send with every request.  We also need to navigate to the login page if there is no token. The file containing the link logic is GraphQLLink.js:

import NavigationService from "./NavigationService.js";
import { ApolloClient } from "apollo-client";
import { ApolloProvider } from "react-apollo";
import { HttpLink, createHttpLink } from "apollo-link-http";
import { ApolloLink } from "apollo-link";
import { onError } from "apollo-link-error";
import { InMemoryCache } from "apollo-cache-inmemory";
import { setContext } from "apollo-link-context";

//import AsyncStorage from "@react-native-community/async-storage";
import { AsyncStorage } from "react-native";

const httpLink = createHttpLink({
  uri: "http://192.168.1.107:4001/graphql",
  credentials: "include"
});

const authLink = setContext(async (_, { headers }) => {
  // get the authentication token from local storage if it exists
  //  const token = localStorage.getItem("token");
  const token = await AsyncStorage.getItem("token");
  // return the headers to the context so httpLink can read them
  return {
    headers: {
      ...headers,
      authorization: token ? `Basic ${token}` : ""
    }
  };
});

const errorHandlerLink = onError(({ response, networkError }) => {
  if (networkError) {
    if (networkError.statusCode === 401) {
      AsyncStorage.removeItem("token");
      NavigationService.navigate("Login");
    } else {
      NavigationService.navigate("Error", { error: networkError });
      //display the error (for example network error)
    }
  }
});

export default new ApolloClient({
  link: errorHandlerLink.concat(authLink).concat(httpLink),
  cache: new InMemoryCache()
});

In the snippet above, replace the URI of the HTTP link with the IP address of your computer.  The above link uses the NavigationService.js to navigate to the login page in case of the wrong username and password. Below is its source code:

import { NavigationActions } from "react-navigation";

let _navigator;

function setTopLevelNavigator(navigatorRef) {
  _navigator = navigatorRef;
}

function navigate(routeName, params) {
  _navigator.dispatch(
    NavigationActions.navigate({
      routeName,
      params
    })
  );
}

// add other navigation functions that you need and export them

export default {
  navigate,
  setTopLevelNavigator
};

We have everything ready now to create our first GraphQL - powered component. Change the List.js to following:

import React, { Component } from "react";
import { View, Text, FlatList } from "react-native";
import gql from "graphql-tag";
import { Query } from "react-apollo";
import styles from "./Styles.js";
import { ListItem } from "react-native-elements";

const poQuery = gql`
  query($qbe: POQBE) {
    po(fromRow: 0, numRows: 20, qbe: $qbe) {
      ponum
      description
      status
      purchaseagent
      vendor
      receipts
      totalcost
      id
      _handle
      
    }
  }
`;

const qbe = { status: "=WAPPR" };

const POList = props => (
  <Query query={poQuery} variables={{ qbe }}>
    {({ loading, error, data }) => {
      if (loading) return <Text styles={styles.text}>Loading...</Text>;
      if (error) return <Text styles={styles.text}>Error :(</Text>;
      return (
        <FlatList
          data={data.po}
          keyExtractor={item => item.id}
          renderItem={({ item }) => (
            <ListItem
              title={item.ponum}
              subtitle={item.description}
              onPress={() =>
                props.navigation.navigate("Details", { data: item })
              }
            />
          )}
        />
      );
    }}
  </Query>
);

export default POList;

The poQuery const is the actual GraphQL query. It has one variable $qbe, that we use to limit the results to WAPPR POs. You can try out this query in the GraphQL playground, before running this app. The Apollo client Query component feeds the result data to React Native's standard FlatList component. The ListItem is a component from the react-native-elements library with the pre-defined styling. Note that there is no usage of the Apollo link we defined in the GraphQL.js. How then Apollo client runs the query?

Connecting to the actual Apollo HTTP link is the rule of the ApolloProvider. Typically, this is a root component that provides the connection to all the nested queries and mutations. Change the App.js to following:

import React from "react";
import { ApolloProvider } from "react-apollo";
import Main from "./Main";
import client from "./GraphQLLink";

export default function App() {
  return (
    <ApolloProvider client={client}>
      <Main />
    </ApolloProvider>
  );
}

Finally let's create the Details.js :

import React, { Component } from "react";
import { View, Text } from "react-native";
import { Input, Button } from "react-native-elements";

class Details extends Component {
  constructor(props) {
    super(props);
    this.state = props.navigation.state.params.data;
  }
  render() {
    return (
      <View>
        <Input lebel="PO" value={this.state.ponum} editable={false} />
        <Input label="Status" value={this.state.status} editable={false} />
        <Input
          label="Buyer"
          value={this.state.purchaseagent}
          editable={false}
        />
        <Input label="Vendor" value={this.state.vendor} editable={false} />
        <Input label="Receipts" value={this.state.receipts} editable={false} />
        <Input
          label="Total Cost"
          value={this.state.totalcost && String(this.state.totalcost)}
          editable={false}
        />
        <Button
          title="Workflow"
          onPress={() => this.props.navigation.navigate("Workflow")}
        />
      </View>
    );
  }
}

export default Details;

Notice that the Details page is not connected to the GraphQL data. Our application uses the React Navigation mechanism to pass the data from the List.js page. We are using the state instead of props here so that you can change the data through the mutation to the server if you want (Not covered in the tutorial). The button at the end of the form doesn't do anything yet. To see it in action, read the next blog post: React Native Workflow Routing with GraphQL Server