/RESTDroid

Resource oriented REST client for Android

Primary LanguageJava

RESTDroid : REST client library for Android

Alpha release 0.7.2.5 : Testers and contributors are welcome :)

RESTDroid provides a way to handle REST call to REST web-service. RESTDroid only packed fundamental logic to handle request but comes with additionnal logic such as automatic data persistency with remote server. Using or extending this logic is the role of Module. Here you can found severals Module such as an ORMlite-Jackon module to handle data persistence and mapping/parsing.

RESTDroid Documentation

RESTDroid in a nutshell :

  • Make asynchronous REST request
  • You're not limited to one web service
  • Requests hold POJO's (can be your database model)
  • Network calls are not tied to your Activity, if the Activity is killed, network / database operations (ore whathever you decided to do) are still running
  • You can notify your Activities with request listeners
  • You can dynamically change the process logic via RESTDroid Module (choose to cache & persist, only debug, not to cache, or whatever you want/need by creating a new RESTDroid Module)
  • You can know at any moment if a particular local resource is remotely syncronized. Data persistence between local and remote is automatically handles.

Futures features for v1

#ROADMAP

  • ResourceList class to handle POJO's list (in next release)
  • Give to the user the possibility to choose a cache limit for any request and also a default action when request has failed (such as automatically retry the request every 10s, retry the request when a futur request will be sent successfully, etc.) (in next release)
  • Use HttpConnection instead of apache HTTP client
  • Handle authentication and certificate
  • Create a good Exception handling model

User guide

Getting started

RESTDroid available Modules :

Forward and return path schema :

Forward and return parth schema

User guide

Getting started

Download RESTDroid library and add it to your Android project. Update your android manifest :


<uses-permission android:name="android.permission.INTERNET" />
<application
        ...
        <activity
            ...
        </activity>
        <service android:enabled="true" android:name="fr.pcreations.labs.RESTDroid.core.RestService"></service>
    </application>
<service android:enabled="true" android:name="fr.pcreations.labs.RESTDroid.core.RestService"></service>

Implement RESTDroid boils down to implement a RESTDroid Module or use an existing one. RESTDroid Core library implements many hooks throughout the processus on which you can attached you specific logic. See image below : Schema All you have to do it's to create a new RESTDroid Module by extending Processor and WebService classes.

Create a RESTDroid Module

Module is the class that holds your own implementation of Processor, WebService, DaoFactory and ParserFactory classes plus whatever you need. Let's consider a very simple example. We just want to create a Test Module that only display response server in LogCat and only send GET request. This Module can be usefull if you just want to test a new web-service. Of course this is a totally overkill implementation for this simple use, but it's a good way to understand how all classes works together.

First create the TestModule class and add unimplemented methods :


public class TestModule extends Module {

	@Override
	public Processor setProcessor() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public ParserFactory setParserFactory() {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public DaoFactory setDaoFactory() {
		// TODO Auto-generated method stub
		return null;
	}

}

As you can see a Module is just a holder class to pack all you logic. We don't need a Parser class or Dao class, remember that it's a very simple example. We need a TestProcessor, let's create it :


public class TestProcessor extends Processor {

	@Override
	protected void preRequestProcess(
			RESTRequest<ResourceRepresentation<?>> r)
			throws Exception {
		r.addHeader("Accept", "application/json");
		r.addHeader("Content-Type", "application/json");
		/* Parse.com Authentication headers */
		r.addHeader("X-Parse-Application-Id", TestWebService.APPLICATION_ID);
		r.addHeader("X-Parse-REST-API-Key", TestWebService.REST_API_KEY);
	}

	@Override
	protected void preGetRequest(
			RESTRequest<ResourceRepresentation<?>> r) {
		// TODO Auto-generated method stub
		
	}

	@Override
	protected void preDeleteRequest(
			RESTRequest<ResourceRepresentation<?>> r) {
		// TODO Auto-generated method stub
		
	}

	@Override
	protected InputStream prePostRequest(
			RESTRequest<ResourceRepresentation<?>> r) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected InputStream prePutRequest(
			RESTRequest<ResourceRepresentation<?>> r) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	protected int postRequestProcess(
			int statusCode, RESTRequest<ResourceRepresentation<?>> r, InputStream resultStream) {
		if(statusCode >= 200 && statusCode <= 210)
			Log.i(TestService.TAG, inputStreamToString(resultStream));
		else
			Log.i(TestService.TAG, "Error : status code = " + String.valueOf(statusCode));
		return statusCode;
	}
	
