• Save
Build an Android Application eBook
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Build an Android Application eBook

on

  • 1,173 views

 

Statistics

Views

Total Views
1,173
Views on SlideShare
965
Embed Views
208

Actions

Likes
7
Downloads
0
Comments
0

4 Embeds 208

http://laurak.co 179
http://www.gabrielle-group.com 16
http://laurakhalil.squarespace.com 7
https://laura-khalil-grh9.squarespace.com 6

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Build an Android Application eBook Document Transcript

  • 1.    1www.stackmob.comStep by Step Guidefor AndroidBuild aPhotoSharingApp in aDay
  • 2.    2www.stackmob.comThis series is focused on the creation of SnapStack, a location-based photo sharing app for Androidphones. We’ll walk through the entire process of building SnapStack, from the initial idea and design tosubmitting to the Google Play Store. Along the way, we’ll demonstrate the usefulness of the StackMobplatform and highlight the benefits of incorporating StackMob into your next project. DownloadSnapStack from the Google Play Store.SnapStack Android BootcampWelcome!Who should read this tutorial?PrerequisitesIdeaWhat we’ll coverThis series is aimed at developers of all skill levels who are looking for an example app that showcasesthe features of the StackMob platform. The goal is to illustrate how easy it is to build and release to theGoogle Play Store using StackMob as the backend of your app. The pace of this tutorial will start out slowand 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, viewphotos nearby both in a feed and on a map, and post comments. Download SnapStack from the GooglePlay Store.Creating an app like SnapStack requires a fair amount of setup. In this tutorial, we’ll be adding all ofthe ingredients necessary to construct our app. We’ll create an Android Eclipse project as well as aStackMob app. The next part of this tutorial will cover adding the necessary SDKs and configurations toour 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.comWe’ll be using many features provided by the StackMob Android SDK and the StackMob Marketplace. Themodules 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 todownload and install the ADT bundle.SnapStack Android BootcampPart 1: SetupUsing StackMobDownload ADTCreate a new Android project1. Open up ADT and select a directory for your workspace. The default is ~/Documents/workspace.2. Choose File > New > Android Application Project.Access Controls:The access controls module will give us greater control over schema permissions.API: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.com3. Enter “SnapStack” for thename of the application, and“SnapStackAndroid” for theproject name. Enter a uniqueidentifier for your packagename. Set your minimumAndroid SDK to 3.0. Set theproject to compile with thelatest Google APIs. Click next.4. Make sure “Create Activity” isselected. Click next twice.
  • 5.    5www.stackmob.com6. Enter “MainActivity” for theActivity 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 advantageof the StackMob platform immediately. To do this, we’ll create an app on StackMob, which will comecomplete with Development and Production Keys, API requests and some Marketplace modulespreinstalled, all for free!1. Head over to StackMob andlogin to your Dashboard (ifyou don’t have an account,create one first). Click “CreateNew App,” in the top rightcorner.Create a StackMob application5. Choose “Blank Activity” andclick next.
  • 6.    6www.stackmob.com2. Create an app called“snapstack” and chooseAndroid as your platform.Click next.3. Follow the setup instructionsfor importing the StackMobAndroid SDK.Follow the tutorial to create and add S3 credentials to your StackMob app.Setting up S3Our 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 tobe 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, aswell 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 ofthe comment, and the relationship to its parent Snap object.Creating the data model1. Navigate to the Schema Configuration tab inyour Dashboard.2. Click “Edit” next to the User schema. Add thefollowing 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.com3. 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 location4. StackMob allows you to manage schema permissions using the Access Controls module. Edit thepermissions 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 therelated object set to “snap.” Make it a one-to-one relationship. Add another one-to-one relationshipto “user,” called “creator.”6. Go back and edit the “snap” schema. Add a relationship called “comments” and set the related objectto “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, “Adda Map.”Setting up Google Maps
  • 8.    8www.stackmob.compackage 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 newclass called Snap.java, with the following code:Creating a Snap modelWe’ll add a class named “User” to our project that subclasses StackMobUser. StackMobUser is aspecialized subclass of StackMobModel meant to represent a user of your app. Like StackMobModel, it’smeant 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 modelpackage 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.comOur “Comment” class will also subclass StackMobModel. Add a new class called Comment.java, with thefollowing code:Creating a comment modelpackage 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.comSnapStack makes use of many string constants throughout the app. Edit res/values/strings.xml withthe 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 temporarypassword.</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.comDownload Android Pull to Refresh and add it to your project:1. Right-click the project and choose “Import…”Adding 3rd party libraries2. 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.com5. Navigate to the Android menu and click “Add…”:6. Select the library and click OK.Download Android UniversalImage Loader and add it to yourproject. Copy the jar file intoyour libs folder.4. Right-click the project, and select “Properties”:
  • 13.    13www.stackmob.comIn our app we’ll create anApplication class. This iswhere we’ll store our Userobject, for use throughoutthe app. Create a class calledSnapStackApplication.java.SnapStackApplicationMake sure it subclasses theApplication class.Adding the necessary assets1. Drag the entire drawable folder into the project, under the res folder. Make sure you have “Copyfiles” 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 sureyou have “Copy files” selected.4. Finally, copy the contents of the menu folder into the corresponding directory in your project. Makesure you have “Copy files” selected.We’ve included the assets needed for this project, including drawables and XML layouts. Download andunzip the assets for this project.
  • 14.    14www.stackmob.compackage 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 forboth of them. We’ll use these two extensively throughout the project. We’ll also be utilizing ImageLoaderthroughout the project, and we initialize it here.
  • 15.    15www.stackmob.comUpdate 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-featureandroid:glEsVersion=”0x00020000”android:required=”true” /><permissionandroid: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-featureandroid:name=”android.hardware.camera”android:required=”true” /><uses-sdkandroid:minSdkVersion=”14”android:targetSdkVersion=”17” /><applicationandroid:name=”.SnapStackApplication”android:allowBackup=”true”android:icon=”@drawable/app_icon”android:label=”@string/app_name”android:theme=”@style/AppTheme” ><meta-dataandroid:name=”com.google.android.maps.v2.API_KEY”android:value=”YOUR_API_KEY” /><activityandroid: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><activityandroid:name=”.SignUpActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.SignInActivity”android:screenOrientation=”portrait” ></activity><activity
  • 16.    16www.stackmob.comandroid:name=”.ChoosePhotoActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.MasterActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.ProfileActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.SharePhotoActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.DetailViewActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.PhotoViewActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.CommentViewActivity”android:screenOrientation=”portrait”></activity><activityandroid:name=”.ShareCommentActivity”android:screenOrientation=”portrait” ></activity><activityandroid:name=”.ForgotPasswordActivity”android:screenOrientation=”portrait” ></activity><activityandroid: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 arein place: StackMob, S3 integration, XML layouts and more. We can focus on simply the code from hereon out.In Part 2, we’ll focus on creating and uploading Snaps, as well as the profile and map view.Sanity checkCongrats!
  • 17.    17www.stackmob.comSnapStack Android BootcampPart 2In 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 throughMasterActivity. 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 haveusers create accounts by providing a username, password and email. We’ll also require users to upload aprofile picture.For photos, we’ll make use of the standard Android Camera library. Users will have the option to take aphoto 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 coverCreating the MasterActivity classThe signup flowpackage com.stackmob.snapstack;import android.app.Activity;import android.os.Bundle;public class MasterActivity extends Activity { @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_master);}}
  • 18.    18www.stackmob.compackage 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.compackage 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 errorsaving 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, intitem) { 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 moreinformation, 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.comimport 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 errorsigning 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.compublic 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 loggingin.”); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); }}The signup view accepts a username/password/email combo and creates an account for the user. We dothis by creating a new User object and calling save on it. When the save is successful, we use a Handlerto call the UserLogin runnable, which in its run method signs the User in. The foundError methodchecks for any empty or incorrect fields and notifies the user. If the save is successful, we present theChoosePhotoActivity.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 theforgot password email. With the Android SDK, we can call a method that will trigger the process to allowthe user to reclaim their account.Implementing forgot password
  • 27.    27www.stackmob.compackage 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.compackage 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, newStackMobModelCallback() { @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, alongwith 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.comThis is where users enter their username and request a temporary password. ThesentForgotPasswordEmail method causes a temporary password to be sent to the user via email. Thatpassword is valid for 24 hours.Create a new Activity named SignInActivity.java, with the following code:SignInActivitypackage 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 torecover 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 errorsigning 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.compackage 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 statusOur app will leverage the User’s location to find Snaps nearby, and to add geopoints to the Snaps theyupload. Add a class named GPSTracker.java:Adding GPSTrackerThe SignInActivity accepts a username and a password; the login attempts to sign in the user. Ourhelper method, foundError, notifies the user if they’re missing their username or password. Wepresent the option to recover password if necessary, linking to ForgotPasswordActivity. If the sign in issuccessful, 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 loggingin.”); AlertDialog dialog = builder.create(); dialog.show(); } }); } }); }
  • 34.    34www.stackmob.comboolean isGPSEnabled = false;// flag for network statusboolean isNetworkEnabled = false;// flag for GPS statusboolean canGetLocation = false;Location location; // locationdouble latitude; // latitudedouble longitude; // longitude// The minimum distance to change Updates in metersprivate static final long MIN_DISTANCE_CHANGE_FOR_UPDATES = 10; // 10 meters// The minimum time between updates in millisecondsprivate static final long MIN_TIME_BW_UPDATES = 1000 * 60 * 1; // 1 minute// Declaring a Location Managerprotected LocationManager locationManager;public GPSTracker(Context context) {this.mContext = context;getLocation();}public Location getLocation() {try {locationManager = (LocationManager) mContext.getSystemService(LOCATION_SERVICE);// getting GPS statusisGPSEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);// getting network statusisNetworkEnabled = locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);if (!isGPSEnabled && !isNetworkEnabled) {// no network provider is enabled} else {this.canGetLocation = true;// First get location from Network Providerif (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 Servicesif (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.comif (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 latitudereturn latitude;}/*** Function to get longitude* */public double getLongitude(){if(location != null){longitude = location.getLongitude();}// return longitudereturn 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 TitlealertDialog.setTitle(“GPS is settings”);// Setting Dialog MessagealertDialog.setMessage(“GPS is not enabled. Do you want to go to settings menu?”);// On pressing Settings buttonalertDialog.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 buttonalertDialog.setNegativeButton(“Cancel”, new DialogInterface.OnClickListener() {public void onClick(DialogInterface dialog, int which) {dialog.cancel();}});// Showing Alert MessagealertDialog.show();}@Overridepublic void onLocationChanged(Location location) {}@Overridepublic void onProviderDisabled(String provider) {}@Overridepublic void onProviderEnabled(String provider) {}@Overridepublic void onStatusChanged(String provider, int status, Bundle extras) {}@Overridepublic 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 MainActivitypackage com.stackmob.snapstack;import java.util.List;import android.app.Activity;
  • 37.    37www.stackmob.comimport 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;@Overrideprotected 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 buttonssign_up = (Button) findViewById(R.id.signup_button);sign_in = (Button) findViewById(R.id.signin_button);// Set an OnClickListener for the sign_up buttonsign_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 buttonsign_in.setOnClickListener(new View.OnClickListener() {public void onClick(View v) { Intent intent = new Intent( MainActivity.this, SignInActivity.class); startActivity(intent);}});}@Overrideprotected void onResume() {super.onResume();
  • 38.    38www.stackmob.comIn MainActivity, we initialize the StackMob SDK. Copy your public key from the Dashboard and use it inthe init method.The MainActivity simply presents two buttons that link to Sign Up and Sign In, respectively. At this pointwe’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 inflows and added forgot password functionality to our app.In Part 3, we’ll focus on the foundation of our app, MasterActivity.Sanity CheckCongrats!if(StackMob.getStackMob().isLoggedIn()) { final ProgressDialog progressDialog = ProgressDialog.show( MainActivity.this, “Signing in”, “Signing back in”, true); User.getLoggedInUser(User.class, new StackMobQueryCallback<User>() {@Overridepublic 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);}@Overridepublic void failure(StackMobException e) { progressDialog.dismiss(); Toast.makeText(MainActivity.this, “Couldn’t sign back in”, Toast.LENGTH_LONG).show();}});}}}
  • 39.    39www.stackmob.comSnapStack Android BootcampPart 3In this chapter we’ll add MasterActivity, the core of our app. It consists of a pull-to-refresh list viewlayered 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 coverAdding DetailViewActivitypackage 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 helperclass. Create a file named SnapAdapter, and add the following code:SnapAdapterpackage 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.comimport 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.compackage 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(); @Overrideprotected 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.compublic 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);}} @Overridepublic boolean onCreateOptionsMenu(Menu menu) {MenuInflater inflater = getMenuInflater();inflater.inflate(R.menu.signout_menu, menu);return true;} @Overridepublic 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), newStackMobQueryCallback<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 itwith their location to create a Snap. We’ll walkthrough building that feature. Create an Activity calledSharePhotoActivity, with the following code:The profile displays a user’s picture and username, as well as a list view of their Snaps. InonCreateOptionsMenu, we’ve added a sign out menu option to fully complete the flow for users. WhenonOptionsItemSelected is called, we sign the user out and bring them back to MainActivity. TheloadObjects method queries for Snaps created by the user. We feed the objects to our SnapAdapter,which we pair with our list view.Adding SharePhotoActivitypackage 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.comimport 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 photowas shared to SnapStack!”); setResult(RESULT_OK, null); finish(); } @Override public void failure(StackMobException e) { progressDialog.dismiss(); threadAgnosticDialog(SharePhotoActivity.this, “There wasan 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, intitem) { 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.comIn 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 thesame doCrop method to crop our images. Once we have a photo, we attach it to the Snap object stored inSnapStackApplication, and call save. If the save is successful, we call finish on SharePhotoActivity.The MasterActivitypackage 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.compublic 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; @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_master);gps = new GPSTracker(this);snapStackApplication = (SnapStackApplication) getApplication();// Getting Google Play availability statusint status = GooglePlayServicesUtil.isGooglePlayServicesAvailable(getBaseContext());// Showing statusif(status!=ConnectionResult.SUCCESS){ // Google Play Services are not availableint 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() {@Overridepublic 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() {@Overridepublic 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), newStackMobQueryCallback<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 buttonto 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 queryis successful, the list view is refreshed using the run method from our private ListUpdater. The list viewuses SnapAdapter to build its list items.Also in the run method, we update the map fragment, using the setMarkers. This method plots theSnaps as annotations using their locations, and focuses the map camera to fit them all. The map uses anOnMarkerClickListener to build a custom dialog window for the annotations; when an annotation isselected, 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 signout. Finally, we added the biggest piece of our app, MasterActivity.In Part 4, we’ll wrap up development on SnapStack.Congrats!
  • 56.    56www.stackmob.comSnapStack Android BootcampPart 4In this chapter, we’ll focus on the detail view and comment view to our app. We’ll also add the ability todelete Snaps. Finally, we’ll finish with the ability to add comments to Snaps.Add an Activity named PhotoViewActivity:What we’ll coverPhotoViewActivitypackage 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; @Overrideprotected 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.comsnapStackApplication = (SnapStackApplication) getApplication();snap = snapStackApplication.getSnap(); ImageView imageView = (ImageView) findViewById(R.id.photo_image); imageLoader.displayImage(snap.getPhoto().getS3Url(), imageView, options); } @Overridepublic 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;} @Overridepublic 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.comIn SnapStack, users can add comments to Snaps. Add an Activity named ShareCommentActivity:ShareCommentActivitypackage 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. TheonCreateOptionsMenu presents the option to delete the Snap, if it was created by the user. WhenonOptionsItemSelected is called, we use the destroy method on the Snap object. If the delete issuccessful, 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, “Therewas 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 theassociated 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 anActivity named CommentViewActivity:CommentViewActivity
  • 60.    60www.stackmob.comimport 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 loadingcomments.”); 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);
  • 62.    62www.stackmob.compackage com.stackmob.snapstack;import android.app.Activity;import android.content.Intent;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Bundle;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;import android.view.View;import android.view.View.OnClickListener;import android.view.animation.AlphaAnimation;import android.view.animation.Animation;import android.view.animation.DecelerateInterpolator;import android.widget.Button;import android.widget.ImageView;import android.widget.TextView;import com.nostra13.universalimageloader.core.DisplayImageOptions;import com.nostra13.universalimageloader.core.ImageLoader;import com.stackmob.sdk.api.StackMobQuery;import com.stackmob.sdk.callback.StackMobCountCallback;import com.stackmob.sdk.exception.StackMobException;public class DetailViewActivity extends Activity { private Snap snap; SnapStackApplication snapStackApplication;In CommentViewActivity we’ve built a ListAdapter similar to SnapAdapter. In the OnCreate method, wecall loadComments, which queries for any Comments associated with the Snap.Add an Activity named DetailViewActivity, which simply ties all of previous Activities together:DetailViewActivity if (comment.getCreator().getPhoto() != null) { imageLoader.displayImage(comment.getCreator().getPhoto() .getS3Url(), comment_item_profile_image, options); } else { Bitmap bitmap = BitmapFactory.decodeResource( getResources(), R.drawable.default_avatar); comment_item_profile_image.setImageBitmap(bitmap); } TextView text = (TextView) view.findViewById(R.id.comment_text); text.setText(comment.getText()); } return view; } }}
  • 63.    63www.stackmob.com private Button commentsButton; DisplayImageOptions options; protected ImageLoader imageLoader = ImageLoader.getInstance(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_detail); options = new DisplayImageOptions.Builder() .showStubImage(R.drawable.placeholder) .showImageForEmptyUri(R.drawable.placeholder) .showImageOnFail(R.drawable.placeholder).cacheInMemory() .cacheOnDisc().bitmapConfig(Bitmap.Config.RGB_565).build(); snapStackApplication = (SnapStackApplication) getApplication(); snap = snapStackApplication.getSnap(); TextView user_name = (TextView) findViewById(R.id.detail_username); user_name.setText(snap.getCreator().getUsername()); ImageView detail_profile_image = (ImageView) findViewById(R.id.detail_profile_image); if (snap.getCreator().getPhoto() != null) { imageLoader.displayImage(snap.getCreator().getPhoto().getS3Url(), detail_profile_image, options); } else { Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.default_avatar); detail_profile_image.setImageBitmap(bitmap); } ImageView imageView = (ImageView) findViewById(R.id.detail_image); imageLoader .displayImage(snap.getPhoto().getS3Url(), imageView, options); imageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(DetailViewActivity.this, PhotoViewActivity.class); startActivityForResult(intent, 0); } }); commentsButton = (Button) findViewById(R.id.detail_comments); commentsButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(DetailViewActivity.this, CommentViewActivity.class); startActivityForResult(intent, 0); } }); commentsButton.setVisibility(View.GONE); countComments(); } private void countComments(){
  • 64.    64www.stackmob.com Snap snap = snapStackApplication.getSnap(); StackMobQuery query = new StackMobQuery(); query.fieldIsEqualTo(“snap”, snap.getID()); Comment.count(Comment.class, query, new StackMobCountCallback(){ @Override public void success(long count) { // TODO Auto-generated method stub String commentLabel; if (count == 0) { return; } else if (count > 1) { commentLabel = “ Comments”; } else { commentLabel = “ Comment”; } final String label = “” + (int)count + commentLabel; runOnUiThread(new Runnable() { @Override public void run() { commentsButton.setText(label); commentsButton.setVisibility(View.VISIBLE); Animation fadeIn = new AlphaAnimation(0, 1); fadeIn.setInterpolator(new DecelerateInterpolator()); fadeIn.setDuration(500); commentsButton.setAnimation(fadeIn); } }); } @Override public void failure(StackMobException arg0) { // TODO Auto-generated method stub } }); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK && requestCode == 0) { setResult(RESULT_OK, null); finish(); } if (resultCode == Activity.RESULT_OK && requestCode == 1) { setResult(RESULT_OK, null); countComments(); } } @Override
  • 65.    65www.stackmob.comBuild an run, and you have a complete app on your hands!After completing this part, our app is operating and fully functional.In Part 5, we’ll detail the steps to submit our finished app to the Google Play Store.Congrats!Build and run public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.comment_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { Intent intent = new Intent(DetailViewActivity.this, ShareCommentActivity.class); startActivityForResult(intent, 1); return true; }}
  • 66.    66www.stackmob.comSnapStack Android BootcampPart 5In this chapter, we’ll talk about testing on our app, push to production and learn how to submit to thePlay Store.Although we strive for our code to be perfect, usually it ends up far from it. It’s not a question of if yourapplication has bugs, but when and how many. It’s very important to test your app as much as possiblebefore releasing to the Google Play Store. Fortunately, there are a few resources available to help in thisprocess.The Android Emulator comes packaged with the Android SDK, and allows for the developers to createmany different virtual devices. Using the emulator, a developer can test against a large selection ofAndroid API levels.With Android, it’s very easy to build anddistribute your app to potential testers.Simply export and APK file of the project,and you can install it on anyones phone.To export a project into an APK, right clickthe project and select export:Testing your appAndroid emulatorAd-hoc distributionWhat we’ll cover
  • 67.    67www.stackmob.comUnder Android, select “ExportAndroid Application” and clicknext:In the keystore selection screen,navigate to wherever yourproduction keystore is located.If you don’t have a productionkeystore, you can create one.Enter your keystore passwordand click next.Select your keystore key alias,enter your password and clicknext.
  • 68.    68www.stackmob.comFinally, choose a place to saveyour APK and click finish.Developers can send out theirAPKs to groups of testersand have them install rightfrom email. This makes ad-hocdistribution and coordinationless painful.Now that you’ve finished building and testing your app, it’s ready to be released, right? Not so fast.Before we showcase our creation to the world, we need to setup our production environment. Duringthe development process, everything about your app is in the development stage. This allows you totest and experiment. Once your app goes public, you’ll want to separate everything into two separateenvironments: Development and Production. This provides you the opportunity to test changes andimprovements in your app without directly affecting live users.To accomplish this on StackMob, head to the deploy section of your dashboard. Check “Deploy API”and click Deploy. Enter a version number, and all of your schemas will be copied over into a Productionenvironment.Tip: If it’s your first time deploying to production, use the version number 1.0.Back in Eclipse, update where we instantiate StackMob in LoginActivity; add your Production keys andchange your version number to 1.Deploy to Production
  • 69.    69www.stackmob.comTo submit apps to the Google Play store, you’ll need to be registered for a Google Play publisher account.Check out this tutorial on how to register to be a Google Play publisher.Once you’ve registered, you’ll be provided with a developer console to view the status of your apps.Google provides an excellent walkthrough of the Google Play developer console.Finally, the time comes to submit to the Google Play store. Make sure you follow the checklist for submittingto the Play store, a detailed tutorial provided by Google for developers in preparation for release.Preparing for releaseYou’ve successfully completed our Android bootcamp. We went through the different stages ofdevelopment a pieced together a complete app. Using StackMob allowed us to rapidly build in thefeatures for this app. Let’s recap the benefits brought by utilizing the StackMob platform and ourAndroid SDK:• User Authentication• Access Control Lists (ACL)• CRUD API• S3 Integration• GeoPoints• Development and Production EnvironmentsImagine if you had to create that backend entirely by yourself!Through this series, you’ve seen the power of the StackMob platform, as well as its flexibility. Thepossibilities are endless for developers who utilize StackMob with their apps and services. In the finalchapter, we’ll finish out our SnapStack series with a final post about monitoring the performance of yourapp.If you’re new to StackMob, it’s completely free to get started. Sign up today!Congrats!