Wednesday, November 20, 2013

Storing Centralized Mobile Data with Parse and Kinvey

Storing centralized data is important to mobile apps and simple websites alike but many developers don't have the resources to set up their own middle and back end tiers. Independent mobile developers especially can benefit from cheap and simple storage options that they don't need to manage themselves. I've recently learned about several back end as a service providers that allow developers to skip maintaining their own middle tier and databases and store data straight in a third party through API calls. While some might balk at giving someone else full control over storage, the services can allow for great cost savings over setting everything up yourself. Additionally, most providers support both mobile apps and websites coded in different languages. The usage, pricing and features of the providers varies certainly vary so its helpful to compare to see what might be a good fit. I myself have been seeking a good storage option for some of my Android mobile efforts so I thought I'd take a look at a couple of the options available today.

Parse

Parse is a backend data service that has SDKs for mobile devices and web sites alike. It has a free tier for up to a million data requests or pushes a month and a $199 dollar a month paid tier in addition to an Enterprise tier. It is pretty easy to get up and running.

The first step is to get set up on Parse's website with an account and download the SDK. You can register a project and get your application id and client key that you will need in the code. Once your all set up you are ready to code. You start by initializing Parse with the app id and key:

Parse.initialize(this, "myappid","mykey");
Once initialized you can start accessing data. We'll start by creating a simple user account and signing in:
ParseUser user = new ParseUser();
user.setUsername("Tim");
user.setPassword("Tester");
user.setEmail("tim@test.com");

user.signUpInBackground(new SignUpCallback() {
    @Override
    public void done(ParseException arg0) {
        if (arg0 == null) {
            //sign up was successful
        }
    }
});

Parse methods have synchronous and asynchronous methods so you can also call the standard signUp method if you don't care for multi-threading. Once you have user accounts created you can let users log in. Once again this has synchronous and asynchronous versions:

ParseUser.logInInBackground("Tim", "Tester", new LogInCallback() {
    public void done(ParseUser user, ParseException e) {
        if (user != null) {
            //do work here
        } else {
            //log in failed          }
        }
    }
});   

If you aren't storing the current user and need to get it at a later point you can call getCurrentUser. You can also log them out the current user without having a pointer to them with the logout function:

ParseUser user = ParseUser.getCurrentUser();
ParseUser.logout();

Storing Objects

Next we will look at how data can be stored and retrieved. This can be done with or without user accounts. Objects are created using ParseObject and giving a name for the specific object you want to create. You can then add as many fields to it as you like. The following demonstrates saving user reviews and attaching them to the logged in user:

ParseUser user = ParseUser.getCurrentUser();
ParseObject review1 = new ParseObject("Review");
review1.put("title", "Sam's Restaurant");
review1.put("content", "It was great.");
review1.put("user", user); //this association is optional
review1.save();

ParseObject review2 = new ParseObject("Review");
review2.put("title", "Mary's Diner");
review2.put("content", "Service was slow.");
review2.put("user", user); //this association is optional
review2.save();

We now have two objects called Review associated with the current user. The association allows us to easily retrieve the specific user's records:

ParseQuery query = ParseQuery.getQuery("Review");
query.whereEqualTo("user", user);
List reviews = query.find();
for (ParseObject review : reviews) {
    String title = review.get("title");
    ...
}

If we wanted to further narrow down results from the query we could add additional filters. Retrieved objects can of course be updated by calling save or saveInBackground:

review.put("content", "Not so good."); //update content
review.saveInBackground();

Finally you can delete an object by using delete or deleteInBackground:

review.deleteInBackground();

User Security

Another feature of Parse is the ability to set the security on objects. For example, if we only want to allow the logged in user read and write access to a specific review, we can do this:

ParseUser user = ParseUser.getCurrentUser();
ParseACL acl = new ParseACL();
acl.setReadAccess(user, true);
acl.setWriteAccess(user, true);
review.setACL(acl); //apply the control list to the review
review.save();

You can add as many users to the read and write permissions on an ACL as you like. You can also call setPublicReadAccess(true) or setPublicWriteAccess(true) if you want to allow all users to have one of those permissions.

Browsing Data

A nice feature of Parse is an online data browser allowing you to track your access and your data. You can access it by logging on to the parse home page and choosing Access Dashboard. Once inside you can view users and objects, ACLs, notifications and information on how often data is accessed.

There is lots more to try of course but this is just to demonstrate the basics. Their useful android guide can be found here.

Kinvey

Kinvey offers a lot of similar features to parse but has a more generous pricing structure centered around the number of users. Like Parse it is a great starting point for simple projects. The first step is to register on the Kinvey site and download the SDK. Like Parse, Kinvey supports many different clients such as BackBone, HTML5 and Node.js. Once you have the SDK and your app key and secret, you can create your client object:

String appKey = "xxxx";
String appSecret = "xxxxx";
final Client kinveyClient = new Client.Builder(appKey, appSecret, this.getApplicationContext()).build();

After creating our client, we need to call logout to make sure no default user is logged in:

kinveyClient.user().logout().execute();

Now we can create user's similar to Parse. This will also log the user in which is why its important to call logout first:

kinveyClient.user().create("Tim", "Tester", new KinveyUserCallback() {
    public void onFailure(Throwable t) {
        //log error
    }
    public void onSuccess(User u) {
        //user code here            
    }
});

To log the user in in the future we can call the login function:

kinveyClient.user().login("Tim", "Tester", new KinveyUserCallback() {
    public void onFailure(Throwable t) {
        //log error
    }
    public void onSuccess(User u) {
        //user code here             
    }
}); 

Storing Data

Data is stored in Kinvey by creating objects that extend from GenericJson. You list your fields as private and then can access them like dictionary objects using get and put methods. You can optionally use the Key annotation to specify the exact name of the field you want it to be stored in:

public class ReviewEntity extends GenericJson {
    @Key("Title")
    private String title;
    private String content;
}

To create and save the object, we can do the following:

ReviewEntity review1 = new ReviewEntity();
review1.put("title", "Corner Bistro");
review1.put("content", "Interior was dirty.");
AsyncAppData reviews = kinveyClient.appData("reviewCollection",ReviewEntity.class);
  
reviews.save(review1, new KinveyClientCallback() {
    @Override
    public void onFailure(Throwable e) {
        //log error here
    }

    @Override
    public void onSuccess(ReviewEntity r) {
    }
});

Once an object is saved, a string id is automatically created and can be fetched with get("_id"). You can also specify your own property to map to it by using the @Key("_id") annotation when defining your entity class. This property is helpful if you need to retrieve an object based on its id.

Querying

You can easily fetch data from a collection by using the entities id string in the getEntity call or by creating a Query and calling get. The following shows a simple query to get back a review:

Query query = kinveyClient.query();
query.equals("title","Corner Bistro");
reviews.get(query, new KinveyListCallback() {
    @Override
    public void onSuccess(ReviewEntity result[]) {
        //use restaurant here
    }
    @Override
    public void onFailure(Throwable error) { 
       //handle error here
    }
});

You can also skip the query parameter and get all the records back. To retrieve a specific object, you can call getEntity and specify the objects string id.

User Security

Similar to Parse you can also set up access control lists to allow read or write access to data. If we want to apply read permissions on our review entity, we can modify our class like this:

public class ReviewEntity extends GenericJson {

    private String title;
    private String content;

    @Key("_acl")
    private KinveyMetaData.AccessControlList acl;
 
    public KinveyMetaData.AccessControlList getACL() {
        return acl;
    }
 
    public ReviewEntity() {
        acl = new KinveyMetaData.AccessControlList();
    }
}

Now when creating a review, we can specify that only our current user is to be able to read it. After logging in, user holds Tim:

ReviewEntity review1 = new ReviewEntity();
review1.put("title", "Secured Review");
review1.put("content", "Only viewable by Tim");
ArrayList users = new ArrayList();
users.add(user.getId());
review1.getACL().setGloballyReadable(false);
review1.getACL().setRead(users);

Now we will be able to query this object like we did above. If we log in with someone else and search for that title, we will get an empty list. Kinvey supports a lot more as you can imagine. For more useful things to do you can refer to this site.

Conclusion

So this was a basic look at Kinvey and Parse. Both are great for providing data backends mobile apps that do not have the infrastructure or ability to manage their own database. There are several more out there such as StackMob or StorageRoom and which is best for you may likely come down to pricing and usage terms.

Friday, November 15, 2013

Handling Activity Re-creation in Android Fragments

I like using ActionBar tabs in Android for simple applications where I want to quickly switch between two or three views. Fragments are easy to create and switch between. One of the challenges I have found however is properly handling orientation changes or activity pauses. Since activities are recreated when display changes occur, I have seen artifacts such as tabs drawing on top of each other and data getting erased for certain kinds of controls. I have found a couple of things to help me provide a consistent user experience.

First, its very important to have a properly coded Tab Listener. The listener is what controls how tabs are added or removed to their parent based on their selection. Its most important of course to attach and detach but I found that its important to first look for the tab in the fragment manager and using it in place of the local variable if its found. Performing this step instead of just checking to see if the local variable is set prevented issues of tabs drawing on top of each other:

public class TabListener implements ActionBar.TabListener {
    private android.app.Fragment mFragment;  
    private final Activity mActivity;  
    private final String mTag;
    private Class mClass;
 
