Neo4j

Embedding Neo4j to Android

post2

We all have been eagerly awaiting to run Neo4j on mobile devices, specially on Android. Finally it’s here. During the Android Hacking at Google I/O an amazing new Community project was announced: Neo4j Mobile for Android v0.1!The project is currently available on GitHub for experimentation and evaluation purposes. As the version 0.1 indicates that it is currently an incubation project, the project itself is fully-functional, but as-yet unsupported. We will be developing a small use-case to get ourselves acquainted with it.

How does it work?

Android has a notion of Services, which can be assumed to be as background processes. Neo4j Mobile for Android runs as a service, and is accessed via Android Inter-Process Communication using an AIDL (Android Interface Definition Language) connector. This makes it possible for multiple apps on an Android device to access the same database service. The picture below summarizes an example application architecture, taken from the original project, showing Neo4j Mobile for Android interacting with other solution elements:

Neo4j concept V1_0

The Neo4j project has a pretty modular structure. It basically consists of 4 modules, while the other modules have been omitted because they are much  more oriented towards server usage, REST and command-line invocations, all of which do not apply to this particular use-case.

  • neo4j-kernel : Core graph DB concepts, embeddable database, I/O, transactions, …
  • neo4j-graph-algo : Standard graph algorithms,
  • neo4j-graph-matching : Graph matching algorithms,
  • neo4j-lucene-indexing : Indexing support via Apache Lucene.

Embedding Neo4j

There are two ways to embedded Neo4j in your application:

  1. Reference the neo4j-android project as an Android library project, or
  2. Copy the neo4j-android JARs after building the project and use it like we have been doing with any other external Java library.

Building the Project

The Noe4J APK has been successfully built with Java 1.6, Ant 1.8.1, Android SDK r21 and tested on Android 4.0.3.

There’s a rudimentary demo app which is included in the project. We are going to build the project and install the app on our emulator/device.

  • First, you need to have Android SDK setup with Eclipse. Refer android developer site for details.
  • Get the source from here.
  • You need to create local.properties file in each project, which should point to the location of the Android SDK.
sdk.dir=C:\\Android\\android-sdk
  • Build and install the service
cd neo4j-android-service
ant clean debug
adb uninstall org.neo4j.android.service
adb install bin/neo4j-android-service-debug.apk
  • Build and install the dbinspector app
cd neo4j-android-dbinspector
ant clean debug
adb uninstall com.noser.neo4j.android.dbinspector
adb install bin/neo4j-android-dbinspector-debug.apk
  • Run the dbinspector app on the target device. The service will be started automatically.

DB_Inspector_app

NOTE: You might face a lot of errors while building the service and demo app. The problem basically arises due to the different version of build.xml present in neo4j-android project folder and the one in the android-sdk/tools/ant/ folder. The build error on the active terminal will display the lines where the discrepancy is present. Compare both the build.xml files and edit the one present in the neo4j-android folder to build the project successfully.

Go through the project page to gain in-depth knowledge of the implementation.


It’s now time to include Neo4j in our own use-case. As mentioned earlier, we will be referencing neo4j-android.jar as an external Java library, along with neo4j-android-client.jar and neo4j-android-common.jar. But before we begin working on our use-case, it’s advised to go through the DB-Inspector app code to better understand the API and the resources used.

Our aim here is to create a database, create some nodes and establish relationship between them. Once all done, we will be generating a log to verify it all went well.

After setting up your development environment on Eclipse, create a new Android Application Project. Keep the Target SDK version as 15.

new_proj

Eclipse automatically creates various folders inside your project. One of them is ‘Libs’ where you place your external libraries that you want to include in your build path. Copy neo4j-android.jar from neo4j-android\binneo4j-android-client.jar  from neo4j-android-client\bin and neo4j-android-common.jar from neo4j-android-common\bin  to Libs of your newly created android project.

The ‘Libs’ folder of your project will already be containing android-support-v4.jar.

Refresh your project to view newly added JARs in the Libs folder. Select them, right click, goto Build Path >> Add to Build Path. The JARs would be removed from the Libs folder and added to Referenced Libraries folder.

As explained earlier, Neo4j Mobile for Android runs as a service, and is accessed via Android Inter-Process Communication using an AIDL (Android Interface Definition Language) connector. AIDL allows you to define the programming interface that both the client and service agree upon in order to communicate with each other using interprocess communication (IPC). The Neo4j service interface has already been implemented, we now have to override onBind() to return the implementation of the Stub class.

Now we are all set to code. Open MyActivity.java and add the highlighted imports as below.

package com.bishwajeet.myneo4japp;

import org.neo4j.android.common.IGraphDatabase;
import org.neo4j.android.common.INeo4jService;
import org.neo4j.android.client.Neo4jService;

