SlideShare una empresa de Scribd logo
1 de 69
   1www.stackmob.com
Step by Step Guide
for 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. We’ll walk through the entire process of building SnapStack, from the initial idea and design to
submitting to the Google Play Store. Along the way, we’ll 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 we’ll 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. It’s 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, we’ll be adding all of
the ingredients necessary to construct our app. We’ll 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.
• We’ll be using the Android Developer Tools v21 and our mininum SDK will be Android 4.0.
• If you’re not already a StackMob customer, sign up for free.
   3www.stackmob.com
We’ll 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 haven’t 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 project
1. 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:
We’ll 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, we’ll 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, we’ll 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 don’t 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 we’ll create a class named “Snap,” which will subclass StackMobModel. Add a new
class called Snap.java, with the following code:
Creating a Snap model
We’ll 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, it’s
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
<?xml version=”1.0” encoding=”utf-8”?>
<resources>
<string name=”app_name”>SnapStack</string>
<string name=”action_settings”>Settings</string>
<string name=”hello_world”>Hello world!</string>
<string name=”signin”>Sign In</string>
<string name=”signup”>Sign Up For SnapStack</string>
<string name=”forgot_password”>Forgot Your Password?</string>
<string name=”username_hint”>Enter your username</string>
<string name=”password_hint”>Enter your password</string>
<string name=”email_hint”>Enter your email address</string>
<string name=”join”>Join SnapStack</string>
<string name=”choose_photo”>Make profile picture</string>
<string name=”contentDescriptionChoosePhoto”>Choose your profile picture</string>
<string name=”toggle_turn_on”>Show Map</string>
<string name=”toggle_turn_off”>Show List</string>
<string name=”contentDescriptionProfileImage”>This is the profile picture</string>
<string name=”contentDescriptionImageView”>This is an image</string>
<string name=”share_photo”>Share Photo</string>
<string name=”comments”>Comments</string>
<string name=”signout”>Sign Out</string>
<string name=”delete”>Delete</string>
<string name=”comment_hint”>Type your comment here</string>
<string name=”share_comment”>Share Comment</string>
<string name=”comment”>Comment</string>
<string name=”forgot_password_button”>Email a temporary password</string>
<string name=”forgot_password_textview”>Enter your username, and we’ll email you a temporary
password.</string>
<string name=”change_password_hint”>Enter temporary password</string>
</resources>
	}
	 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 we’ll create an
Application class. This is
where we’ll 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.
We’ve 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. We’ll use these two extensively throughout the project. We’ll also be utilizing ImageLoader
throughout the project, and we initialize it here.
   15www.stackmob.com
Update your project’s AndroidManifest.xml file to look like this:
AndroidManifest
<?xml version=”1.0” encoding=”utf-8”?>
<manifest xmlns:android=”http://schemas.android.com/apk/res/android”
package=”com.stackmob.snapstack”
android:versionCode=”1”
android:versionName=”1.0” >
<uses-feature
android:glEsVersion=”0x00020000”
android:required=”true” />
<permission
android:name=”com.stackmob.snapstack.permission.MAPS_RECEIVE”
android:protectionLevel=”signature” />
<uses-permission android:name=”com.stackmob.snapstack.permission.MAPS_RECEIVE” />
<uses-permission android:name=”android.permission.INTERNET” />
<uses-permission android:name=”android.permission.CAMERA” />
<uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />
<uses-permission android:name=”com.google.android.providers.gsf.permission.READ_GSERVICES” />
<uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” />
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” />
<uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” />
<uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” />
<uses-feature
android:name=”android.hardware.camera”
android:required=”true” />
<uses-sdk
android:minSdkVersion=”14”
android:targetSdkVersion=”17” />
<application
android:name=”.SnapStackApplication”
android:allowBackup=”true”
android:icon=”@drawable/app_icon”
android:label=”@string/app_name”
android:theme=”@style/AppTheme” >
<meta-data
android:name=”com.google.android.maps.v2.API_KEY”
android:value=”YOUR_API_KEY” />
<activity
android:name=”com.stackmob.snapstack.MainActivity”
android:screenOrientation=”portrait” >
<intent-filter>
<action android:name=”android.intent.action.MAIN” />
<category android:name=”android.intent.category.LAUNCHER” />
</intent-filter>
</activity>
<activity
android:name=”.SignUpActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.SignInActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
   16www.stackmob.com
