A quick rant on Android Studio

Posted By Hoyt Summers Pittman

As a software developer I love for things to be simple, and I hate for things to get between me, my code, and execution. As such I like powerful, scriptable build systems with wonderful dependency management (Gradle & make much love). I love for my coding tools to be able to use to be able to look at my build scripts and do intelligent things (see NetBeans with Maven, seriously this is done very, very right). I don’t like it when my coding tools try to subvert me (Eclipse is a war crime in this regard) or make changes on my behalf.

Android Studio, which is based on IntelliJ is starting toward the “subvert me” route by pestering me to use its instance of the Android SDK instead of my own. This is important because my instance of the SDK includes all of the packages downloaded already and it’s the location all of my build tools reference. Now when Android Studio overrides my choices it points my projects to a broken/unconfigured SDK, and suddenly things which worked hours before now have errors cropping up. Even worse, if I change my projects file back, Studio begins pestering me to use its SDK again, and it will every time you open your project until you use its SDK and start this whole mess over again.

I guess my point is, if your project breaks check your local.project file and make sure that it is using your SDK and not Android Studio’s.

Dec 24th, 2013

Realtime sync with AeroGear on Android

Posted By Hoyt Summers Pittman

AeroGear 1.2.0 included support for AeroGear Unified Push. Part of the technology underpinning that feature on Android are the Registrar APIs. These APIs make it very easy to create a message generating or message consuming Object and integrate it with your applications in Android. In my own time I have experimented with a MQTT PushRegistrar and a WebSocket PushRegistrar. Using the my WebSocket Push Registrar I created a very simple text sync demo.

As a quick bit of background, I also wrote the server component of the demo. It runs on vert.x and persists data to MongoDB. I implemented Neil Fraser’s Differential Synchronization process on both the client and server side. This algorithm handles merging the documents various states across devices. It is also very well written and easy to follow.

In this demo a user loads a document (using a RESTful Pipe). In the callback, the Activity registers itself as a MessageHandler with the Registrar API, creates a configuration object which describes a WebSocketPushRegisrar and passes this configuration into the Registrations class which creates a new Web Socket and connects it to the server. Now when the document is changed by a different application, a diff is received on the WebSocket and its contents are given to the Activity. If the Activity edits the document it creates a diff and saves it to the server. The server is in charge of synchronizing diffs across the different devices.

CRUD operations are performed using two Pipes. A Pad Pipe which is responsible for creating documents, getting lists of documents, and getting an individual document. The second Pipe is a PadDiff Pipe. Its job is to send diffs to the server.

//Create a Pad Pipe
PipeConfig padConfig = new PipeConfig(URL, Pad.class);
pipeline.pipe(Pad.class, padConfig);

//Use the Pad Pipe
//By passing in the padListFragment to  Pipeline#get we are telling Aerogear to 
//wrap the Pipe in a Loader.

public void getPads(GetPadsListFragment getPadsListFragment, PadCallback padCallback) {
    pipeline.get("pad", getPadsListFragment, this).read(padCallback);
}

//Create a PadDiff pipe.  PadDiff pipes are 1:1 with the "Session" which is a identifier for the web socket.
//In this app a session is killed with the fragment is put into the background.  Thus I decided
//to make a PadDiff Pipe 1:1 with the fragment.
public Pipe<PadDiff> padDiff(PadFragment padFragment, String sessionId) {
    PipeConfig config = new PipeConfig(URL, PadDiff.class);
    config.setName("padDiff");
    config.setEndpoint("/padDiff/" + sessionId);
    pipeline.pipe(PadDiff.class, config);
    return pipeline.get("padDiff", padFragment, this);
}

From the devices POV the documents are fetched using the following code:

ReadFilter filter = new ReadFilter();
//Currently the way to get an element by ID is to use the LinkUri.  
//This is scheduled to be fixed in a future version of Aerogear
filter.setLinkUri(URI.create("/" + padId));
pipeline.get("pad", fragment, this).read(filter, new PadCallback(padId));