import android.os.Bundle;
import android.os.IBinder;

If the imports do not show any error it means you have successfully included the JARs in your build path.

Declare instances of INeo4jService and IGraphDatabase as below.

public class MainActivity extends Activity {

	private INeo4jService neoServiceManager;
	private IGraphDatabase graphDB;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

Our client application can bind to Neo4j Service by calling bindService(). When it does, it must provide an implementation of ServiceConnection, which monitors the connection with the service. The bindService() method returns immediately without a value, but when Android system creates the connection between the client and the service, it calls onServiceConnected() on the service connection, to deliver the IBinder that the client can use to communicate with the service. Create the ServiceConnection as given below.

private ServiceConnection neoServiceConnection = new ServiceConnection() {

	@Override
	public void onServiceDisconnected(ComponentName name) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onServiceConnected(ComponentName name, IBinder service) {
		// TODO Auto-generated method stub

	}
};

We will get back to what has to be done within the above two methods.

Since a service connection has been created, we can now bind our Neo4j service with our app inside the onCreate() method.

boolean neoConnect = Neo4jService.bindService(getApplicationContext(), neoServiceConnection);

Pass the application context as the first parameter and the service connection as second. If the connection is successfully established, it will return a boolean as true. Once done, fill the onServiceConnected as below. Leave the onServiceDisconnected() empty as of now.

@Override
public void onServiceConnected(ComponentName name, IBinder service) {
	neoServiceManager = INeo4jService.Stub.asInterface(service);
}

I will be utilizing the Options Menu for creating a simple UI for our app. Design the options menu to have options for creating a graph database, creating nodes, creating relationships and firing cypher query. Go through the reference for creating an Android Options Menu. Note that you can create any UI depending upon your requirement.

I have created four menu items in the options menu. We will look each of the one-by-one.

Creating Database Instance

Before we create our database instance, we need to insure a database of the same name does not already exists. Refer below code.

@Override
public boolean onOptionsItemSelected(MenuItem item)	{
	parceableError = new ParcelableError();
	switch (item.getItemId())	{
			case 	R.id.create_db:
				try {
					if(!neoServiceManager.databaseExists("MyNewGraphDatabase"))	{
							graphDB = neoServiceManager.openOrCreateDatabase("MyNewGraphDatabase", new ParcelableError());

						if(graphDB != null)	{
							logText.append("\nGraph DB created : MyNewGraphDatabase");
						}
						else	{
							logText.append("\nError in creating database!");
						}
					}
					else	{
						logText.append("\nDatabase already exists!");
					}
				}
				catch (RemoteException e)	{
					Log.e("RemoteException", e.toString());
					e.getMessage();
				}
				break;

Creating Nodes

We will be creating a total of four nodes, representing a Country, a State of that country and two cities of the State. The createNode() method has a return type Long, which will help us in referencing the node while creating relationships. Set property key-value pair according to your need.

case	R.id.create_node:
	try {
		if (neoServiceManager.databaseExists("MyNewGraphDatabase")) {
			logText.append("\nChecking database existence.");

			if(neoServiceManager.isDatabaseOpen("MyNewGraphDatabase")) {
				logText.append("\nDatabase is open.");
				logText.append("\nCreating Node...");

				graphDB.beginTx(parceableError);
				try {
					nodeCountry = new ParcelableNode();
					nodeState = new ParcelableNode();
					nodeCityOne = new ParcelableNode();
					nodeCityTwo = new ParcelableNode();

					nodeCountry.setProperty("Name", "India");
					nodeState.setProperty("Name", "Maharashtra");
					nodeCityOne.setProperty("Name", "Mumbai");
					nodeCityTwo.setProperty("Name", "Pune");

					nodeCountryID = graphDB.createNode(nodeCountry, parceableError);
					nodeStateID = graphDB.createNode(nodeState, parceableError);
					nodeCityOneID = graphDB.createNode(nodeCityOne, parceableError);
					nodeCityTwoID = graphDB.createNode(nodeCityTwo, parceableError);

					logText.append("\nCreated Country node : '"+nodeCountry+"' with nodeID '"+nodeCountry+"'.");
					logText.append("\nCreated State node : '"+nodeState+"' with nodeID '"+nodeStateID+"'.");
					logText.append("\nCreated City nodes : '"+nodeCityOne+"' with nodeID '"+nodeCityOneID+"', '"+nodeCityTwo+"' with nodeID '"+nodeCityTwoID+"'");

					graphDB.txSuccess(parceableError);
				} finally {
					graphDB.txFinish(parceableError);
				}
			}
		}
		else {
			logText.append("\nDatabase does not exist!");
		}
	} catch (RemoteException e) {
		Log.e("RemoteException", e.toString());
		e.getMessage();
	}
break;

Creating Relationships

We have created our nodes and have their corresponding nodeIDs. We will now establish relationships between them.

case 	R.id.create_rel:
	try {
		graphDB.beginTx(parceableError);

		try {
			relateCountryState = new ParcelableRelationship();
			relateCountryState.setStartNodeId(nodeCountryID);
			relateCountryState.setEndNodeId(nodeStateID);
			relateCountryState.setName("HAS");
			relateCountryStateID = graphDB.createRelationship(relateCountryState, parceableError);
			logText.append("\nCreated Country -> State relationship with RelationshipID: '"+relateCountryStateID+"'");

			relateStateCity = new ParcelableRelationship();
			relateStateCity.setStartNodeId(nodeStateID);
			relateStateCity.setEndNodeId(nodeCityOneID);
			relateStateCity.setEndNodeId(nodeCityTwoID);
			relateStateCity.setName("HAS");
			relateStateCityID = graphDB.createRelationship(relateStateCity, parceableError);
			logText.append("\nCreated State -> City relationship with RelationshipID: '"+relateStateCityID+"'");

			relateCountryCity = new ParcelableRelationship();
			relateCountryCity.setStartNodeId(nodeCountryID);
			relateCountryCity.setEndNodeId(nodeCityOneID);
			relateCountryCity.setEndNodeId(nodeCityTwoID);
			relateCountryCity.setName("HAS");
			relateCountryCityID = graphDB.createRelationship(relateCountryCity, parceableError);
			logText.append("\nCreated Country -> City relationship with RelationshipID: '"+relateCountryCityID+"'");

			graphDB.txSuccess(parceableError);
		} finally {
			graphDB.txFinish(parceableError);
		}
	} catch  (RemoteException e) {
		Log.e("RemoteException", e.toString());
		e.getMessage();
	}
break;

Deleting Database

Finally, we will be erasing the database and conclude the onOptionsItemSelected() method.

		case	R.id.delete_db:
				try {
					graphDB.beginTx(parceableError);

					try {
						boolean flag = neoServiceManager.deleteDatabase("MyNewGraphDatabase", new ParcelableError());
						if(flag)
							logText.append("\nDatabase Erased.");

						graphDB.txSuccess(parceableError);
					} finally {
						graphDB.txFinish(parceableError);
					}
				} catch (RemoteException e) {
					Log.e("RemoteException", e.toString());
					e.getMessage();
				}
				break;
		default:
			Toast.makeText(getApplicationContext(), "Welcome to Neo4j Android App!", Toast.LENGTH_LONG).show();
	}
	return super.onOptionsItemSelected(item);
}

Add the following declarations before the start of onCreate() method.

private INeo4jService neoServiceManager;
private IGraphDatabase graphDB;
private ParcelableError parceableError;
private ParcelableNode nodeCountry, nodeState, nodeCityOne, nodeCityTwo;
private ParcelableRelationship relateCountryCity, relateStateCity, relateCountryState;
private TextView logText;
private long nodeCountryID, nodeStateID, nodeCityOneID, nodeCityTwoID, relateCountryCityID, relateStateCityID, relateCountryStateID;

Your MyActivity.java page is complete now. We need to provide READ, WRITE & ADMIN permission in the AndroidManifest.xml file. Add the below lines in the manifest file.

<uses-permission android:name="org.neo4j.android.permission.READ"/>
<uses-permission android:name="org.neo4j.android.permission.WRITE"/>
<uses-permission android:name="org.neo4j.android.permission.ADMIN"/>

Building the Project

As we have done with the DBInspector App, we will be doing an Ant build of our project. But before that we must create the build.xml file for the Ant build.

<?xml version="1.0" encoding="UTF-8"?>
<project name="MyNeo4jApp" default="help">

    <property file="local.properties" />
    <property file="ant.properties" />
    <property environment="env" />
    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
        <isset property="env.ANDROID_HOME" />
    </condition>
    <loadproperties srcFile="project.properties" />
    <fail
            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
            unless="sdk.dir"
    />
    <import file="custom_rules.xml" optional="true" />
    <import file="${sdk.dir}/tools/ant/build.xml" />
</project>

Now do an Ant build of the project.

cd MyNeo4jApp
ant clean debug
adb install bin/MyNeo4jApp-debug.apk

Install the Neo4j service on your device first, followed by the MyNeo4jApp we just created. Test each of the options and analyze the messages logged onto the app screen. It should be like the one below.

Screenshot_2014-08-11-00-00-40

I hope this tutorial has been informative.

Now go and play with it!! 🙂