Android Tutorial

69
1 www.stackmob.com Step by Step Guide for Android Build a Photo Sharing App in a Day

description

android tutorial,learning

Transcript of Android Tutorial

  • 1www.stackmob.com

    Step by Step Guidefor Android

    Build a Photo Sharing App in a Day

  • 2www.stackmob.com

    This series is focused on the creation of SnapStack, a location-based photo sharing app for Android

    phones. Well walk through the entire process of building SnapStack, from the initial idea and design to

    submitting to the Google Play Store. Along the way, well demonstrate the usefulness of the StackMob

    platform and highlight the benefits of incorporating StackMob into your next project. Download

    SnapStack from the Google Play Store.

    SnapStack Android Bootcamp Welcome!

    Who should read this tutorial?

    Prerequisites

    Idea

    What well cover

    This series is aimed at developers of all skill levels who are looking for an example app that showcases

    the features of the StackMob platform. The goal is to illustrate how easy it is to build and release to the

    Google Play Store using StackMob as the backend of your app. The pace of this tutorial will start out slow

    and ramp up quickly. Its recommended that you have a basic understanding of Java and the Eclipse IDE.

    Our app, SnapStack, will be a simple photo taking app. Users will be able to snap and share photos, view

    photos nearby both in a feed and on a map, and post comments. Download SnapStack from the Google

    Play Store.

    Creating an app like SnapStack requires a fair amount of setup. In this tutorial, well be adding all of

    the ingredients necessary to construct our app. Well create an Android Eclipse project as well as a

    StackMob app. The next part of this tutorial will cover adding the necessary SDKs and configurations to

    our project.

    Well be using the Android Developer Tools v21 and our mininum SDK will be Android 4.0.

    If youre not already a StackMob customer, sign up for free.

  • 3www.stackmob.com

    Well be using many features provided by the StackMob Android SDK and the StackMob Marketplace. The

    modules in the Marketplace are services that can be quickly installed and incorporated into your app.

    Our app will utilize the following modules from the StackMob Marketplace:

    If you havent done so yet, visit the Android developer center to

    download and install the ADT bundle.

    SnapStack Android Bootcamp Part 1: Setup

    Using StackMob

    Download ADT

    Create a new Android project1. Open up ADT and select a directory for your workspace. The default is ~/Documents/workspace.

    2. Choose File > New > Android Application Project.

    Access Controls:

    The access controls module will give us greater control over schema permissions.

    API:

    Well use the API to perform CRUD operations on our data.

    GeoQueries:

    The geoqueries module will enable our app to be powered by GPS location data.

    S3:

    To integrate photo storage into our app, well use the S3 module.

  • 4www.stackmob.com

    3. Enter SnapStack for the

    name of the application, and

    SnapStackAndroid for the

    project name. Enter a unique

    identifier for your package

    name. Set your minimum

    Android SDK to 3.0. Set the

    project to compile with the

    latest Google APIs. Click next.

    4. Make sure Create Activity is

    selected. Click next twice.

  • 5www.stackmob.com

    6. Enter MainActivity for the

    Activity name and activity_

    main for the Layout name.

    Click finish.

    Instead of building our own backend server (which would take weeks of work), we can take advantage

    of the StackMob platform immediately. To do this, well create an app on StackMob, which will come

    complete with Development and Production Keys, API requests and some Marketplace modules

    preinstalled, all for free!

    1. Head over to StackMob and

    login to your Dashboard (if

    you dont have an account,

    create one first). Click Create

    New App, in the top right

    corner.

    Create a StackMob application

    5. Choose Blank Activity and

    click next.

  • 6www.stackmob.com

    2. Create an app called

    snapstack and choose

    Android as your platform.

    Click next.

    3. Follow the setup instructions

    for importing the StackMob

    Android SDK.

    Follow the tutorial to create and add S3 credentials to your StackMob app.

    Setting up S3

    Our app will have a straightforward data model, consisting of three schemas: User, Snap and Comment.

    The User schema is automatically created for you when you create an app on StackMob. It is assumed to

    be the schema for storing your user objects as well as for password management, etc.

    A snap is created by a user, and contains the image taken, a relationship to the User who created it, as

    well as the geo location of where it was created.

    Users can make comments on snaps. A comment has a relationship to the User who created it, the text of

    the comment, and the relationship to its parent Snap object.

    Creating the data model

    1. Navigate to the Schema Configuration tab in

    your Dashboard.

    2. Click Edit next to the User schema. Add the

    following attributes:

    A string attribute called email

    A binary attribute called photo

    Save the User schema.

    In the User schema, set the Forgot Password Email Field to email.

  • 7www.stackmob.com

    3. Click Schema Configuration from the menu. Click Create New Schema. Create a schema called snap

    and add the following attributes:

    A binary attribute called photo A geopoint attribute called location

    4. StackMob allows you to manage schema permissions using the Access Controls module. Edit the

    permissions for this schema:

    Set the create permission level to Allow to any logged in user

    Set the read permission level to Allow to any logged in user

    Set the edit permission level to Allow to sm_owner (object owner)

    Set the delete permission level to Allow to sm_owner (object owner)

    Save the Snap schema.

    5. Create another schema called comment and add the following attribute:

    A String attribute called text

    Edit the permissions to match those in the snap schema. Add a relationship called snap with the

    related object set to snap. Make it a one-to-one relationship. Add another one-to-one relationship

    to user, called creator.

    6. Go back and edit the snap schema. Add a relationship called comments and set the related object

    to comment. Make it a one-to-many relationship. Add a one-to-one relationship to user, called

    creator.

    Our app utilizes Google Maps, which requires Google Play Services to be installed.

    Visit the Google tutorial for Google Maps Android API v2. Follow the guide up until the last section, Add

    a Map.

    Setting up Google Maps

  • 8www.stackmob.com

    package com.stackmob.snapstack; import com.stackmob.sdk.api.StackMobFile;import com.stackmob.sdk.model.StackMobUser; public class User extends StackMobUser { private String email; private StackMobFile photo; public User(String username, String password, String email) { super(User.class, username, password); this.email = email; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public StackMobFile getPhoto() { return photo; } public void setPhoto(StackMobFile photo) { this.photo = photo; } }

    To create and save snaps well create a class named Snap, which will subclass StackMobModel. Add a new class called Snap.java, with the following code:

    Creating a Snap model

    Well add a class named User to our project that subclasses StackMobUser. StackMobUser is a specialized subclass of StackMobModel meant to represent a user of your app. Like StackMobModel, its meant to be subclassed with whatever data you want to store with your user.

    Create a file, User.java, and add the following code:

    Creating a User model

    package com.stackmob.snapstack; import com.stackmob.sdk.api.StackMobFile;import com.stackmob.sdk.api.StackMobGeoPoint;import com.stackmob.sdk.model.StackMobModel; public class Snap extends StackMobModel { private User creator; private StackMobGeoPoint location;

  • 9www.stackmob.com

    Our Comment class will also subclass StackMobModel. Add a new class called Comment.java, with the following code:

    Creating a comment model

    package com.stackmob.snapstack; import com.stackmob.sdk.model.StackMobModel; public class Comment extends StackMobModel { private User creator; private String text; private Snap snap; public Comment(User creator, String text, Snap snap) { super(Comment.class); this.creator = creator; this.text = text; this.snap = snap; } public User getCreator() { return creator;

    private StackMobFile photo; public Snap(User creator, StackMobGeoPoint location) { super(Snap.class); this.creator = creator; this.location = location; } public User getCreator() { return creator; } public void setCreator(User creator) { this.creator = creator; } public StackMobGeoPoint getLocation() { return location; } public void setLocation(StackMobGeoPoint location) { this.location = location; } public void setPhoto(StackMobFile photo) { this.photo = photo; } public StackMobFile getPhoto() { return photo; }}

  • 10www.stackmob.com

    SnapStack makes use of many string constants throughout the app. Edit res/values/strings.xml with the following strings:

    Adding strings

    SnapStack Settings Hello world! Sign In Sign Up For SnapStack Forgot Your Password? Enter your username Enter your password Enter your email address Join SnapStack Make profile picture Choose your profile picture Show Map Show List This is the profile picture This is an image Share Photo Comments Sign Out Delete Type your comment here Share Comment Comment Email a temporary password Enter your username, and we\ll email you a temporary password. Enter temporary password

    } public void setCreator(User creator) { this.creator = creator; } public String getText() { return text; } public void setText(String text) { this.text = text; } public Snap getSnap() { return snap; } public void setSnap(Snap snap) { this.snap = snap; }}

  • 11www.stackmob.com

    Download Android Pull to Refresh and add it to your project:

    1. Right-click the project and choose Import

    Adding 3rd party libraries

    2. Choose Existing Android Code Into Workspace as an import source.

    3. Select only the library and click Finish.Make sure you have

    Copy projects into Workspace

    checked.

  • 12www.stackmob.com

    5. Navigate to the Android menu and click Add:

    6. Select the library and click OK.

    Download Android Universal

    Image Loader and add it to your

    project. Copy the jar file into

    your libs folder.

    4. Right-click the project, and select Properties:

  • 13www.stackmob.com

    In our app well create an

    Application class. This is

    where well store our User

    object, for use throughout

    the app. Create a class called

    SnapStackApplication.java.

    SnapStack Application

    Make sure it subclasses the

    Application class.

    Adding the necessary assets

    1. Drag the entire drawable folder into the project, under the res folder. Make sure you have Copy files selected.

    2. Copy the contents of the drawable-hdpi folder into the corresponding directory in your project. Make sure you have Copy files selected.

    3. Copy the contents of the layout folder into the corresponding directory in your project. Make sure you have Copy files selected.

    4. Finally, copy the contents of the menu folder into the corresponding directory in your project. Make sure you have Copy files selected.

    Weve included the assets needed for this project, including drawables and XML layouts. Download and

    unzip the assets for this project.

  • 14www.stackmob.com

    package com.stackmob.snapstack; import android.app.Application;import android.content.Context; import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;import com.nostra13.universalimageloader.core.ImageLoader;import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;import com.nostra13.universalimageloader.core.assist.QueueProcessingType; public class SnapStackApplication extends Application { private User user; private Snap snap; @Override public void onCreate() { super.onCreate(); initImageLoader(getApplicationContext()); } public User getUser() { return user; } public void setUser(User user) { this.user = user; } public Snap getSnap() { return snap; } public void setSnap(Snap snap) { this.snap = snap; } public static void initImageLoader(Context context) { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder( context).threadPriority(Thread.NORM_PRIORITY - 2) .denyCacheImageMultipleSizesInMemory() .discCacheFileNameGenerator(new Md5FileNameGenerator()) .tasksProcessingOrder(QueueProcessingType.LIFO).enableLogging() .build(); // Initialize ImageLoader with configuration. ImageLoader.getInstance().init(config); } }

    This application class contains two instance variables, user and snap, as well as getters and setters for

    both of them. Well use these two extensively throughout the project. Well also be utilizing ImageLoader

    throughout the project, and we initialize it here.

  • 15www.stackmob.com

    Update your projects AndroidManifest.xml file to look like this:

    AndroidManifest

  • 16www.stackmob.com

    android:name=.ChoosePhotoActivity android:screenOrientation=portrait >

    The project now includes all the necessary permissions and activity references for our app.

    Be sure to build your project to double check that it is free of errors.

    Weve reached the end of Part 1 and have completed the majority of the grunt work. All the pieces are

    in place: StackMob, S3 integration, XML layouts and more. We can focus on simply the code from here

    on out.

    In Part 2, well focus on creating and uploading Snaps, as well as the profile and map view.

    Sanity check

    Congrats!

  • 17www.stackmob.com

    SnapStack Android Bootcamp Part 2

    In this part well build the sign up/sign in flow for the app, allowing users to create profiles on SnapStack.

    Well also implement forgot password functionality in our app.

    Create a class that extends Activity, named MasterActivity.java. Most of our app will run through

    MasterActivity. For now, we wont add any logic behind it:

    Lets build out the signup flow for SnapStack. Our signup process will be straightforward: well have

    users create accounts by providing a username, password and email. Well also require users to upload a

    profile picture.

    For photos, well make use of the standard Android Camera library. Users will have the option to take a

    photo or select from their gallery, and afterwards, crop the photo into a square.

    Add the following classes, which weve borrowed from this image cropping example on Github.

    CropOption.java:

    What well cover

    Creating the MasterActivity class

    The signup flow

    package com.stackmob.snapstack; import android.app.Activity;import android.os.Bundle; public class MasterActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_master); } }

  • 18www.stackmob.com

    package com.stackmob.snapstack; import android.content.Intent;import android.graphics.drawable.Drawable; public class CropOption { public CharSequence title; public Drawable icon; public Intent appIntent;}

    package com.stackmob.snapstack; import java.util.ArrayList; import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ArrayAdapter;import android.widget.ImageView;import android.widget.TextView; public class CropOptionAdapter extends ArrayAdapter { private ArrayList mOptions; private LayoutInflater mInflater; public CropOptionAdapter(Context context, ArrayList options) { super(context, R.layout.crop_selector, options); mOptions = options; mInflater = LayoutInflater.from(context); } @Override public View getView(int position, View convertView, ViewGroup group) { if (convertView == null) convertView = mInflater.inflate(R.layout.crop_selector, null); CropOption item = mOptions.get(position); if (item != null) { ((ImageView) convertView.findViewById(R.id.iv_icon)).setImageDrawable(item.icon); ((TextView) convertView.findViewById(R.id.tv_name)).setText(item.title); return convertView; } return null; }}

    CropOptionAdapter.java:

  • 19www.stackmob.com

    package com.stackmob.snapstack; import java.io.ByteArrayOutputStream;import java.io.File;import java.util.ArrayList;import java.util.List; import android.app.Activity;import android.app.AlertDialog;import android.app.ProgressDialog;import android.app.AlertDialog.Builder;import android.content.ActivityNotFoundException;import android.content.ComponentName;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.ResolveInfo;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.drawable.BitmapDrawable;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;import android.view.View;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.ImageView;import android.widget.Toast; import com.stackmob.sdk.api.StackMobFile;import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException; public class ChoosePhotoActivity extends Activity { private SnapStackApplication snapStackApplication; private Uri imageCaptureUri; private ImageView choose_photo_imageview; private Button choose_photo_button; private ProgressDialog progressDialog; private static final int PICK_FROM_CAMERA = 1; private static final int CROP_FROM_CAMERA = 2; private static final int PICK_FROM_FILE = 3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_choose_photo); snapStackApplication = (SnapStackApplication) getApplication(); final String[] items = new String[] { Take from camera, Select from gallery }; ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.select_dialog_item, items); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(Select Image); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { // pick from // camera if (item == 0) {

    Next, add an Activity named ChoosePhotoActivity, with the following code:

  • 20www.stackmob.com

    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); imageCaptureUri = Uri.fromFile(new File(Environment .getExternalStorageDirectory(), tmp_avatar_ + String.valueOf(System.currentTimeMillis()) + .jpg)); intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageCaptureUri); try { intent.putExtra(return-data, true); startActivityForResult(intent, PICK_FROM_CAMERA); } catch (ActivityNotFoundException e) { e.printStackTrace(); } } else { // pick from file Intent intent = new Intent(); intent.setType(image/*); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, Complete action using), PICK_FROM_FILE); } } }); final AlertDialog dialog = builder.create(); choose_photo_button = (Button) findViewById(R.id.choose_photo_button); choose_photo_button.setEnabled(false); choose_photo_imageview = (ImageView) findViewById(R.id.choose_photo_imageview); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_avatar); choose_photo_imageview.setImageBitmap(bitmap); choose_photo_imageview.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.show(); } }); choose_photo_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog = ProgressDialog.show( ChoosePhotoActivity.this, Uploading photo, Uploading your profile pic, true); Bitmap bitmap = ((BitmapDrawable) choose_photo_imageview.getDrawable()).getBitmap(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); byte[] image = stream.toByteArray(); User user = snapStackApplication.getUser(); user.setPhoto(new StackMobFile(image/jpeg, profile_picture.jpg, image)); user.save(new StackMobModelCallback() { @Override

  • 21www.stackmob.com

    public void success() { progressDialog.dismiss(); int callingActivity = getIntent().getIntExtra(calling_activity, 0); if (callingActivity == 666) { setResult(RESULT_OK, null); finish(); } else if (callingActivity == 333) { Intent intent = new Intent( ChoosePhotoActivity.this, MasterActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); } } @Override public void failure(StackMobException e) { progressDialog.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder(ChoosePhotoActivity.this); builder.setTitle(Uh oh...); builder.setCancelable(true); builder.setMessage(There was an error saving your photo.); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); } }); dialog.show(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) return; switch (requestCode) { case PICK_FROM_CAMERA: doCrop(); break; case PICK_FROM_FILE: imageCaptureUri = data.getData(); doCrop(); break; case CROP_FROM_CAMERA:

  • 22www.stackmob.com

    Bundle extras = data.getExtras(); if (extras != null) { Bitmap photo = extras.getParcelable(data); choose_photo_imageview.setImageBitmap(photo); choose_photo_button.setEnabled(true); } File f = new File(imageCaptureUri.getPath()); if (f.exists()) f.delete(); break; } } private void doCrop() { final ArrayList cropOptions = new ArrayList(); Intent intent = new Intent(com.android.camera.action.CROP); intent.setType(image/*); List list = getPackageManager().queryIntentActivities( intent, 0); int size = list.size(); if (size == 0) { Toast.makeText(this, Can not find image crop app, Toast.LENGTH_SHORT).show(); return; } else { intent.setData(imageCaptureUri); intent.putExtra(outputX, 200); intent.putExtra(outputY, 200); intent.putExtra(aspectX, 1); intent.putExtra(aspectY, 1); intent.putExtra(scale, true); intent.putExtra(return-data, true); if (size == 1) { Intent i = new Intent(intent); ResolveInfo res = list.get(0); i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name)); startActivityForResult(i, CROP_FROM_CAMERA); } else { for (ResolveInfo res : list) { final CropOption co = new CropOption(); co.title = getPackageManager().getApplicationLabel( res.activityInfo.applicationInfo); co.icon = getPackageManager().getApplicationIcon( res.activityInfo.applicationInfo); co.appIntent = new Intent(intent); co.appIntent .setComponent(new ComponentName( res.activityInfo.packageName, res.activityInfo.name));

  • 23www.stackmob.com

    cropOptions.add(co); } CropOptionAdapter adapter = new CropOptionAdapter( getApplicationContext(), cropOptions); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(Choose Crop App); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { startActivityForResult( cropOptions.get(item).appIntent, CROP_FROM_CAMERA); } }); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (imageCaptureUri != null) { getContentResolver().delete(imageCaptureUri, null, null); imageCaptureUri = null; } } }); AlertDialog alert = builder.create(); alert.show(); } } }}

    The doCrop method makes use of the CropOption and CropOptionAdapter classes. For more information, check out this blog post explaining the code.

    Once a photo is chosen, we save it to StackMob and present MasterActivity.

    Finally, add an Activity named SignUpActivity:

    package com.stackmob.snapstack; import android.app.Activity;import android.app.AlertDialog;import android.app.AlertDialog.Builder;import android.app.ProgressDialog;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.os.Handler;import android.view.View;import android.view.inputmethod.InputMethodManager;import android.widget.Button;import android.widget.EditText;

  • 24www.stackmob.com

    import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException; public class SignUpActivity extends Activity { private SnapStackApplication snapStackApplication; private EditText username_edittext; private EditText password_edittext; private EditText email_edittext; private Button join_button; private ProgressDialog progressDialog; private Handler handler = new Handler(); private User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_signup); snapStackApplication = (SnapStackApplication) getApplication(); // Find our views username_edittext = (EditText) findViewById(R.id.username_edittext); password_edittext = (EditText) findViewById(R.id.password_edittext); email_edittext = (EditText) findViewById(R.id.email_edittext); join_button = (Button) findViewById(R.id.join_button); join_button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(username_edittext.getWindowToken(), 0); imm.hideSoftInputFromWindow(password_edittext.getWindowToken(), 0); imm.hideSoftInputFromWindow(email_edittext.getWindowToken(), 0); progressDialog = ProgressDialog.show( SignUpActivity.this, Signing up, Signing up for SnapStack, true); String username = username_edittext.getText().toString().trim(); String password = password_edittext.getText().toString().trim(); String email = email_edittext.getText().toString().trim(); if (foundError(username, password, email)) { progressDialog.dismiss(); return; } User user = new User(username, password, email); snapStackApplication.setUser(user); user.save(new StackMobModelCallback() { @Override public void success() { handler.post(new UserLogin()); } @Override public void failure(StackMobException e) {

  • 25www.stackmob.com

    progressDialog.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder( SignUpActivity.this); builder.setTitle(Uh oh...); builder.setCancelable(true); builder.setMessage(There was an error signing up.); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); } }); } public boolean foundError(String username, String password, String email) { Builder builder = new AlertDialog.Builder(this); builder.setTitle(Oops); builder.setCancelable(true); if (username.equals()) { builder.setMessage(Dont forget to enter a username!); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (password.equals()) { builder.setMessage(Dont forget to enter a password!); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (email.equals()) { builder.setMessage(Dont forget to enter an email); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()) { builder.setMessage(Please enter a valid email address.); AlertDialog dialog = builder.create(); dialog.show(); return true; } return false; } private class UserLogin implements Runnable{ public UserLogin(){ }

  • 26www.stackmob.com

    public void run(){ user = snapStackApplication.getUser(); user.login(new StackMobModelCallback() { @Override public void success() { progressDialog.dismiss(); snapStackApplication.setUser(user); Intent intent = new Intent(SignUpActivity.this, ChoosePhotoActivity.class); intent.putExtra(calling_activity, 333); startActivity(intent); finish(); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder( SignUpActivity.this); builder.setTitle(Uh oh...); builder.setCancelable(true); builder.setMessage(There was an error logging in.); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); } }

    The signup view accepts a username/password/email combo and creates an account for the user. We do

    this by creating a new User object and calling save on it. When the save is successful, we use a Handler to call the UserLogin runnable, which in its run method signs the User in. The foundError method checks for any empty or incorrect fields and notifies the user. If the save is successful, we present the

    ChoosePhotoActivity.

    Its a good idea to have a way for a user to recover their account, in case they ever lose their password.

    StackMob provides this feature with all User schemas. Earlier, we specified what field to use for the

    forgot password email. With the Android SDK, we can call a method that will trigger the process to allow

    the user to reclaim their account.

    Implementing forgot password

  • 27www.stackmob.com

    package com.stackmob.snapstack; import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException; import android.app.Activity;import android.app.AlertDialog;import android.app.ProgressDialog;import android.app.AlertDialog.Builder;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.view.inputmethod.InputMethodManager;import android.widget.Button;import android.widget.EditText; public class ChangePasswordActivity extends Activity { private SnapStackApplication snapStackApplication; private EditText username_edittext; private EditText temporary_password_edittext; private EditText password_edittext; private Button signin_button; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_change_password); snapStackApplication = (SnapStackApplication) getApplication(); // Find our views username_edittext = (EditText) findViewById(R.id.username_edittext); password_edittext = (EditText) findViewById(R.id.password_edittext); temporary_password_edittext = (EditText) findViewById(R.id.temporary_password_edittext); signin_button = (Button) findViewById(R.id.signin_button); signin_button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(username_edittext.getWindowToken(), 0); imm.hideSoftInputFromWindow(temporary_password_edittext.getWindowToken(), 0); imm.hideSoftInputFromWindow(password_edittext.getWindowToken(), 0); progressDialog = ProgressDialog.show( ChangePasswordActivity.this, Signing in, Signing into SnapStack, true); String username = username_edittext.getText().toString().trim(); String temp = temporary_password_edittext.getText().toString().trim(); String password = password_edittext.getText().toString().trim(); if (foundError(username, password)) { progressDialog.dismiss(); return; } User user = new User(username, temp, null);

    First, create an Activity named ChangePasswordActivity, and add the following code:

  • 28www.stackmob.com

    snapStackApplication.setUser(user); user.loginResettingTemporaryPassword(password,new StackMobModelCallback() { @Override public void success() { progressDialog.dismiss(); Intent intent = new Intent( ChangePasswordActivity.this, MasterActivity.class); startActivity(intent); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder(ChangePasswordActivity.this); builder.setTitle(Uh oh...); builder.setCancelable(true); builder.setMessage(There was an error signing in.); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); } }); } public boolean foundError(String username, String password) { Builder builder = new AlertDialog.Builder(this); builder.setTitle(Oops); builder.setCancelable(true); if (username.equals()){ builder.setMessage(Dont forget to enter a username!); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (password.equals()){ builder.setMessage(Dont forget to enter a password!); AlertDialog dialog = builder.create(); dialog.show(); return true; } return false; } }

  • 29www.stackmob.com

    package com.stackmob.snapstack; import android.app.Activity;import android.app.AlertDialog;import android.app.ProgressDialog;import android.app.AlertDialog.Builder;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.EditText; import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException;import com.stackmob.sdk.model.StackMobUser; public class ForgotPasswordActivity extends Activity { private EditText username_edittext; private Button forgot_password_button; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_forgot_password); username_edittext = (EditText) findViewById(R.id.username_edittext); forgot_password_button = (Button) findViewById(R.id.forgot_password_button); forgot_password_button.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View arg0) { String username = username_edittext.getText().toString(); if (username.trim().length() != 0){ progressDialog = ProgressDialog.show( ForgotPasswordActivity.this, Saving, Sending email, true); StackMobUser.sentForgotPasswordEmail(username, new StackMobModelCallback() { @Override public void success() { progressDialog.dismiss(); Intent intent = new Intent(ForgotPasswordActivity.this, ChangePasswordActivity.class); startActivity(intent); } @Override public void failure(StackMobException e) { progressDialog.dismiss();

    Next, add the Activity ForgotPasswordActivity:

    The activity contains a special sign in flow which allows the user to enter the temporary password, along

    with a new password. The method loginResettingTemporaryPassword makes this happen seamlessly.

    On a successful call, the user is signed into the master activity.

  • 30www.stackmob.com

    This is where users enter their username and request a temporary password. The

    sentForgotPasswordEmail method causes a temporary password to be sent to the user via email. That password is valid for 24 hours.

    Create a new Activity named SignInActivity.java, with the following code:

    SignInActivity

    package com.stackmob.snapstack; import android.app.Activity;import android.app.AlertDialog;import android.app.AlertDialog.Builder;import android.app.ProgressDialog;import android.content.Context;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.view.inputmethod.InputMethodManager;import android.widget.Button;import android.widget.EditText;import android.widget.TextView; import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException; public class SignInActivity extends Activity { private SnapStackApplication snapStackApplication; private EditText username_edittext; private EditText password_edittext; private Button signin_button; private TextView forgot_password; private ProgressDialog progressDialog; private User user;

    runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder( ForgotPasswordActivity.this); builder.setTitle(Uh oh...); builder.setCancelable(true); builder.setMessage(Unable to recover password.); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); } } }); }

  • 31www.stackmob.com

    @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_signin); snapStackApplication = (SnapStackApplication) getApplication(); // Find our views username_edittext = (EditText) findViewById(R.id.username_edittext); password_edittext = (EditText) findViewById(R.id.password_edittext); signin_button = (Button) findViewById(R.id.signin_button); forgot_password = (TextView) findViewById(R.id.forgot_password); forgot_password.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent( SignInActivity.this, ForgotPasswordActivity.class); startActivity(intent); } }); signin_button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(username_edittext.getWindowToken(), 0); imm.hideSoftInputFromWindow(password_edittext.getWindowToken(), 0); progressDialog = ProgressDialog.show( SignInActivity.this, Signing in, Signing into SnapStack, true); String username = username_edittext.getText().toString().trim(); String password = password_edittext.getText().toString().trim(); if (foundError(username, password)) { progressDialog.dismiss(); return; } user = new User(username, password, null); user.login(new StackMobModelCallback() { @Override public void success() { progressDialog.dismiss(); snapStackApplication.setUser(user); Intent intent = new Intent( SignInActivity.this, MasterActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); finish(); }

  • 32www.stackmob.com

    @Override public void failure(StackMobException e) { progressDialog.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder(SignInActivity.this); builder.setTitle(Uh oh...); builder.setCancelable(true); builder.setMessage(There was an error signing in.); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); } }); } public boolean foundError(String username, String password) { Builder builder = new AlertDialog.Builder(this); builder.setTitle(Oops); builder.setCancelable(true); if (username.equals()){ builder.setMessage(Dont forget to enter a username!); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (password.equals()){ builder.setMessage(Dont forget to enter a password!); AlertDialog dialog = builder.create(); dialog.show(); return true; } return false; } }

    The SignInActivity accepts a username and a password; the login attempts to sign in the user. Our helper method, foundError, notifies the user if theyre missing their username or password. We present the option to recover password if necessary, linking to ForgotPasswordActivity. If the sign in is

    successful, we present MasterActivity; if not, we present an error dialog.

  • 33www.stackmob.com

    package com.stackmob.snapstack; import android.app.AlertDialog;import android.app.Service;import android.content.Context;import android.content.DialogInterface;import android.content.Intent;import android.location.Location;import android.location.LocationListener;import android.location.LocationManager;import android.os.Bundle;import android.os.IBinder;import android.provider.Settings;import android.util.Log; public class GPSTracker extends Service implements LocationListener { private final Context mContext; // flag for GPS status boolean isGPSEnabled = false; // flag for network status boolean isNetworkEnabled = false; // flag for GPS status boolean canGetLocation = false; Location location; // location double latitude; // latitude double longitude; // longitude // The minimum distance to change Updates in meters private static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters // The minimum time between updates in milliseconds private static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute // Declaring a Location Manager protected LocationManager locationManager; public GPSTracker(Context context) { this.mContext = context; getLocation(); } public Location getLocation() { try { locationManager = (LocationManager) mContext .getSystemService(LOCATION_SERVICE); // getting GPS status isGPSEnabled = locationManager .isProviderEnabled(LocationManager.GPS_PROVIDER); // getting network status isNetworkEnabled = locationManager .isProviderEnabled(LocationManager.NETWORK_PROVIDER);

    Our app will leverage the Users location to find Snaps nearby, and to add geopoints to the Snaps they

    upload. Add a class named GPSTracker.java:

    Adding GPSTracker

  • 34www.stackmob.com

    if (!isGPSEnabled && !isNetworkEnabled) { // no network provider is enabled } else { this.canGetLocation = true; // First get location from Network Provider if (isNetworkEnabled) { locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this); Log.d(Network, Network); if (locationManager != null) { location = locationManager .getLastKnownLocation(LocationManager.NETWORK_PROVIDER); if (location != null) { latitude = location.getLatitude(); longitude = location.getLongitude(); } } } // if GPS Enabled get lat/long using GPS Services if (isGPSEnabled) { if (location == null) { locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, MIN_TIME_BW_UPDATES, MIN_DISTANCE_CHANGE_FOR_UPDATES, this); Log.d(GPS Enabled, GPS Enabled); if (locationManager != null) { location = locationManager .getLastKnownLocation(LocationManager.GPS_PROVIDER); if (location != null) { latitude = location.getLatitude(); longitude = location.getLongitude(); } } } } } } catch (Exception e) { e.printStackTrace(); } return location; } /** * Stop using GPS listener * Calling this function will stop using GPS in your app * */ public void stopUsingGPS(){ if(locationManager != null){ locationManager.removeUpdates(GPSTracker.this); } } /** * Function to get latitude * */ public double getLatitude(){ if(location != null){ latitude = location.getLatitude(); } // return latitude

  • 35www.stackmob.com

    return latitude; } /** * Function to get longitude * */ public double getLongitude(){ if(location != null){ longitude = location.getLongitude(); } // return longitude return longitude; } /** * Function to check GPS/wifi enabled * @return boolean * */ public boolean canGetLocation() { return this.canGetLocation; } /** * Function to show settings alert dialog * On pressing Settings button will lauch Settings Options * */ public void showSettingsAlert(){ AlertDialog.Builder alertDialog = new AlertDialog.Builder(mContext); // Setting Dialog Title alertDialog.setTitle(GPS is settings); // Setting Dialog Message alertDialog.setMessage(GPS is not enabled. Do you want to go to settings menu?); // On pressing Settings button alertDialog.setPositiveButton(Settings, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog,int which) { Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS); mContext.startActivity(intent); } }); // on pressing cancel button alertDialog.setNegativeButton(Cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.cancel(); } }); // Showing Alert Message alertDialog.show(); } @Override public void onLocationChanged(Location location) { } @Override public void onProviderDisabled(String provider) { } @Override public void onProviderEnabled(String provider) { }

  • 36www.stackmob.com

    @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public IBinder onBind(Intent arg0) { return null; } }

    Well utilize this Service to handle all of our location needs. For more information, check out this tutorial.

    Lets add the logic for our main view. Edit MainActivity to look like this:

    Editing MainActivity

    package com.stackmob.snapstack; import java.util.List; import android.app.Activity;import android.app.ProgressDialog;import android.content.Intent;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.Toast; import com.stackmob.android.sdk.common.StackMobAndroid;import com.stackmob.sdk.api.StackMob;import com.stackmob.sdk.callback.StackMobQueryCallback;import com.stackmob.sdk.exception.StackMobException; public class MainActivity extends Activity { private Button sign_up; private Button sign_in; private SnapStackApplication snapStackApplication; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); StackMobAndroid.init(getApplicationContext(), 1, YOUR_PUBLIC_KEY); StackMob.getStackMob().getSession().getLogger().setLogging(true); snapStackApplication = (SnapStackApplication) getApplication(); GPSTracker gps = new GPSTracker(this); if(!gps.canGetLocation()){ gps.showSettingsAlert(); } // Find our buttons sign_up = (Button) findViewById(R.id.signup_button); sign_in = (Button) findViewById(R.id.signin_button);

  • 37www.stackmob.com

    // Set an OnClickListener for the sign_up button sign_up.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent( MainActivity.this, SignUpActivity.class); startActivity(intent); } }); // Set an OnClickListener for the sign_in button sign_in.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { Intent intent = new Intent( MainActivity.this, SignInActivity.class); startActivity(intent); } }); } @Override protected void onResume() { super.onResume(); if(StackMob.getStackMob().isLoggedIn()) { final ProgressDialog progressDialog = ProgressDialog.show( MainActivity.this, Signing in, Signing back in, true); User.getLoggedInUser(User.class, new StackMobQueryCallback() { @Override public void success(List list) { progressDialog.dismiss(); User user = list.get(0); snapStackApplication.setUser(user); Intent intent = new Intent( MainActivity.this, MasterActivity.class); startActivity(intent); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); Toast.makeText(MainActivity.this, Couldnt sign back in, Toast.LENGTH_LONG).show(); } }); } } }

  • 38www.stackmob.com

    In MainActivity, we initialize the StackMob SDK. Copy your public key from the Dashboard and use it in

    the init method.

    The MainActivity simply presents two buttons that link to Sign Up and Sign In, respectively. At this point

    weve fully built out our sign up/sign in flows.

    Build and run the app to check for errors. Youll be greeted with MainActivity. Create an account and sign in.

    Youve finished Part 2. In this part, we laid out the skeleton for our app; we setup the sign up and sign in

    flows and added forgot password functionality to our app.

    In Part 3, well focus on the foundation of our app, MasterActivity.

    Sanity Check

    Congrats!

  • 39www.stackmob.com

    SnapStack Android Bootcamp Part 3

    In this chapter well add MasterActivity, the core of our app. It consists of a pull-to-refresh list view

    layered over a map view, and will serve as the main view in the app. Well also add a Profile view. Finally,

    well build the feature to allow users to take and upload Snaps.

    Add an Activity called DetailViewActivity. This activity will serve as host to an individual Snap object.

    Well add more code to it in the next tutorial; for now, keep it as an empty view:

    What well cover

    Adding DetailViewActivity

    package com.stackmob.snapstack; import android.app.Activity; public class DetailViewActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); } }

    Well display our Snaps in list views. To help with that, well create a reusable ListAdapter as a helper

    class. Create a file named SnapAdapter, and add the following code:

    SnapAdapter

    package com.stackmob.snapstack; import java.util.List; import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ArrayAdapter;

  • 40www.stackmob.com

    import android.widget.ImageView;import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions;import com.nostra13.universalimageloader.core.ImageLoader; public class SnapAdapter extends ArrayAdapter { private Context context; private List objects; private DisplayImageOptions options; protected ImageLoader imageLoader = ImageLoader.getInstance(); public SnapAdapter(Context context, List objects) { super(context, R.layout.listview_snap_item, objects); this.objects = objects; this.context = context; options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.placeholder) .showImageForEmptyUri(R.drawable.placeholder) .showImageOnFail(R.drawable.placeholder).cacheInMemory() .cacheOnDisc().bitmapConfig(Bitmap.Config.RGB_565).build(); } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.listview_snap_item, null); } if (objects != null) { Snap snap = objects.get(position); ImageView snap_item_profile_image = (ImageView) view .findViewById(R.id.snap_item_profile_image); if (snap.getCreator().getPhoto() != null) { imageLoader.displayImage(snap.getCreator().getPhoto().getS3Url(), snap_item_profile_image, options); } else { Bitmap bitmap = BitmapFactory.decodeResource( context.getResources(), R.drawable.default_avatar); snap_item_profile_image.setImageBitmap(bitmap); } TextView user_name = (TextView) view .findViewById(R.id.snap_item_username); user_name.setText(snap.getCreator().getUsername()); ImageView snap_item_image = (ImageView) view .findViewById(R.id.snap_item_image); imageLoader.displayImage(snap.getPhoto() .getS3Url(), snap_item_image, options); } return view; } }

  • 41www.stackmob.com

    package com.stackmob.snapstack; import java.util.ArrayList;import java.util.List; import android.app.Activity;import android.content.Intent;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Bundle;import android.os.Handler;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.animation.AlphaAnimation;import android.view.animation.Animation;import android.view.animation.DecelerateInterpolator;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;import android.widget.Toast; import com.handmark.pulltorefresh.library.PullToRefreshBase;import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;import com.handmark.pulltorefresh.library.PullToRefreshListView;import com.nostra13.universalimageloader.core.DisplayImageOptions;import com.nostra13.universalimageloader.core.ImageLoader;import com.stackmob.sdk.api.StackMobOptions;import com.stackmob.sdk.api.StackMobQuery;import com.stackmob.sdk.callback.StackMobNoopCallback;import com.stackmob.sdk.callback.StackMobQueryCallback;import com.stackmob.sdk.exception.StackMobException; public class ProfileActivity extends Activity { SnapStackApplication snapStackApplication; private PullToRefreshListView pull_refresh_list; private List snaps = new ArrayList(); private Handler handler = new Handler(); private SnapAdapter adapter; private User user; private TextView profile_username; private ImageView profile_photo_imageview; private DisplayImageOptions options; protected ImageLoader imageLoader = ImageLoader.getInstance(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_profile); options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.default_avatar) .showImageForEmptyUri(R.drawable.default_avatar) .showImageOnFail(R.drawable.default_avatar) .cacheInMemory() .cacheOnDisc()

    Create an Activity named ProfileActivity.java, with the following code:

    Adding a Profile

  • 42www.stackmob.com

    .bitmapConfig(Bitmap.Config.RGB_565) .build(); snapStackApplication = (SnapStackApplication) getApplication(); user = snapStackApplication.getUser(); profile_photo_imageview = (ImageView) findViewById(R.id.profile_photo_imageview); if (user.getPhoto() != null) { imageLoader.displayImage(user.getPhoto().getS3Url(), profile_photo_imageview, options); } else { Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_avatar); profile_photo_imageview.setImageBitmap(bitmap); } profile_photo_imageview.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(ProfileActivity.this, ChoosePhotoActivity.class); intent.putExtra(calling_activity, 666); startActivityForResult(intent, 0); } }); profile_username = (TextView) findViewById(R.id.profile_username); profile_username.setText(user.getUsername()); pull_refresh_list = (PullToRefreshListView) findViewById(R.id.pull_refresh_list); pull_refresh_list.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(PullToRefreshBase refreshView) { loadObjects(); } }); pull_refresh_list.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView parent, View view, int position, long id){ Snap snap = snaps.get(position - 1); Intent intent = new Intent( ProfileActivity.this, DetailViewActivity.class); snapStackApplication.setSnap(snap); startActivity(intent); } }); loadObjects(); } private class ListUpdater implements Runnable{ public ListUpdater(){ }

  • 43www.stackmob.com

    public void run(){ if (snaps.size() == 0) { Toast.makeText(ProfileActivity.this, No Snaps found, Toast.LENGTH_LONG).show(); } adapter = new SnapAdapter(ProfileActivity.this, snaps); pull_refresh_list.onRefreshComplete(); pull_refresh_list.setAdapter(adapter); Animation fadeIn = new AlphaAnimation(0, 1); fadeIn.setInterpolator(new DecelerateInterpolator()); //add this fadeIn.setDuration(1000); pull_refresh_list.setAnimation(fadeIn); } } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.signout_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.signOut: SnapStackApplication snapStackApplication = (SnapStackApplication) this.getApplication(); snapStackApplication.getUser().logout(new StackMobNoopCallback()); Intent myIntent = new Intent(this, MainActivity.class); myIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(myIntent); return true; default: return super.onOptionsItemSelected(item); } } private void loadObjects() { StackMobQuery query = new StackMobQuery(); query.fieldIsOrderedBy(createddate, StackMobQuery.Ordering.DESCENDING); query.fieldIsEqualTo(creator, user.getUsername()); Snap.query(Snap.class, query, StackMobOptions.depthOf(1), new StackMobQueryCallback() { @Override public void success(List result) { snaps = result; handler.post(new ListUpdater()); } @Override public void failure(StackMobException e) { handler.post(new ListUpdater());

  • 44www.stackmob.com

    } }); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { imageLoader.clearDiscCache(); imageLoader.clearMemoryCache(); user = snapStackApplication.getUser(); imageLoader.displayImage(user.getPhoto().getS3Url(), profile_photo_imageview, options); } }}

    One of the central features of the app is the share photo feature, where users take a photo and pair it

    with their location to create a Snap. Well walkthrough building that feature. Create an Activity called

    SharePhotoActivity, with the following code:

    The profile displays a users picture and username, as well as a list view of their Snaps. In

    onCreateOptionsMenu, weve added a sign out menu option to fully complete the flow for users. When onOptionsItemSelected is called, we sign the user out and bring them back to MainActivity. The loadObjects method queries for Snaps created by the user. We feed the objects to our SnapAdapter, which we pair with our list view.

    Adding SharePhotoActivity

    package com.stackmob.snapstack; import java.io.ByteArrayOutputStream;import java.io.File;import java.util.ArrayList;import java.util.List; import android.app.Activity;import android.app.AlertDialog;import android.app.ProgressDialog;import android.app.AlertDialog.Builder;import android.content.ActivityNotFoundException;import android.content.ComponentName;import android.content.Context;import android.content.DialogInterface;import android.content.Intent;import android.content.pm.ResolveInfo;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.drawable.BitmapDrawable;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.provider.MediaStore;

  • 45www.stackmob.com

    import android.view.View;import android.widget.ArrayAdapter;import android.widget.Button;import android.widget.ImageView;import android.widget.Toast; import com.stackmob.sdk.api.StackMobFile;import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException; public class SharePhotoActivity extends Activity { SnapStackApplication snapStackApplication; private Uri imageCaptureUri; private ImageView share_photo_imageview; private Button share_photo_button; private ProgressDialog progressDialog; private static final int PICK_FROM_CAMERA = 1; private static final int CROP_FROM_CAMERA = 2; private static final int PICK_FROM_FILE = 3; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share_photo); snapStackApplication = (SnapStackApplication) getApplication(); final String[] items = new String[] { Take from camera, Select from gallery }; ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.select_dialog_item, items); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(Select Image); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { // pick from // camera if (item == 0) { Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); imageCaptureUri = Uri.fromFile(new File(Environment .getExternalStorageDirectory(), tmp_avatar_ + String.valueOf(System.currentTimeMillis()) + .jpg)); intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageCaptureUri); try { intent.putExtra(return-data, true); startActivityForResult(intent, PICK_FROM_CAMERA); } catch (ActivityNotFoundException e) { e.printStackTrace(); } } else { // pick from file Intent intent = new Intent(); intent.setType(image/*); intent.setAction(Intent.ACTION_GET_CONTENT); startActivityForResult(Intent.createChooser(intent, Complete action using), PICK_FROM_FILE); }

  • 46www.stackmob.com

    } }); final AlertDialog dialog = builder.create(); dialog.show(); share_photo_button = (Button) findViewById(R.id.share_photo_button); share_photo_button.setEnabled(false); share_photo_imageview = (ImageView) findViewById(R.id.share_photo_imageview); Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.placeholder); share_photo_imageview.setImageBitmap(bitmap); share_photo_imageview.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dialog.show(); } }); share_photo_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { progressDialog = ProgressDialog.show( SharePhotoActivity.this, Saving..., Saving your snap, true); Bitmap bitmap = ((BitmapDrawable) share_photo_imageview .getDrawable()).getBitmap(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream); byte[] image = stream.toByteArray(); Snap snap = snapStackApplication.getSnap(); snap.setPhoto(new StackMobFile(image/jpeg, profile_picture.jpg, image)); snap.save(new StackMobModelCallback() { @Override public void success() { progressDialog.dismiss(); threadAgnosticDialog(SharePhotoActivity.this, Your photo was shared to SnapStack!); setResult(RESULT_OK, null); finish(); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); threadAgnosticDialog(SharePhotoActivity.this, There was an error saving your photo.); } }); } }); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) return; switch (requestCode) {

  • 47www.stackmob.com

    case PICK_FROM_CAMERA: doCrop(); break; case PICK_FROM_FILE: imageCaptureUri = data.getData(); doCrop(); break; case CROP_FROM_CAMERA: Bundle extras = data.getExtras(); if (extras != null) { Bitmap photo = extras.getParcelable(data); share_photo_imageview.setImageBitmap(photo); share_photo_button.setEnabled(true); } File f = new File(imageCaptureUri.getPath()); if (f.exists()) f.delete(); break; } } private void doCrop() { final ArrayList cropOptions = new ArrayList(); Intent intent = new Intent(com.android.camera.action.CROP); intent.setType(image/*); List list = getPackageManager().queryIntentActivities( intent, 0); int size = list.size(); if (size == 0) { Toast.makeText(this, Can not find image crop app, Toast.LENGTH_SHORT).show(); return; } else { intent.setData(imageCaptureUri); intent.putExtra(outputX, 200); intent.putExtra(outputY, 200); intent.putExtra(aspectX, 1); intent.putExtra(aspectY, 1); intent.putExtra(scale, true); intent.putExtra(return-data, true); if (size == 1) { Intent i = new Intent(intent); ResolveInfo res = list.get(0); i.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name)); startActivityForResult(i, CROP_FROM_CAMERA); } else {

  • 48www.stackmob.com

    for (ResolveInfo res : list) { final CropOption co = new CropOption(); co.title = getPackageManager().getApplicationLabel( res.activityInfo.applicationInfo); co.icon = getPackageManager().getApplicationIcon( res.activityInfo.applicationInfo); co.appIntent = new Intent(intent); co.appIntent .setComponent(new ComponentName( res.activityInfo.packageName, res.activityInfo.name)); cropOptions.add(co); } CropOptionAdapter adapter = new CropOptionAdapter( getApplicationContext(), cropOptions); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(Choose Crop App); builder.setAdapter(adapter, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int item) { startActivityForResult( cropOptions.get(item).appIntent, CROP_FROM_CAMERA); } }); builder.setOnCancelListener(new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { if (imageCaptureUri != null) { getContentResolver().delete(imageCaptureUri, null, null); imageCaptureUri = null; } } }); AlertDialog alert = builder.create(); alert.show(); } } } private void threadAgnosticDialog(final Context ctx, final String txt) { runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder(ctx); builder.setTitle(Share Photo); builder.setCancelable(true); builder.setMessage(txt); AlertDialog dialog = builder.create(); dialog.show(); } }); } }

  • 49www.stackmob.com

    In the last part of the tutorial, we left MasterActivity blank. Add the following code to MasterActivity:

    This activity works much like the ChoosePhotoActivity; we call the same Camera intent and use the

    same doCrop method to crop our images. Once we have a photo, we attach it to the Snap object stored in SnapStackApplication, and call save. If the save is successful, we call finish on SharePhotoActivity.

    The MasterActivity

    package com.stackmob.snapstack; import java.util.ArrayList;import java.util.List; import android.app.Activity;import android.app.AlertDialog;import android.app.AlertDialog.Builder;import android.app.Dialog;import android.content.Context;import android.content.DialogInterface;import android.content.Intent;import android.graphics.Bitmap;import android.location.Location;import android.os.Bundle;import android.os.Handler;import android.view.LayoutInflater;import android.view.View;import android.view.animation.AccelerateInterpolator;import android.view.animation.AlphaAnimation;import android.view.animation.Animation;import android.view.animation.DecelerateInterpolator;import android.widget.AdapterView;import android.widget.AdapterView.OnItemClickListener;import android.widget.CompoundButton;import android.widget.ImageButton;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.ListView;import android.widget.Toast;import android.widget.ToggleButton; import com.google.android.gms.common.ConnectionResult;import com.google.android.gms.common.GooglePlayServicesUtil;import com.google.android.gms.maps.CameraUpdateFactory;import com.google.android.gms.maps.GoogleMap;import com.google.android.gms.maps.GoogleMap.OnMarkerClickListener;import com.google.android.gms.maps.MapFragment;import com.google.android.gms.maps.model.LatLng;import com.google.android.gms.maps.model.LatLngBounds;import com.google.android.gms.maps.model.Marker;import com.google.android.gms.maps.model.MarkerOptions;import com.handmark.pulltorefresh.library.PullToRefreshBase;import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener;import com.handmark.pulltorefresh.library.PullToRefreshListView;import com.nostra13.universalimageloader.core.DisplayImageOptions;import com.nostra13.universalimageloader.core.ImageLoader;import com.stackmob.sdk.api.StackMobGeoPoint;import com.stackmob.sdk.api.StackMobOptions;import com.stackmob.sdk.api.StackMobQuery;import com.stackmob.sdk.callback.StackMobQueryCallback;import com.stackmob.sdk.exception.StackMobException;

  • 50www.stackmob.com

    public class MasterActivity extends Activity { private SnapStackApplication snapStackApplication; private ImageButton profile_button; private ImageButton camera_button; private GoogleMap map; private LinearLayout transparent_cover; private PullToRefreshListView pull_refresh_list; private ToggleButton toggle_button; private List snaps = new ArrayList(); private Handler handler = new Handler(); private SnapAdapter adapter; private GPSTracker gps; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_master); gps = new GPSTracker(this); snapStackApplication = (SnapStackApplication) getApplication(); // Getting Google Play availability status int status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getBaseContext()); // Showing status if(status!=ConnectionResult.SUCCESS){ // Google Play Services are not available int requestCode = 10; Dialog dialog = GooglePlayServicesUtil.getErrorDialog(status, this, requestCode); dialog.show(); } profile_button = (ImageButton) findViewById(R.id.profile_button); profile_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent( MasterActivity.this, ProfileActivity.class); startActivity(intent); } }); map = ((MapFragment) getFragmentManager().findFragmentById(R.id.map)) .getMap(); map.setMyLocationEnabled(true); map.getUiSettings().setAllGesturesEnabled(false); camera_button = (ImageButton) findViewById(R.id.camera_button); camera_button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (gps.getLocation() == null) { Builder builder = new AlertDialog.Builder(MasterActivity.this); builder.setTitle(Oh snap!); builder.setCancelable(true); builder.setMessage(Couldnt get your location.);

  • 51www.stackmob.com

    AlertDialog dialog = builder.create(); dialog.show(); return; } Location location = gps.getLocation(); StackMobGeoPoint point = new StackMobGeoPoint(location.getLongitude(), location.getLatitude()); User user = snapStackApplication.getUser(); Snap snap = new Snap(user, point); snapStackApplication.setSnap(snap); Intent intent = new Intent( MasterActivity.this, SharePhotoActivity.class); startActivityForResult(intent, 0); } }); transparent_cover = (LinearLayout) findViewById(R.id.transparent_cover); pull_refresh_list = (PullToRefreshListView) findViewById(R.id.pull_refresh_list); adapter = new SnapAdapter(MasterActivity.this, snaps); pull_refresh_list.setAdapter(adapter); pull_refresh_list.setRefreshing(true); pull_refresh_list.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh(PullToRefreshBase refreshView) { loadObjects(); } }); pull_refresh_list.setOnItemClickListener(new OnItemClickListener() { public void onItemClick(AdapterView parent, View view, int position, long id){ Snap snap = snaps.get(position - 1); Intent intent = new Intent( MasterActivity.this, DetailViewActivity.class); snapStackApplication.setSnap(snap); startActivityForResult(intent, 0); } }); toggle_button = (ToggleButton) findViewById(R.id.toggle_button); toggle_button.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Save the state here if (isChecked) { Animation fadeOut = new AlphaAnimation(1, 0); fadeOut.setInterpolator(new AccelerateInterpolator()); //and this fadeOut.setDuration(500); transparent_cover.setAnimation(fadeOut); pull_refresh_list.setAnimation(fadeOut); transparent_cover.setVisibility(View.GONE);

  • 52www.stackmob.com

    pull_refresh_list.setVisibility(View.GONE); map.getUiSettings().setAllGesturesEnabled(true); } else { transparent_cover.setVisibility(View.VISIBLE); pull_refresh_list.setVisibility(View.VISIBLE); Animation fadeIn = new AlphaAnimation(0, 1); fadeIn.setInterpolator(new DecelerateInterpolator()); fadeIn.setDuration(500); transparent_cover.setAnimation(fadeIn); pull_refresh_list.setAnimation(fadeIn); map.getUiSettings().setAllGesturesEnabled(false); } } }); map.setOnMarkerClickListener(new OnMarkerClickListener() { @Override public boolean onMarkerClick(final Marker marker) { AlertDialog.Builder builder = new AlertDialog.Builder(MasterActivity.this); LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflater.inflate(R.layout.activity_photo, null); builder.setView(v); int i = Integer.parseInt(marker.getSnippet()); Snap snap = snaps.get(i); ImageView imageView = (ImageView) v.findViewById(R.id.photo_image); DisplayImageOptions options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.placeholder) .showImageForEmptyUri(R.drawable.placeholder) .showImageOnFail(R.drawable.placeholder) .cacheInMemory() .cacheOnDisc() .bitmapConfig(Bitmap.Config.RGB_565) .build(); ImageLoader imageLoader = ImageLoader.getInstance(); imageLoader.displayImage(snap.getPhoto().getS3Url(), imageView, options); imageView.setAdjustViewBounds(true); imageView.setMaxHeight(150); imageView.setMaxWidth(150); imageView.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { int i = Integer.parseInt(marker.getSnippet()); Snap snap = snaps.get(i); Intent intent = new Intent( MasterActivity.this, DetailViewActivity.class); snapStackApplication.setSnap(snap);

  • 53www.stackmob.com

    startActivity(intent); } }); builder.setPositiveButton(Show Details, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int id) { int i = Integer.parseInt(marker.getSnippet()); Snap snap = snaps.get(i); Intent intent = new Intent( MasterActivity.this, DetailViewActivity.class); snapStackApplication.setSnap(snap); startActivity(intent); } }); builder.setNegativeButton(Close, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); Dialog dialog = builder.create(); dialog.show(); return true; } }); loadObjects(); } private void setMarkers () { if (snaps == null || snaps.size() == 0) return; LatLngBounds.Builder builder = new LatLngBounds.Builder(); for (int i = 0; i < snaps.size(); i++) { Snap snap = snaps.get(i); LatLng point = new LatLng(snap.getLocation().getLatitude(), snap.getLocation().getLongitude()); builder.include(point); map.addMarker(new MarkerOptions().position(point) .snippet(+i)); } if (gps.canGetLocation) { LatLng location = new LatLng(gps.latitude, gps.longitude); builder.include(location); } LatLngBounds bounds = builder.build(); map.animateCamera(CameraUpdateFactory.newLatLngBounds(bounds, 30)); }

  • 54www.stackmob.com

    private class ListUpdater implements Runnable{ public ListUpdater(){ } public void run(){ if (snaps.size() == 0) { Toast.makeText(MasterActivity.this, Couldnt find any Snaps nearby, Toast.LENGTH_LONG).show(); } adapter = new SnapAdapter(MasterActivity.this, snaps); pull_refresh_list.onRefreshComplete(); pull_refresh_list.setAdapter(adapter); setMarkers(); Animation fadeIn = new AlphaAnimation(0, 1); fadeIn.setInterpolator(new DecelerateInterpolator()); //add this fadeIn.setDuration(1000); pull_refresh_list.setAnimation(fadeIn); } } private void loadObjects() { Location location = gps.getLocation(); if (location == null) { pull_refresh_list.onRefreshComplete(); Toast.makeText(this, Couldnt get your location, Toast.LENGTH_LONG).show(); return; } LatLng currentLocation = new LatLng(location.getLatitude(), location.getLongitude()); // Move the camera instantly to the current location with a zoom of 15. map.animateCamera(CameraUpdateFactory.newLatLngZoom(currentLocation, 15)); StackMobGeoPoint point = new StackMobGeoPoint(location.getLongitude(), location.getLatitude()); StackMobQuery query = new StackMobQuery(); query.fieldIsNear(location, point); query.fieldIsOrderedBy(createddate, StackMobQuery.Ordering.DESCENDING); query.isInRange(0, 50); Snap.query(Snap.class, query, StackMobOptions.depthOf(1), new StackMobQueryCallback() { @Override public void success(List result) { snaps = result; handler.post(new ListUpdater()); } @Override public void failure(StackMobException e) { handler.post(new ListUpdater()); } });

  • 55www.stackmob.com

    } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { loadObjects(); } }}

    MasterActivity handles a lot at once. We have a map fragment, using the Google Maps Android v2 API.

    Layered on top of it is a pull-to-refresh list view for Snap objects. Beneath the list view is a toggle button

    to switch between the map and the list view; each time, the list view is faded out or in with an animation.

    The loadObjects method grabs the users location from the map, and queries for Snaps nearby. If a query

    is successful, the list view is refreshed using the run method from our private ListUpdater. The list view uses SnapAdapter to build its list items.

    Also in the run method, we update the map fragment, using the setMarkers. This method plots the Snaps as annotations using their locations, and focuses the map camera to fit them all. The map uses an

    OnMarkerClickListener to build a custom dialog window for the annotations; when an annotation is selected, the image associated with the Snap is shown.

    Finally the MasterActivity has links to the ProfileActivity and SharePhotoActivity we just built.

    Youve finished Part 3. We added the ability to take and upload Snap. We also added a Profile with sign

    out. Finally, we added the biggest piece of our app, MasterActivity.

    In Part 4, well wrap up development on SnapStack.

    Congrats!

  • 56www.stackmob.com

    SnapStack Android Bootcamp Part 4

    In this chapter, well focus on the detail view and comment view to our app. Well also add the ability to

    delete Snaps. Finally, well finish with the ability to add comments to Snaps.

    Add an Activity named PhotoViewActivity:

    What well cover

    PhotoViewActivity

    package com.stackmob.snapstack; import android.app.Activity;import android.app.AlertDialog;import android.app.ProgressDialog;import android.app.AlertDialog.Builder;import android.graphics.Bitmap;import android.os.Bundle;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.widget.ImageView; import com.nostra13.universalimageloader.core.DisplayImageOptions;import com.nostra13.universalimageloader.core.ImageLoader;import com.stackmob.sdk.callback.StackMobModelCallback;import com.stackmob.sdk.exception.StackMobException; public class PhotoViewActivity extends Activity { private Snap snap; SnapStackApplication snapStackApplication; DisplayImageOptions options; protected ImageLoader imageLoader = ImageLoader.getInstance(); private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_photo); options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.placeholder) .showImageForEmptyUri(R.drawable.placeholder) .showImageOnFail(R.drawable.placeholder) .cacheInMemory() .cacheOnDisc() .bitmapConfig(Bitmap.Config.RGB_565) .build();

  • 57www.stackmob.com

    snapStackApplication = (SnapStackApplication) getApplication(); snap = snapStackApplication.getSnap(); ImageView imageView = (ImageView) find