    public TabListener(Activity activity, String tag, Class cl) {  
        mActivity = activity;  
        mTag = tag;  
        mClass = cl;
    }  

    public void onTabSelected(Tab tab, android.app.FragmentTransaction ft) {
        Fragment preInitFragment = mActivity.getFragmentManager().findFragmentByTag(mTag);
        if ((mFragment == null) && (preInitFragment == null)) { 
     mFragment = android.app.Fragment.instantiate(mActivity, mClass.getName());
     ft.add(android.R.id.content, mFragment, mTag);         
        } else if (mFragment != null) {
            ft.attach(mFragment);
        }
        else if (preInitFragment != null) {
            ft.attach(preInitFragment);
            mFragment = preInitFragment;
        } 
    }  

    public void onTabUnselected(Tab tab, android.app.FragmentTransaction ft) {
 if (mFragment != null) {  
     // Detach the fragment, because another one is being attached  
     ft.detach(mFragment);  
        }  
    }  

    public void onTabReselected(Tab tab, android.app.FragmentTransaction ft) {  
    }
}

So nothing fancy above, just an extra check to make use of the existing tab if its found.

A second issue I have with fragments (and activities in general) is that they get recreated when orientation changes happen. Controls that might get filled programmatically such as a listbox you might be filling will not automatically reload with the last value like a text box will. Thankfully, fragments support overriding onSaveInstanceState and onActivityCreated (similar to onRestoreInstanceState) so that custom values can be stored and retrieved. For example I had a list of items you could add to from a separate list that I wanted to keep track of. The following shows how you can store an array of integers representing the indexes of the selected items and then reload them in to the adapter when the activity is recreated:

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

        ArrayList<Integer> indexList = new ArrayList<Integer>();
        for (int i=0; i < contactListAdapter.getCount(); i++) {
            int id = contactListAdapter.getItem(i).getId();
            //get its index
            for (int counter=0; counter < masterList.size(); counter++) {
         if (masterList.get(counter).getId() == id) {
                    indexList.add(counter);
                    break;
                }
            }
        }
        savedInstanceState.putIntegerArrayList("contactList", indexList);
    }


    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
     super.onActivityCreated(savedInstanceState);
     
     if ((savedInstanceState != null) && (savedInstanceState.containsKey("contactList"))) {
            ArrayList<Integer> indexes = savedInstanceState.getIntegerArrayList("contactList");
            contactListAdapter.clear();
            for (int index : indexes) {
         contactListAdapter.addItem(masterList.get(index));
            }
     }
    }

It is also possible of course to write your own parcelable class to store the actual selected data from the adapter so as not to have to build an index list but I thought this was a little easier. Once re-created you are good to go.

Saturday, November 9, 2013

Setting up the Cubieboard 2 with Linux

The Raspberry Pi has become very popular among hobbyists and has given rise to many similar boards. One such is the Cubieboard. The Cubieboard is a single board computer made in China using an AllWinner system on a chip. The original was released in 2012 with a single Cortex A8 ARM chip clocked at 1GHz, 512 MB of RAM, a 10/100 megabit ethernet port and 4 GB of NAND flash RAM with Android pre-installed. In 2013, the Cubieboard 2 was released containing 1 GB of RAM and a dual core Cortex A8 followed by the Cubietruck which added 2GB of RAM and gigabit ethernet. A nice feature of all Cubieboards is the presence of a SATA connector, allowing for better disk performance compared to the flash or micro sd memory cards. Also, they are all under $100 with the original costing around $49, the Cubieboard 2 going for about $69 and the latest version costing around $89. I purchased a Cubieboard 2 to play with finding it really good bang for the buck.

The Cubieboard is roughly the size of a credit card and won't take up much space on a desk. The board comes with a USB to DC connctor which I hooked up to 2 amp USB wall charger I bought so I would not need to rely on another computer to power it. There are 2 USB ports so its easy to hook it up to a USB mouse and keyboard. It also features an HDMI out for connecting to monitors. You can power up the board as is and run Android 4 after connecting everything or you can replace the OS with something else. There are two ways you can accomplish this. First you can use Berryboot to write an Operating System to a micro sd card and then boot from the card as described here. Additionally you can flash the NAND memory that comes with it and replace the default Operating System. I chose the latter option.

Flashing the Cubieboard 2

The first step I took was to download and install PhoenixSuit from the CubieBoard site to my Windows 7 PC. This included a driver for recognizing the Cubieboard on my PC. I then downloaded a Linux image from the site to flash on to the board. I had trouble finding one that PhoenixSuit would accept and ultimately settled on Lubuntu desktop 1.06 from here. I then hooked up the Cubieboard to my PC using a micro USB to USB cable which holding down the FEL button at the same time (located right under the micro USB port). After it loaded the driver and recognized the board, I then disconnected it and launched PhoenixSuit.