	private String inputStreamToString(InputStream is) {
        BufferedReader bufferedReader;
		try {
			bufferedReader = new BufferedReader(new InputStreamReader(is, "UTF-8"));
			StringBuilder inputStringBuilder = new StringBuilder();
	        String line;
			try {
				line = bufferedReader.readLine();
				while(line != null){
		            inputStringBuilder.append(line);inputStringBuilder.append('\n');
		            try {
						line = bufferedReader.readLine();
					} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
		        }
				return inputStringBuilder.toString();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
	        
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        
        return null;
	}

Our TestProcessor could looks like this. We are overriding only few hooks :

  • preRequestProcess hook : used here to defined some headers to add in all request. In this example I decided to use Parse.com for rapid testing, so I need to use authentication header. It's a good hook to setting them up.
  • postRequestProcess hook : used here to display in LogCat the server's response

Now let's implement the TestWebService


public class TestWebService extends WebService {

	public static final String APPLICATION_ID = "your_application_id";
	public static final String REST_API_KEY = "your_rest_api_key";
	public static final String TAG = "fr.pcreations.labs.RESTDROID.sample.TestWebService.TAG";
	
	private static final String BASE_URI = "https://api.parse.com/1/classes/";
	private static final String TEST_OBJECT = "Test/";
	
	/* Must defines this constructor for dynamic instanciation */
	public DebugWebService(Context context) {
		super(context);
	}
	
	public void getTest(RESTRequest<TestObject> r, String id) {
		get(r, BASE_URI + TEST_OBJECT + id);
	}

}

TestWebService acts as a helper. It provides a simple asynchronous API to the user interface. When implementing your own WebService class you must implement the constructor for dynamic instanciation.

getTest() takes a RESTRequest as a first argument. RESTRequest is the object that represents our request and must be parameterized with the item that will be sent/received.

In this example, I've created un Test object in Parse.com which have the following fields :

  • String objectId
  • String content
  • String title
  • String createdAt
  • String updatedAt

Let's create this ResourceRepresentation :


public class TestObject implements ResourceRepresentation<String> {

	/**
	 * 
	 */
	private static final long serialVersionUID = 2958835534649642979L;
	
	private String mId;
	private String mContent;
	private String mTitle;
	private Date mCreatedAt;
	private Date mUpdatedAt;
	
	public TestObject(String mId, String mContent, String mTitle,
			Date mCreatedAt, Date mUpdatedAt) {
		super();
		this.mId = mId;
		this.mContent = mContent;
		this.mTitle = mTitle;
		this.mCreatedAt = mCreatedAt;
		this.mUpdatedAt = mUpdatedAt;
	}

	public String getId() {
		return mId;
	}


	/* METHOD FOR DATA PERSISTENCE AND CACHING */

	public int getState() {
		// TODO Auto-generated method stub
		return 0;
	}

	public int getResultCode() {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean getTransactingFlag() {
		// TODO Auto-generated method stub
		return false;
	}

	public void setId(String id) {
		mId = id;
	}

	public void setState(int stateRetrieving) {
		// TODO Auto-generated method stub
		
	}

	public void setTransactingFlag(boolean transacting) {
		// TODO Auto-generated method stub
		
	}

	public void setResultCode(int resultCode) {
		// TODO Auto-generated method stub
		
	}

	/* END METHOD FOR DATA PERSISTENCE AND CACHING */

	public String getContent() {
		return mContent;
	}

	public String getTitle() {
		return mTitle;
	}

	public Date getCreatedAt() {
		return mCreatedAt;
	}

	public Date getUpdatedAt() {
		return updatedAt;
	}

	public void setmContent(String content) {
		mContent = content;
	}

	public void setmTitle(String title) {
		mTitle = title;
	}

	public void setmCreatedAt(Date createdAt) {
		mCreatedAt = createdAt;
	}

	public void setUpdatedAt(Date updatedAt) {
		mUpdatedAt = updatedAt;
	}
	
}

Interface ResourceRepresentation is paramaterized with the type of item id field.

We're almost done, just a little obvious step : set the TestProcessor in TestModule :


public class TestModule extends Module {

	@Override
	public Processor setProcessor() {
		return new TestProcessor();
	}

	@Override
	public ParserFactory setParserFactory() {
		return null;
	}

	@Override
	public DaoFactory setDaoFactory() {
		return null;
	}

}

Use a RESTDroid Module

Using a RESTDroid Module is very simple. All you have to do is to register this module to your WebService class. It's a very small snippet :


public class TestActivity extends Activity {

	private DebugWebService ws;
	private RESTRequest<TestObject> getTestRequest;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        RESTDroid.init(getApplicationContext());
        try {
			ws = (TestWebService) RESTDroid.getInstance().getWebService(TestWebService.class);
			ws.registerModule(new TestModule());
			getTestRequest = ws.getTest(TestObject.class, "mG2hB0Xvco"); /* we want to retrieve the object with id "mG2hB0Xvco" from the 
			server */
			getTestRequest.setRequestListeners(this, TestRequestListeners.class); /* we now register RequestListeners class */
			ws.executeRequest(getTestRequest) /* and we execute the request */
		} catch (RESTDroidNotInitializedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
        setContentView(R.layout.activity_main);
    }

    public class TestRequestListeners extends RequestListeners {
    	private OnStartedRequestListener onStart = new OnStartedRequestListener() {

    		public void onStartedRequest() {
    			Log.i(TestWebService.TAG, "getTestRequest has started");
    		}
    		
    	};
    	
    	private OnFinishedRequestListener onFinished = new OnFinishedRequestListener() {

    		public void onFinishedRequest(int resultCode) {
    			Log.i(TestWebService.TAG, inputStreamToString(this.getResultStream())); //You can access the server response by calling RequestListeners#getResultStream()
    			Log.i(TestWebService.TAG, (Test) mRequest.getResourceRepresentation().toString()); //You can access the request wich is holding this RequestListeners class via member RequestListener#mRequest
    			Log.i(TestWebService.TAG, "getTestRequest has finished with code " + resultCode);
    		}
    		
    	};
    	
    	private OnFailedRequestListener onFailed = new OnFailedRequestListener() {
    		
    		public void onFailedRequest(int resultCode) {
    			Log.i(TestWebService.TAG, "getTestRequest has failed with code " + resultCode);
    		}
    		
    	};
    	
    	public TestRequestListeners() {
    		super();
    		addOnStartedRequestListener(onStart);
    		addOnFinishedRequestListener(onFinished);
    		addOnFailedRequestListener(onFailed);
    	}
    }

}

We're done ! Launch your test application and you will see in LogCat the response server :). Again, this a totally overkill implementation for this use, you've seen only 10% of RESTDroid potential. Now let's take a look of a complete exemple with ORMLite for local database and Jackson for parsing/mapping and request listeners : ORMLiteJacksonModule