In `PadCallback` the Pad is loaded and a WebSocket is created. When the websocket creation is finished, the server sends a message to the Activity with a session_id object. This message is received on the onMessage method of the Activity. This method is also responsible for handling error messages and handling diff messages which are updates from other clients.

First we handle the response from getting the pad:

//Called from PadCallback#onSuccess
public void handlePads(List<Pad> pads) {
    pad = pads.get(0);
    setupEditor(pad);
    PushConfig pushConfig = new PushConfig();
    pushConfig.setPushServerURI(URI.create("ws://10.0.2.2:8080/pad/" + pad.getId()));

    //This will open the web socket. 
    ((AeropadApplication) getActivity().getApplication()).register(pushConfig);
}

Then the Activity receives a callback from the WSPushRegistrar with a session id. The server will send different messages depending on what information it is sending to the client. In AeroGear a MessageHandler(in this case our Activity) receives a message from the WSPushRegisrar and extracts the content based on if there is a “diff”, “session_id”, or “error” object in the response. These keys are specific to this server but the general principle can be applied to any application.

@Override
public void onMessage(Context context, Bundle message) {
    try {
        String json = message.getString(WebsocketMessageReceiver.PAYLOAD);
        JSONObject object = new JSONObject(json);
        if (object.has("diff")) {
            handleDiff(object);
        } else if (object.has("session_id")) {
            //Create a PadDiff Pipe
            diffPipe = ((AeropadApplication)getActivity().getApplication()).padDiff(this, object.getString("session_id"));
        } else if (object.has("error")) {
            Log.e("ERROR", json);
            Toast.makeText(getActivity(), json, Toast.LENGTH_LONG).show();
        } else {
            Toast.makeText(getActivity(), json, Toast.LENGTH_LONG).show();
        }
    } catch (JSONException e) {
        Log.e("MESSAGE", e.getMessage(), e);
    }
 }

The full source code for the Android application can be found at my Github. It is build using the aar branch on the official AeroGear Android repository.

Oct 29th, 2013

AeroGear Android 1.2.0 Released

Posted By Hoyt Summers Pittman

After last month’s relatively small release, we are happy to being you AeroGear Android 1.2.0. This release brings support for Google Cloud Messaging and AeroGear Unified Push to the Android libraries. This release also marks the 1.1.0 release of the AeroGear project itself covering multiple serverside components, JavaScript libraries, and iOS libraries.

With AeroGear Android’s Unified Push support developers can easily register and process messages sent from the push server through Google Cloud Messaging to their devices.

What’s new

We’ve introduced several new APIs to manage the Unified Push network. A full run down can be found on aerogear.org, but here are the highlights.

Registrations : This is the factory class, similar to Pipeline. It is responsible for instanciating and referencing PushRegistrar instances. It also routes messages from AeroGearGCMMessageReceiver to MessageHandler instances.

PushRegistrar : This is an interface that provides methods to manage the connection to the Unified Push Server and GCM push networks.

MessageHandler : This interface provides method callbacks which will be invoked when a message is received by a push network.

AeroGearGCMMessageReceiver : An insteance of BroadcastReceiver which will consume Intents from GCM and public them to the Registrations class.

Examples

Where can I get it

  • Github
  • Maven Central
        <dependency>
                <groupId>org.jboss.aerogear</groupId>
                <artifactId>aerogear-android</artifactId>
                <type>apklib</type>
                <version>1.2.0</version>
        </dependency>
    
Aug 19th, 2013

AeroGear Android 1.1.0 Released

Posted By Hoyt Summers Pittman

I’ve been working with the AeroGear team to prepare a new release for Android and here it is.