android:name=”.ChoosePhotoActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.MasterActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.ProfileActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.SharePhotoActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.DetailViewActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.PhotoViewActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.CommentViewActivity”
android:screenOrientation=”portrait”>
</activity>
<activity
android:name=”.ShareCommentActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.ForgotPasswordActivity”
android:screenOrientation=”portrait” >
</activity>
<activity
android:name=”.ChangePasswordActivity”
android:screenOrientation=”portrait” >
</activity>
</application>
</manifest>
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.
We’ve 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, we’ll 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 we’ll build the sign up/sign in flow for the app, allowing users to create profiles on SnapStack.
We’ll 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 won’t add any logic behind it:
Let’s build out the signup flow for SnapStack. Our signup process will be straightforward: we’ll have
users create accounts by providing a username, password and email. We’ll also require users to upload a
profile picture.
For photos, we’ll 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 we’ve borrowed from this image cropping example on Github.
CropOption.java:
What we’ll 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<CropOption> {
private ArrayList<CropOption> mOptions;
	 private LayoutInflater mInflater;
	
	 public CropOptionAdapter(Context context, ArrayList<CropOption> 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<String> adapter = new ArrayAdapter<String>(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<CropOption> cropOptions = new ArrayList<CropOption>();
		 Intent intent = new Intent(“com.android.camera.action.CROP”);
		 intent.setType(“image/*”);
		 List<ResolveInfo> 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(“Don’t forget to enter a username!”);
			 AlertDialog dialog = builder.create();
			dialog.show();
			return true;
		}
		 else if (password.equals(“”)) {
			 builder.setMessage(“Don’t forget to enter a password!”);
			 AlertDialog dialog = builder.create();
			dialog.show();
			return true;
		}
		 else if (email.equals(“”)) {
			 builder.setMessage(“Don’t 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.
It’s 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(“Don’t forget to enter a username!”);
			 AlertDialog dialog = builder.create();
		 dialog.show();
		 return true;
		}
		
		 else if (password.equals(“”)){
			 builder.setMessage(“Don’t 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.os.Handler;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
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;
					 	
					 	 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_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) {
						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();
							}
   32www.stackmob.com
						});
					}
				});
			}
		});
	}
	 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(“Don’t forget to enter a username!”);
			 AlertDialog dialog = builder.create();
			dialog.show();
			return true;
		}
		 else if (password.equals(“”)) {
			 builder.setMessage(“Don’t forget to enter a password!”);
			 AlertDialog dialog = builder.create();
			dialog.show();
			return true;
		}
		 else if (email.equals(“”)) {
			 builder.setMessage(“Don’t 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(){
	
}
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);
   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
Our app will leverage the User’s location to find Snaps nearby, and to add geopoints to the Snaps they
upload. Add a class named GPSTracker.java:
Adding GPSTracker
The SignInActivity accepts a username and a password; the login attempts to sign in the user. Our
helper method, foundError, notifies the user if they’re 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.
					 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();
						}
					});
			 }
			});
	
}
   34www.stackmob.com
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);
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”);
   35www.stackmob.com
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
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);
   36www.stackmob.com
// 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) {
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
We’ll utilize this Service to handle all of our location needs. For more information, check out this tutorial.
Let’s 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;
   37www.stackmob.com
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);
// 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();
   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
we’ve fully built out our sign up/sign in flows.
Build and run the app to check for errors. You’ll be greeted with MainActivity. Create an account and sign in.
You’ve 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, we’ll focus on the foundation of our app, MasterActivity.
Sanity Check
Congrats!
if(StackMob.getStackMob().isLoggedIn()) {
	
	 final ProgressDialog progressDialog = ProgressDialog.show(
			 MainActivity.this, “Signing in”,
					 “Signing back in”, true);
	
User.getLoggedInUser(User.class, new StackMobQueryCallback<User>() {
@Override
public void success(List<User> 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, “Couldn’t sign back in”, Toast.LENGTH_LONG).
show();
}
});
}
}
}
   39www.stackmob.com
SnapStack Android Bootcamp
Part 3
In this chapter we’ll 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. We’ll also add a Profile view. Finally,
we’ll 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.
We’ll add more code to it in the next tutorial; for now, keep it as an empty view:
What we’ll 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);
}
	
}
We’ll display our Snaps in list views. To help with that, we’ll 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<Snap> {
	 private Context context;
	 private List<Snap> objects;
	 private DisplayImageOptions options;
	 protected ImageLoader imageLoader = ImageLoader.getInstance();
	 public SnapAdapter(Context context, List<Snap> 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<Snap> snaps = new ArrayList<Snap>();
	 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<ListView>() {
			@Override
			 public void onRefresh(PullToRefreshBase<ListView> 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<Snap>() {
			@Override
		 public void success(List<Snap> 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. We’ll walkthrough building that feature. Create an Activity called
SharePhotoActivity, with the following code:
The profile displays a user’s picture and username, as well as a list view of their Snaps. In
onCreateOptionsMenu, we’ve 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<String> adapter = new ArrayAdapter<String>(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<CropOption> cropOptions = new ArrayList<CropOption>();
		 Intent intent = new Intent(“com.android.camera.action.CROP”);
		 intent.setType(“image/*”);
		 List<ResolveInfo> 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<Snap> snaps = new ArrayList<Snap>();
	 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(“Couldn’t 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<ListView>() {
			@Override
			 public void onRefresh(PullToRefreshBase<ListView> 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, “Couldn’t 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, “Couldn’t 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<Snap>() {
			@Override
		 public void success(List<Snap> 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.
You’ve 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, we’ll wrap up development on SnapStack.
Congrats!
   56www.stackmob.com
SnapStack Android Bootcamp
Part 4
In this chapter, we’ll focus on the detail view and comment view to our app. We’ll also add the ability to
delete Snaps. Finally, we’ll finish with the ability to add comments to Snaps.
Add an Activity named PhotoViewActivity:
What we’ll 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) findViewById(R.id.photo_image);
	 imageLoader.displayImage(snap.getPhoto().getS3Url(), imageView, options);
	
}
	
	@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.delete_menu, menu);
String username = snap.getCreator().getUsername();
User user = snapStackApplication.getUser();
if (username.equals(user.getUsername())) {
	 return true;
}
return false;
}
	
	@Override
public boolean onOptionsItemSelected(MenuItem item) {
		
		 progressDialog = ProgressDialog.show(
			 PhotoViewActivity.this, “Deleting...”,
				 “Deleting your snap”, true);
		
		 snap.destroy(new StackMobModelCallback() {
		 @Override
		 public void success() {
		 // the call succeeded
		 	 progressDialog.dismiss();
		 	 setResult(RESULT_OK, null);
				 finish();
		 }
		
		 @Override
		 public void failure(StackMobException e) {
		 // the call failed
		 	 progressDialog.dismiss();
		 	 runOnUiThread(new Runnable() {
					@Override public void run() {
						 Builder builder = new AlertDialog.
Builder(PhotoViewActivity.this);
						 builder.setTitle(“Uh oh...”);
						builder.setCancelable(true);
						 builder.setMessage(“Couldn’t delete snap”);
						AlertDialog dialog = builder.create();
						dialog.show();
					}
				});
		 }
		});
return true;
}
}
   58www.stackmob.com
In SnapStack, users can add comments to Snaps. Add an Activity named ShareCommentActivity:
ShareCommentActivity
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.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;
public class ShareCommentActivity extends Activity {
	 private SnapStackApplication snapStackApplication;
	 private EditText comment_edittext;
	 private Button share_comment_button;
	 private ProgressDialog progressDialog;
	
	@Override
	 protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		 setContentView(R.layout.activity_share_comment);
		
		 snapStackApplication = (SnapStackApplication) getApplication();
		
		 comment_edittext = (EditText) findViewById(R.id.comment_edittext);
		 share_comment_button = (Button) findViewById(R.id.share_comment_button);
		
		 share_comment_button.setOnClickListener( new View.OnClickListener() {
			@Override
			 public void onClick(View arg0) {
				
				 String commentText = comment_edittext.getText().toString();
				
				 if (commentText.trim().length() != 0){
					
					progressDialog = ProgressDialog.show(
	 			 ShareCommentActivity.this, “Saving”,
							 “Saving comment”, true);
					
					User user = snapStackApplication.getUser();
					
					 final Snap snap = snapStackApplication.getSnap();
					
					 Comment comment = new Comment(user, commentText, snap);
PhotoViewActivity is very basic; it displays the photo from the Snap selected. The
onCreateOptionsMenu presents the option to delete the Snap, if it was created by the user. When
onOptionsItemSelected is called, we use the destroy method on the Snap object. If the delete is
successful, we call finish on the activity.
   59www.stackmob.com
					
					comment.save(new StackMobModelCallback() {
					 @Override
					 public void success() {
					 // the call succeeded
					 	progressDialog.dismiss();
					 	 setResult(RESULT_OK, null);
					 	 finish();
					 }
					
					 @Override
					 public void failure(StackMobException e) {
					 	progressDialog.dismiss();
					 // the call failed
					 	 threadAgnosticDialog(ShareCommentActivity.this, “There
was an error saving your comment.”);
					 }
					});
					
					
				}
			}
			
		});
		
	}
	
	 private void threadAgnosticDialog(final Context ctx, final String txt) {
		 runOnUiThread(new Runnable() {
			 @Override public void run() {
				 Builder builder = new AlertDialog.Builder(ctx);
				builder.setCancelable(true);
				builder.setMessage(txt);
				AlertDialog dialog = builder.create();
				dialog.show();
			}
		});
	}
}
This activity presents a simple EditText for users to type comments. Once the share button is clicked,
we create a Comment object, complete with the User who created it, the text of the comment and the
associated Snap. After we call save, if it’s successful, we finish the activity.
package com.stackmob.snapstack;
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;
Now that we’ve built the feature to add comments, we’ll make an Activity to display them. Add an
Activity named CommentViewActivity:
CommentViewActivity
   60www.stackmob.com
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
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.StackMobQueryCallback;
import com.stackmob.sdk.exception.StackMobException;
public class CommentViewActivity extends Activity {
	 List<Comment> comments;
	 ListView listView;
	 private SnapStackApplication snapStackApplication;
	 private Handler handler = new Handler();
	 CommentAdapter adapter;
	 DisplayImageOptions options;
	 protected ImageLoader imageLoader = ImageLoader.getInstance();
	 private ProgressDialog progressDialog;
	@Override
	 protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		 setContentView(R.layout.activity_comment);
		 options = new DisplayImageOptions.Builder()
				 .showStubImage(R.drawable.placeholder)
				 .showImageForEmptyUri(R.drawable.placeholder)
				 .showImageOnFail(R.drawable.placeholder).cacheInMemory()
				 .cacheOnDisc().bitmapConfig(Bitmap.Config.RGB_565).build();
		 listView = (ListView) findViewById(R.id.comment_listview);
		 comments = new ArrayList<Comment>();
		 snapStackApplication = (SnapStackApplication) getApplication();
		 adapter = new CommentAdapter(CommentViewActivity.this, comments);
		listView.setAdapter(adapter);
		loadComments();
	}
	 public void loadComments() {
		 progressDialog = ProgressDialog.show(CommentViewActivity.this,
				 “Loading...”, “Loading comments”, true);
		 Snap snap = snapStackApplication.getSnap();
		 StackMobQuery query = new StackMobQuery();
		 query.fieldIsEqualTo(“snap”, snap.getID());
		 Comment.query(Comment.class, query, StackMobOptions.depthOf(1),
				new StackMobQueryCallback<Comment>() {
					@Override
					public void success(List<Comment> result) {
   61www.stackmob.com
						progressDialog.dismiss();
						comments = result;
						handler.post(new ListUpdater());
					}
					@Override
					public void failure(StackMobException e) {
						progressDialog.dismiss();
						handler.post(new ListUpdater());
						 Builder builder = new AlertDialog.Builder(
								CommentViewActivity.this);
						 builder.setTitle(“Uh oh...”);
						builder.setCancelable(true);
						 builder.setMessage(“There was an error loading
comments.”);
						AlertDialog dialog = builder.create();
						dialog.show();
					}
				});
	}
	 private class ListUpdater implements Runnable {
		 public ListUpdater() {
		}
		 public void run() {
			 adapter = new CommentAdapter(CommentViewActivity.this, comments);
			listView.setAdapter(adapter);
		}
	}
	 private class CommentAdapter extends ArrayAdapter<Comment> {
		 private List<Comment> objects;
		 public CommentAdapter(Context context, List<Comment> objects) {
			 super(context, R.layout.listview_comment_item, objects);
			this.objects = objects;
		}
		@Override
		 public View getView(int position, View convertView, ViewGroup parent) {
			 View view = convertView;
			 if (view == null) {
				 LayoutInflater inflater = (LayoutInflater) CommentViewActivity.this
						 .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
				 view = inflater.inflate(R.layout.listview_comment_item, null);
			}
			 if (objects != null) {
				Comment comment = objects.get(position);
				 TextView user_name = (TextView) view
						 .findViewById(R.id.comment_username);
				 user_name.setText(comment.getCreator().getUsername());
				 ImageView comment_item_profile_image = (ImageView) view
						 .findViewById(R.id.comment_item_profile_image);
Build an Android Application eBook
Build an Android Application eBook
Build an Android Application eBook
Build an Android Application eBook
Build an Android Application eBook
Build an Android Application eBook
Build an Android Application eBook
Build an Android Application eBook

Más contenido relacionado

Último

Último (20)

Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...Workshop - Best of Both Worlds_ Combine  KG and Vector search for  enhanced R...
Workshop - Best of Both Worlds_ Combine KG and Vector search for enhanced R...
 
Boost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivityBoost PC performance: How more available memory can improve productivity
Boost PC performance: How more available memory can improve productivity
 
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
Strategies for Unlocking Knowledge Management in Microsoft 365 in the Copilot...
 
Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024Tata AIG General Insurance Company - Insurer Innovation Award 2024
Tata AIG General Insurance Company - Insurer Innovation Award 2024
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
Presentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreterPresentation on how to chat with PDF using ChatGPT code interpreter
Presentation on how to chat with PDF using ChatGPT code interpreter
 
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdfThe Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
The Role of Taxonomy and Ontology in Semantic Layers - Heather Hedden.pdf
 
The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024The 7 Things I Know About Cyber Security After 25 Years | April 2024
The 7 Things I Know About Cyber Security After 25 Years | April 2024
 
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time AutomationFrom Event to Action: Accelerate Your Decision Making with Real-Time Automation
From Event to Action: Accelerate Your Decision Making with Real-Time Automation
 
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
Raspberry Pi 5: Challenges and Solutions in Bringing up an OpenGL/Vulkan Driv...
 
Handwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed textsHandwritten Text Recognition for manuscripts and early printed texts
Handwritten Text Recognition for manuscripts and early printed texts
 
Data Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt RobisonData Cloud, More than a CDP by Matt Robison
Data Cloud, More than a CDP by Matt Robison
 
How to convert PDF to text with Nanonets
How to convert PDF to text with NanonetsHow to convert PDF to text with Nanonets
How to convert PDF to text with Nanonets
 
Exploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone ProcessorsExploring the Future Potential of AI-Enabled Smartphone Processors
Exploring the Future Potential of AI-Enabled Smartphone Processors
 
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men08448380779 Call Girls In Greater Kailash - I Women Seeking Men
08448380779 Call Girls In Greater Kailash - I Women Seeking Men
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 

Destacado

Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
Kurio // The Social Media Age(ncy)
 

Destacado (20)

Skeleton Culture Code
Skeleton Culture CodeSkeleton Culture Code
Skeleton Culture Code
 
PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024PEPSICO Presentation to CAGNY Conference Feb 2024
PEPSICO Presentation to CAGNY Conference Feb 2024
 
Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)Content Methodology: A Best Practices Report (Webinar)
Content Methodology: A Best Practices Report (Webinar)
 
How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024How to Prepare For a Successful Job Search for 2024
How to Prepare For a Successful Job Search for 2024
 
Social Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie InsightsSocial Media Marketing Trends 2024 // The Global Indie Insights
Social Media Marketing Trends 2024 // The Global Indie Insights
 
Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024Trends In Paid Search: Navigating The Digital Landscape In 2024
Trends In Paid Search: Navigating The Digital Landscape In 2024
 
5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary5 Public speaking tips from TED - Visualized summary
5 Public speaking tips from TED - Visualized summary
 
ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd ChatGPT and the Future of Work - Clark Boyd
ChatGPT and the Future of Work - Clark Boyd
 
Getting into the tech field. what next
Getting into the tech field. what next Getting into the tech field. what next
Getting into the tech field. what next
 
Google's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search IntentGoogle's Just Not That Into You: Understanding Core Updates & Search Intent
Google's Just Not That Into You: Understanding Core Updates & Search Intent
 
How to have difficult conversations
How to have difficult conversations How to have difficult conversations
How to have difficult conversations
 
Introduction to Data Science
Introduction to Data ScienceIntroduction to Data Science
Introduction to Data Science
 
Time Management & Productivity - Best Practices
Time Management & Productivity -  Best PracticesTime Management & Productivity -  Best Practices
Time Management & Productivity - Best Practices
 
The six step guide to practical project management
The six step guide to practical project managementThe six step guide to practical project management
The six step guide to practical project management
 
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
Beginners Guide to TikTok for Search - Rachel Pearson - We are Tilt __ Bright...
 
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
Unlocking the Power of ChatGPT and AI in Testing - A Real-World Look, present...
 
12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work12 Ways to Increase Your Influence at Work
12 Ways to Increase Your Influence at Work
 
ChatGPT webinar slides
ChatGPT webinar slidesChatGPT webinar slides
ChatGPT webinar slides
 
More than Just Lines on a Map: Best Practices for U.S Bike Routes
More than Just Lines on a Map: Best Practices for U.S Bike RoutesMore than Just Lines on a Map: Best Practices for U.S Bike Routes
More than Just Lines on a Map: Best Practices for U.S Bike Routes
 
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
Ride the Storm: Navigating Through Unstable Periods / Katerina Rudko (Belka G...
 

Build an Android Application eBook

  • 1.    1www.stackmob.com Step by Step Guide for Android Build a Photo Sharing App in a Day
  • 2.    2www.stackmob.com This series is focused on the creation of SnapStack, a location-based photo sharing app for Android phones. We’ll walk through the entire process of building SnapStack, from the initial idea and design to submitting to the Google Play Store. Along the way, we’ll 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 we’ll 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. It’s 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, we’ll be adding all of the ingredients necessary to construct our app. We’ll 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. • We’ll be using the Android Developer Tools v21 and our mininum SDK will be Android 4.0. • If you’re not already a StackMob customer, sign up for free.
  • 3.    3www.stackmob.com We’ll 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 haven’t 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 project 1. 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: We’ll 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, we’ll use the S3 module.
  • 4.    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.
  • 5.    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, we’ll 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 don’t 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.
  • 6.    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.
  • 7.    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
  • 8.    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 we’ll create a class named “Snap,” which will subclass StackMobModel. Add a new class called Snap.java, with the following code: Creating a Snap model We’ll 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, it’s 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;
  • 9.    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; } }
  • 10.    10www.stackmob.com SnapStack makes use of many string constants throughout the app. Edit res/values/strings.xml with the following strings: Adding strings <?xml version=”1.0” encoding=”utf-8”?> <resources> <string name=”app_name”>SnapStack</string> <string name=”action_settings”>Settings</string> <string name=”hello_world”>Hello world!</string> <string name=”signin”>Sign In</string> <string name=”signup”>Sign Up For SnapStack</string> <string name=”forgot_password”>Forgot Your Password?</string> <string name=”username_hint”>Enter your username</string> <string name=”password_hint”>Enter your password</string> <string name=”email_hint”>Enter your email address</string> <string name=”join”>Join SnapStack</string> <string name=”choose_photo”>Make profile picture</string> <string name=”contentDescriptionChoosePhoto”>Choose your profile picture</string> <string name=”toggle_turn_on”>Show Map</string> <string name=”toggle_turn_off”>Show List</string> <string name=”contentDescriptionProfileImage”>This is the profile picture</string> <string name=”contentDescriptionImageView”>This is an image</string> <string name=”share_photo”>Share Photo</string> <string name=”comments”>Comments</string> <string name=”signout”>Sign Out</string> <string name=”delete”>Delete</string> <string name=”comment_hint”>Type your comment here</string> <string name=”share_comment”>Share Comment</string> <string name=”comment”>Comment</string> <string name=”forgot_password_button”>Email a temporary password</string> <string name=”forgot_password_textview”>Enter your username, and we’ll email you a temporary password.</string> <string name=”change_password_hint”>Enter temporary password</string> </resources> } 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; } }
  • 11.    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.
  • 12.    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”:
  • 13.    13www.stackmob.com In our app we’ll create an Application class. This is where we’ll 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. We’ve included the assets needed for this project, including drawables and XML layouts. Download and unzip the assets for this project.
  • 14.    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. We’ll use these two extensively throughout the project. We’ll also be utilizing ImageLoader throughout the project, and we initialize it here.
  • 15.    15www.stackmob.com Update your project’s AndroidManifest.xml file to look like this: AndroidManifest <?xml version=”1.0” encoding=”utf-8”?> <manifest xmlns:android=”http://schemas.android.com/apk/res/android” package=”com.stackmob.snapstack” android:versionCode=”1” android:versionName=”1.0” > <uses-feature android:glEsVersion=”0x00020000” android:required=”true” /> <permission android:name=”com.stackmob.snapstack.permission.MAPS_RECEIVE” android:protectionLevel=”signature” /> <uses-permission android:name=”com.stackmob.snapstack.permission.MAPS_RECEIVE” /> <uses-permission android:name=”android.permission.INTERNET” /> <uses-permission android:name=”android.permission.CAMERA” /> <uses-permission android:name=”android.permission.WRITE_EXTERNAL_STORAGE” /> <uses-permission android:name=”com.google.android.providers.gsf.permission.READ_GSERVICES” /> <uses-permission android:name=”android.permission.ACCESS_COARSE_LOCATION” /> <uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION” /> <uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” /> <uses-permission android:name=”android.permission.ACCESS_WIFI_STATE” /> <uses-feature android:name=”android.hardware.camera” android:required=”true” /> <uses-sdk android:minSdkVersion=”14” android:targetSdkVersion=”17” /> <application android:name=”.SnapStackApplication” android:allowBackup=”true” android:icon=”@drawable/app_icon” android:label=”@string/app_name” android:theme=”@style/AppTheme” > <meta-data android:name=”com.google.android.maps.v2.API_KEY” android:value=”YOUR_API_KEY” /> <activity android:name=”com.stackmob.snapstack.MainActivity” android:screenOrientation=”portrait” > <intent-filter> <action android:name=”android.intent.action.MAIN” /> <category android:name=”android.intent.category.LAUNCHER” /> </intent-filter> </activity> <activity android:name=”.SignUpActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.SignInActivity” android:screenOrientation=”portrait” > </activity> <activity
  • 16.    16www.stackmob.com android:name=”.ChoosePhotoActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.MasterActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.ProfileActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.SharePhotoActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.DetailViewActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.PhotoViewActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.CommentViewActivity” android:screenOrientation=”portrait”> </activity> <activity android:name=”.ShareCommentActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.ForgotPasswordActivity” android:screenOrientation=”portrait” > </activity> <activity android:name=”.ChangePasswordActivity” android:screenOrientation=”portrait” > </activity> </application> </manifest> 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. We’ve 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, we’ll focus on creating and uploading Snaps, as well as the profile and map view. Sanity check Congrats!
  • 17.    17www.stackmob.com SnapStack Android Bootcamp Part 2 In this part we’ll build the sign up/sign in flow for the app, allowing users to create profiles on SnapStack. We’ll 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 won’t add any logic behind it: Let’s build out the signup flow for SnapStack. Our signup process will be straightforward: we’ll have users create accounts by providing a username, password and email. We’ll also require users to upload a profile picture. For photos, we’ll 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 we’ve borrowed from this image cropping example on Github. CropOption.java: What we’ll 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); } }
  • 18.    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<CropOption> { private ArrayList<CropOption> mOptions; private LayoutInflater mInflater; public CropOptionAdapter(Context context, ArrayList<CropOption> 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:
  • 19.    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<String> adapter = new ArrayAdapter<String>(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:
  • 20.    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
  • 21.    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:
  • 22.    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<CropOption> cropOptions = new ArrayList<CropOption>(); Intent intent = new Intent(“com.android.camera.action.CROP”); intent.setType(“image/*”); List<ResolveInfo> 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));
  • 23.    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;
  • 24.    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) {
  • 25.    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(“Don’t forget to enter a username!”); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (password.equals(“”)) { builder.setMessage(“Don’t forget to enter a password!”); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (email.equals(“”)) { builder.setMessage(“Don’t 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(){ }
  • 26.    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. It’s 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
  • 27.    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:
  • 28.    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(“Don’t forget to enter a username!”); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (password.equals(“”)){ builder.setMessage(“Don’t forget to enter a password!”); AlertDialog dialog = builder.create(); dialog.show(); return true; } return false; } }
  • 29.    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.
  • 30.    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.os.Handler; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; 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; 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(); } }); } }); } } }); }
  • 31.    31www.stackmob.com @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) { 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(); }
  • 32.    32www.stackmob.com }); } }); } }); } 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(“Don’t forget to enter a username!”); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (password.equals(“”)) { builder.setMessage(“Don’t forget to enter a password!”); AlertDialog dialog = builder.create(); dialog.show(); return true; } else if (email.equals(“”)) { builder.setMessage(“Don’t 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(){ } 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);
  • 33.    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 Our app will leverage the User’s location to find Snaps nearby, and to add geopoints to the Snaps they upload. Add a class named GPSTracker.java: Adding GPSTracker The SignInActivity accepts a username and a password; the login attempts to sign in the user. Our helper method, foundError, notifies the user if they’re 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. 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(); } }); } }); }
  • 34.    34www.stackmob.com 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); 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”);
  • 35.    35www.stackmob.com 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 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);
  • 36.    36www.stackmob.com // 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) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { } @Override public IBinder onBind(Intent arg0) { return null; } } We’ll utilize this Service to handle all of our location needs. For more information, check out this tutorial. Let’s 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;
  • 37.    37www.stackmob.com 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); // 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();
  • 38.    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 we’ve fully built out our sign up/sign in flows. Build and run the app to check for errors. You’ll be greeted with MainActivity. Create an account and sign in. You’ve 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, we’ll focus on the foundation of our app, MasterActivity. Sanity Check Congrats! if(StackMob.getStackMob().isLoggedIn()) { final ProgressDialog progressDialog = ProgressDialog.show( MainActivity.this, “Signing in”, “Signing back in”, true); User.getLoggedInUser(User.class, new StackMobQueryCallback<User>() { @Override public void success(List<User> 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, “Couldn’t sign back in”, Toast.LENGTH_LONG). show(); } }); } } }
  • 39.    39www.stackmob.com SnapStack Android Bootcamp Part 3 In this chapter we’ll 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. We’ll also add a Profile view. Finally, we’ll 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. We’ll add more code to it in the next tutorial; for now, keep it as an empty view: What we’ll 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); } } We’ll display our Snaps in list views. To help with that, we’ll 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;
  • 40.    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<Snap> { private Context context; private List<Snap> objects; private DisplayImageOptions options; protected ImageLoader imageLoader = ImageLoader.getInstance(); public SnapAdapter(Context context, List<Snap> 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; } }
  • 41.    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<Snap> snaps = new ArrayList<Snap>(); 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
  • 42.    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<ListView>() { @Override public void onRefresh(PullToRefreshBase<ListView> 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(){ }
  • 43.    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<Snap>() { @Override public void success(List<Snap> result) { snaps = result; handler.post(new ListUpdater()); } @Override public void failure(StackMobException e) { handler.post(new ListUpdater());
  • 44.    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. We’ll walkthrough building that feature. Create an Activity called SharePhotoActivity, with the following code: The profile displays a user’s picture and username, as well as a list view of their Snaps. In onCreateOptionsMenu, we’ve 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;
  • 45.    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<String> adapter = new ArrayAdapter<String>(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); }
  • 46.    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) {
  • 47.    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<CropOption> cropOptions = new ArrayList<CropOption>(); Intent intent = new Intent(“com.android.camera.action.CROP”); intent.setType(“image/*”); List<ResolveInfo> 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 {
  • 48.    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(); } }); } }
  • 49.    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;
  • 50.    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<Snap> snaps = new ArrayList<Snap>(); 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(“Couldn’t get your location.”);
  • 51.    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<ListView>() { @Override public void onRefresh(PullToRefreshBase<ListView> 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);
  • 52.    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);
  • 53.    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)); }
  • 54.    54www.stackmob.com private class ListUpdater implements Runnable{ public ListUpdater(){ } public void run(){ if (snaps.size() == 0) { Toast.makeText(MasterActivity.this, “Couldn’t 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, “Couldn’t 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<Snap>() { @Override public void success(List<Snap> result) { snaps = result; handler.post(new ListUpdater()); } @Override public void failure(StackMobException e) { handler.post(new ListUpdater()); } });
  • 55.    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. You’ve 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, we’ll wrap up development on SnapStack. Congrats!
  • 56.    56www.stackmob.com SnapStack Android Bootcamp Part 4 In this chapter, we’ll focus on the detail view and comment view to our app. We’ll also add the ability to delete Snaps. Finally, we’ll finish with the ability to add comments to Snaps. Add an Activity named PhotoViewActivity: What we’ll 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();
  • 57.    57www.stackmob.com snapStackApplication = (SnapStackApplication) getApplication(); snap = snapStackApplication.getSnap(); ImageView imageView = (ImageView) findViewById(R.id.photo_image); imageLoader.displayImage(snap.getPhoto().getS3Url(), imageView, options); } @Override public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.delete_menu, menu); String username = snap.getCreator().getUsername(); User user = snapStackApplication.getUser(); if (username.equals(user.getUsername())) { return true; } return false; } @Override public boolean onOptionsItemSelected(MenuItem item) { progressDialog = ProgressDialog.show( PhotoViewActivity.this, “Deleting...”, “Deleting your snap”, true); snap.destroy(new StackMobModelCallback() { @Override public void success() { // the call succeeded progressDialog.dismiss(); setResult(RESULT_OK, null); finish(); } @Override public void failure(StackMobException e) { // the call failed progressDialog.dismiss(); runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog. Builder(PhotoViewActivity.this); builder.setTitle(“Uh oh...”); builder.setCancelable(true); builder.setMessage(“Couldn’t delete snap”); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); return true; } }
  • 58.    58www.stackmob.com In SnapStack, users can add comments to Snaps. Add an Activity named ShareCommentActivity: ShareCommentActivity 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.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; public class ShareCommentActivity extends Activity { private SnapStackApplication snapStackApplication; private EditText comment_edittext; private Button share_comment_button; private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share_comment); snapStackApplication = (SnapStackApplication) getApplication(); comment_edittext = (EditText) findViewById(R.id.comment_edittext); share_comment_button = (Button) findViewById(R.id.share_comment_button); share_comment_button.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View arg0) { String commentText = comment_edittext.getText().toString(); if (commentText.trim().length() != 0){ progressDialog = ProgressDialog.show( ShareCommentActivity.this, “Saving”, “Saving comment”, true); User user = snapStackApplication.getUser(); final Snap snap = snapStackApplication.getSnap(); Comment comment = new Comment(user, commentText, snap); PhotoViewActivity is very basic; it displays the photo from the Snap selected. The onCreateOptionsMenu presents the option to delete the Snap, if it was created by the user. When onOptionsItemSelected is called, we use the destroy method on the Snap object. If the delete is successful, we call finish on the activity.
  • 59.    59www.stackmob.com comment.save(new StackMobModelCallback() { @Override public void success() { // the call succeeded progressDialog.dismiss(); setResult(RESULT_OK, null); finish(); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); // the call failed threadAgnosticDialog(ShareCommentActivity.this, “There was an error saving your comment.”); } }); } } }); } private void threadAgnosticDialog(final Context ctx, final String txt) { runOnUiThread(new Runnable() { @Override public void run() { Builder builder = new AlertDialog.Builder(ctx); builder.setCancelable(true); builder.setMessage(txt); AlertDialog dialog = builder.create(); dialog.show(); } }); } } This activity presents a simple EditText for users to type comments. Once the share button is clicked, we create a Comment object, complete with the User who created it, the text of the comment and the associated Snap. After we call save, if it’s successful, we finish the activity. package com.stackmob.snapstack; 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; Now that we’ve built the feature to add comments, we’ll make an Activity to display them. Add an Activity named CommentViewActivity: CommentViewActivity
  • 60.    60www.stackmob.com import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; 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.StackMobQueryCallback; import com.stackmob.sdk.exception.StackMobException; public class CommentViewActivity extends Activity { List<Comment> comments; ListView listView; private SnapStackApplication snapStackApplication; private Handler handler = new Handler(); CommentAdapter adapter; DisplayImageOptions options; protected ImageLoader imageLoader = ImageLoader.getInstance(); private ProgressDialog progressDialog; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_comment); options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.placeholder) .showImageForEmptyUri(R.drawable.placeholder) .showImageOnFail(R.drawable.placeholder).cacheInMemory() .cacheOnDisc().bitmapConfig(Bitmap.Config.RGB_565).build(); listView = (ListView) findViewById(R.id.comment_listview); comments = new ArrayList<Comment>(); snapStackApplication = (SnapStackApplication) getApplication(); adapter = new CommentAdapter(CommentViewActivity.this, comments); listView.setAdapter(adapter); loadComments(); } public void loadComments() { progressDialog = ProgressDialog.show(CommentViewActivity.this, “Loading...”, “Loading comments”, true); Snap snap = snapStackApplication.getSnap(); StackMobQuery query = new StackMobQuery(); query.fieldIsEqualTo(“snap”, snap.getID()); Comment.query(Comment.class, query, StackMobOptions.depthOf(1), new StackMobQueryCallback<Comment>() { @Override public void success(List<Comment> result) {
  • 61.    61www.stackmob.com progressDialog.dismiss(); comments = result; handler.post(new ListUpdater()); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); handler.post(new ListUpdater()); Builder builder = new AlertDialog.Builder( CommentViewActivity.this); builder.setTitle(“Uh oh...”); builder.setCancelable(true); builder.setMessage(“There was an error loading comments.”); AlertDialog dialog = builder.create(); dialog.show(); } }); } private class ListUpdater implements Runnable { public ListUpdater() { } public void run() { adapter = new CommentAdapter(CommentViewActivity.this, comments); listView.setAdapter(adapter); } } private class CommentAdapter extends ArrayAdapter<Comment> { private List<Comment> objects; public CommentAdapter(Context context, List<Comment> objects) { super(context, R.layout.listview_comment_item, objects); this.objects = objects; } @Override public View getView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) { LayoutInflater inflater = (LayoutInflater) CommentViewActivity.this .getSystemService(Context.LAYOUT_INFLATER_SERVICE); view = inflater.inflate(R.layout.listview_comment_item, null); } if (objects != null) { Comment comment = objects.get(position); TextView user_name = (TextView) view .findViewById(R.id.comment_username); user_name.setText(comment.getCreator().getUsername()); ImageView comment_item_profile_image = (ImageView) view .findViewById(R.id.comment_item_profile_image);