Android Tutorial: Photo sharing

Learn our JavaScript SDK by getting your hands dirty.

Introduction

In this tutorial we will use the Appstax backend and the Android SDK to create a simple photo sharing app. Users can sign up, log in, see a feed of shared photos, and share their own by taking pictures with the camera.

The code for the finished app can be found in the Android SDK's GitHub repo. This tutorial will first describe how you get the app up and running: registering a new account, setting your app key, and creating your data model. We will then go through the most important bits of code in the app. Here are a couple of screenshots of the result:

App

Getting started

  • Download and install Android Studio.
  • Signup at appstax.com to create your account.
  • From the admin dashboard, create a new app called appstagram.

Creating collections

Now that your account and app is ready, let's set up the data model. First, create the collection that will hold items:

  • Go to the data browser and create a new collection called ItemCollection.
  • Create a new column called image, with the type file.

Each Appstax app comes with a standard users collection. We want each item in the ItemCollection to belong to a single user in that collection. Let's create a relation between the two collections:

  • Open the ItemCollection and click on the Relations button.
  • Click Add relation to create a new relation for this collection.
  • Enter the column name user, and select the users collection in the dropdown menu.
  • Set the relation type to has one, as each item belongs to a single user.

The users collection can also hold extra properties, and we want each user to have a nickname. The standard column sysUsername will hold the email of each user (used to log in), while the name column will be contain publicly displayed nicknames.

  • Click on the users collection.
  • Create a new column called name, with the type string.

Your data collections are now ready. Let's get the app running on your machine.

Download the code

  • Clone or download the Android SDK's GitHub repo.
  • Log in on appstax.com, click on your new app, click on "Settings", and copy your app key.
  • Open Android Studio, select "Import project", and import the examples/appstagram folder.
  • Open app/src/main/java/com/appstax/appstagram/BaseActivity.java and paste in your app key:
abstract class BaseActivity extends AppCompatActivity {

    protected static Appstax ax = new Appstax("YourAppKey");

    // ...

}

BaseActivity is the parent class of all of our app's activities. By instantiating the Appstax class here, we won't have to do that in every other class. This file also includes a bunch of helper methods that are used across the app.

Running the emulator

With your collections and app key set, let's now configure the Android emulator:

  • In Android Studio, go to Tools > Android > AVD Manager
  • Select the emulator you'd like to use, and click on Edit this AVD, to the far left.
  • Click on Show advanced settings, and scroll down to find keyboard and camera settings.
  • For the front and back camera, select Webcam0 (or similar) in both menus.
  • If you wish to use the keyboard, select Enable keyboard input, at the bottom.

You should now be able to run the app in the emulator:

  • In Android Studio, click on the Run app button to start the emulator and run the app.
  • Click on "sign up" to create a new account and log in.
  • Click on the icon in the top right to take a new photo.
  • The app will remember your login info.

We will now go through the most important parts of the code.

Dependencies and permissions

The Appstax Android SDK is avaiable through jcenter, and can be included in the app/build.gradle file:

dependencies {
    // ...
    compile 'com.appstax:appstax-android:+'
}

We also have to set the correct permissions in AndroidManifest.xml:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />

Signup and login

The main activity of the app is FeedActivity, which shows a list of shared photos.

Since the user has to be logged in to see this feed, the activitiy checks if the user is logged in. The SDK keeps track of the currently logged in user. Calling ax.getCurrentUser() will give you an AxUser instance if there is a logged in user, or null otherwise.

If there is no current user, FeedActivity hands over control to the LoginActivity class:

public class FeedActivity extends BaseActivity {

    // ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        if (ax.getCurrentUser() == null) {
            startActivity(LoginActivity.class);
            return;
        }

        // ...

    }

}

The login activity gets the users email and password either from saved preferences, or from manual input fields. We then use the Appstax class to log in:

protected void login(final String email, final String password) {
    ax.login(email, password, new Callback<AxUser>() {
        public void onSuccess(AxUser output) {
            saveLoginInfo(email, password);
            startActivity(FeedActivity.class);
        }

        public void onError(Exception e) {
            dialog("error", e.getMessage());
        }
    });
}

If the user needs to register, the RegisterActivity is started, which uses the Appstax class to create a new user in much the same way:

protected void signup(String email, String password) {
    ax.signup(email, password, new Callback<AxUser>() {
        public void onSuccess(AxUser user) {
            saveName(user, editTextVal(R.id.name));
            startActivity(FeedActivity.class);
        }
        public void onError(Exception e) {
            dialog("error", e.getMessage());
        }
    });
}

protected void saveName(AxUser user, String name) {
    user.put("name", name);
    ax.save(user, null);
}

Showing the feed of photos

Once the user is logged in, the FeedActivity class will fetch the latest photos from the ItemCollection, and show them in a RecyclerView:

protected void refresh() {
    items.clear();
    recyclerAdapter.notifyDataSetChanged();

    ax.find("ItemCollection", new Callback<List<AxObject>>() {
        public void onSuccess(List<AxObject> output) {
            for (AxObject object : output) {
                items.add(new FeedItem(object));
            }
            recyclerAdapter.notifyDataSetChanged();
        }

        public void onError(Exception e) {
            dialog("error", e.getMessage());
        }
    });
}

Whenever the refresh method is called, the existing items are cleared, and new items are fetched from the server. The FeedItem class is a view model that transforms AxObject instances into items in the feed.

The FeedItem is also responsible for loading the image data:


public class FeedItem {

    private AxObject object;

    // ...

    public void getImage(final ImageView image) {
        AxFile file = object.getFile("image");

        if (file.getData() != null) {
            setData(image, file.getData());
            return;
        }

        ax.load(file, new Callback<AxFile>() {
            public void onSuccess(AxFile output) {
                setData(image, output.getData());
            }

            public void onError(Exception e) {
                e.printStackTrace();
            }
        });
    }

}

If we've already loaded the image data from the server, we call setData, a helper for updating the list of photos.

If not, we use ax.load to request the file data from the server. This is necessary since the AxObject won't include file data in the initial response. With this method, file data is only requested from the server when needed.

Uploading a new photo

When the user whises to upload a new photo, the app hands over control to a newly created image capture intent, and wait for this intent to produce a result:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode != CAMERA_REQUEST || resultCode != RESULT_OK) {
        return;
    }

    showLoading();

    String file = UUID.randomUUID().toString() + ".jpg";
    AxObject object = ax.object(ITEM_COLLECTION);
    object.put("image", ax.file(file, uriToByteArray(this.output)));
    object.put("title", ax.getCurrentUser().get("name"));

    ax.save(object, new Callback<AxObject>() {
        public void onSuccess(AxObject output) {
            hideLoading();
            refresh();
        }

        public void onError(Exception e) {
            hideLoading();
            dialog("error", e.getMessage());
        }
    });
}

We first create a new AxObject that will hold the new image.

  • The title of the new post will be the name of the current user.
  • The image is an AxFile created from the output of the camera intent.

We then save the object, which will also upload the image.

Finally, we call refresh so that the new photo shows up in the user's feed.