At the top of the screen is a menu with an item labeled Firmware. I clicked on that and then browsed to the image I downloaded (lubuntu-desktop-nand.img.gz). I then reconnected the Cubieboard to the PC while holding down the FEL button and the program automatically started to flash the device. At the popup that initially appears, I chose Yes. After about 10 minutes or so, the job was done and I disconnected the board.

Running Lubuntu

Unfortunately as of right now I cannot get the HDMI output to work with Lubuntu. Instead, using my PC I used Putty to connect to it using Cubieboard2 as the address. The default user name and password are linaro and linaro. The first step I took was to set the IP address to something static to make it easier to work with other devices on the network. I edited the /etc/network/interfaces file to change the eth0 option to the following:

iface eth0 inet static
    address 192.168.1.50
    netmask 255.255.255.0
    network 192.168.1.0
    broadcast 192.168.1.255
    gateway 192.168.1.1

Then I restarted the networking service:

sudo service networking restart

After this I decided to try out the SATA port. I purchased a 320 GB 2.5 drive and a cheap external rack to mount it in. After hooking up the drive using the provided SATA cable, I checked my devices using fdisk and saw it listed as /dev/sda:

sudo fdisk -l

I then used fdisk to create a single partition on the drive:

sudo fdisk /dev/sda

At the prompt I entered n to tell it to create a new partition. I then chose primary with partition number of 1. I left the default values for the sectors blank so it would use the entire disk. At the main prompt I entered w to write the partition table to the disk. When this was done I exited.

With the partition created I was then able to format and mount the drive. I created an ext3 partition mounted at /storage with the following:

sudo mkfs.ext3 -b 4096 /dev/sda1
sudo mount /dev/sda1 /storage

Finally I added the following entry to /etc/fstab so it would mount after booting:

/dev/sda1   /storage   ext3    defaults 1   1

There is still plenty more to try with this board. Many distributions are available for Berryboot that may allow for a better experience and there is help available on the forums. This is an easy starting point though and will hopefully encourage others to try out these boards.

Tuesday, November 5, 2013

Creating Mobile Webpages with Django

Supporting both mobile and desktop browsers is an important part of any live website. A mobile webpage should be simpler and display the bare minimum of necessary information in a small space. Frameworks like ASP.NET MVC 4 have built in features that allow for detection of mobile devices so you can display different pages depending on how the site is being viewed. I wanted to look at some options for doing this in Django.

Django-mobile 0.3.0

This package is easy to install and doesn't require many steps to be used. Once working it adds a property to request object called flavour that you can query to see if its coming from a mobile device or not. The package can be found here. Setup is easy as described on the page itself. I used pip to install it and then added the necessary changes to settings.py:

TEMPLATE_LOADERS = (
    'django_mobile.loader.Loader',
    ...

TEMPLATE_CONTEXT_PROCESSORS = (
    'django_mobile.context_processors.flavour',
)

MIDDLEWARE_CLASSES = (
    ...
    'django_mobile.middleware.MobileDetectionMiddleware',
    'django_mobile.middleware.SetFlavourMiddleware',

INSTALLED_APPS = (
    ...
    'django_mobile',

Now when defining our function in views.py, we can simply look for the new property and pass it on.

def home(request):
  mobilePage = 0
  if request.flavour == "mobile":
    mobilePage = 1

  ...
  context = { 'mobilePage': mobilePage }
  return render(request, 'home', context)

Now we can use the property in our template. We can render custom tags inside the control statements to change the whole experience of the page based on the device:

<html>
<body>
    {% if mobilePage == 1 %}
    Welcome to the mobile version.
    {% else %}
    Welcome to the regular version.
    {% endif %}
</body>
</html>

Django-mobi

Another easy to use package is called Django-mobi and can be found here. Simply put the package inside your project and you can easily reference it in your code using an attribute. A bonus to this package is that it has the ability to exempt certain devices from being detected as described on their home page. To use the package we just need to add it to settings.py:

MIDDLEWARE_CLASSES = (
    ...
    'mobi.MobileDetectionMiddleware',
Now we can use it in our code. In practice, it works just like the example above:

from mobi.decorators import detect_mobile
...
@detect_mobile
def home(request):
    mobilePage = 0
    if request.mobile:
        mobilePage = 1

    context = { 'mobilePage': mobilePage }
    return render(request, 'home', context)
These are two decent options I found for customizing Django sites. There could be other ways as well but I felt these worked pretty nicely.