New and Noteworthy

  • HTTP Digest Authentication Modules
  • HTTP Basic Authentication Modules
  • HTTP Multipart uploads
  • Configurable Request Builders
  • CI support provided by Travis-CI
  • Also we have begin to make changes necessary for breaking the assumption that JSON is the default serialization format and have introduced several new Interfaces for support this:

    • RequestBuilder
    • ResponseParser

    Internally we still assume JSON is used but that is going to be fixed in 1.2.0.

    As part of this release we have deprecated and cleaned up some APIs.

    • Pipe.getGSON is deprecated and replaced with Pipe.getRequestBuilder
    • A GSONRequestBuilder has been added.
    • Pipe.readWithFilter has been deprecated and replaced with an overloaded Pipe.read method.

    Code samples

    MultipartData

    The MultipartRequestBuilder class can handle normal POJOs. For your binary data it supports serializing byte arrays, InputStreams, and a special class called TypeAndStream which lets you set file type data. Currently the entire request is loaded into memory before it is sent off to the server, so this should not be used with large files or requests.

    MultiPartData.java

    public static class MultiPartData {
      @RecordId
      private String id;
      private InputStream data;        
    
      /*Getters and Setters*/
    }
    

    PipeUsage.java

    public static class MultiPartUsageActivity {      
      public void onCreate() {
        super.onCreate();
        PipeConfig config = new PipeConfig(SAMPLE_URL, MultiPartData.class);
        
        //provide a default MultiPartBuilder
        config.setRequestBuilder(new MultipartRequestBuilder<MultiPartData>());
    
        Pipeline pipeline = new Pipeline(SAMPLE_URL);
        Pipe<MultiPartData> restPipe = pipeline.pipe(MultiPartData.class, config);
        
        MultiPartData data = createData();//pseudo code method
        restPipe.save(data, new MyCallback());//pseudocode callback
    
      }
    }
    

    Digest or Basic Authentication

    Basic and Digest Authentication follow their respective RFC’s. For Digest authentication we extended the AuthenticationModule and added a new method:retryLogin. Classes implement this method if they need to automatically refresh a login (such as with Digest or OAuth). This method is called by AeroGear on its background threads and will block if it is called by the user.

    BasicApplication.java

    public void onCreate() {
      Authenticator authenticator = new Authenticator(SIMPLE_URL);
    
      AuthenticationConfig config = new AuthenticationConfig();
      config.setAuthType(AuthTypes.HTTP_BASIC);
      AuthenticationModule basicAuthModule = authenticator.auth("basic", config);
    
      basic.login("username","password", new MyCallback());
      //By the spec, HTTP Basic does not need to do any server calls.  
      //MyCallback.onSuccess is called immediately.
    }
    

    DigestApplication.java

    public void onCreate() {
      Authenticator authenticator = new Authenticator(SIMPLE_URL);
    
      AuthenticationConfig config = new AuthenticationConfig();
      config.setAuthType(AuthTypes.HTTP_DIGEST);
      AuthenticationModule digestAuthModule = authenticator.auth("basic", config);
    
      basic.login("username","password", new MyCallback());
      //By the spec, HTTP Digest has to make calls to the login endpoint.
      //The call will get the nonce values from the server.
      //Once nonce values are received, the module will generate the credentials 
      //but not send them to the service until another call is made via a Pipe.
    }
    

    Future

    For 1.2.0 we have several big plans. As always these may change and you, the community, are encouraged to speak your mind on our mailing list or our IRC channel.

    • GCM handler APIs
    • Support for the AeroGear Unified Push Service
    • Fully abstracted Requests and Response
    • AAR resources posted to Maven Central
    • Customized login parameters for Authentication Modules

    Getting it

    You can download AeroGear Android from github

    wget https://github.com/aerogear/aerogear-android/archive/1.1.0.tar.gz
    

    or from Maven Central

         <dependency>
            <groupId>org.jboss.aerogear</groupId>
            <artifactId>aerogear-android</artifactId>
            <version>1.1.0</version>
            <type>apklib</type>
         </dependency>
         <dependency>
            <groupId>org.jboss.aerogear</groupId>
            <artifactId>aerogear-android</artifactId>
            <version>1.1.0</version>
            <scope>provided</scope>
            <type>jar</type>
         </dependency>
    
    Jul 23rd, 2013
    Next Page »