Introduction
With the increased use of mobile devices, it is common for applications to offer features such as backup storage, data sync, data sharing etc. Building stand alone applications that only function and save their data on the device they are installed on is at times not feasible. A backend is usually needed where data can be saved and manipulated for the app to provide the service it was intended for.Building this requires time, a different skill set and other resources (e.g. servers, push notification services, etc). Fortunately, there are several platforms that provide ready-made customizable backends that you can integrate with your apps. These are known as ‘Backend as a Service’, or BaaS in short.
Pros and Cons of Using BaaS
Pros
- Saves time and resources.
- Provides different services in one package. Most of the available BaaS vendors not only provide backend cloud storage for you data, but also services as push notifications, analytics, social network integration, etc.
- Caters to scaling. The BaaS platforms are built for scaling and as a developer, you will not need to do any extra work if you were to gain a large amount of users. It will cost you more though, as you may move up the pricing plan.
- Easy to make changes. You can easily make changes to your application’s functionality without requiring a lot of rewrites to its backend. It is easy to change your database models using the platform’s dashboard. This comes in handy to anyone following the iterative ‘lean startup’ methodology of shipping and improving the app based on usage data.
Cons
- Using a BaaS can be expensive. While most of these services offer a free package for a certain number of requests a month, it can get costly if your app was to take off and gain many users.
- You might experience vendor lock-in where a switch to another platform is difficult. But this is becoming less of a problem as most services are built to allow for easier migrations. You might have to give up some services as not all BaaS platforms offer the same services.
- A huge disadvantage you should consider before using a BaaS is the dependence of your app on a third-party service. A provider might make changes to its service which you will either have to tolerate or look for another solution. There is also the possibility of a service being shut down which will be a huge inconvenience as you will be forced to migrate your data to another provider or to your own backend. An example of this is the recent shutdown of StackMob which forced developers to migrate their data.
Enter Parse
For this tutorial, we are going to look at using Parse to provide a backend for an Android application. We’ll briefly look at what it offers before building an application that will use it for user registration, authentication and storing of user data.Parse is one of the more popular Backend as a Service platforms. The service offers three products in one package: Parse Core, Parse Push and Parse Analytics.
Parse Core generally handles the saving of data and social media integration.
Parse Push is used to send push notifications. It enables the developer to customize, schedule and send push notifications to either all registered users or a select group of users.
Parse Analytics enables you to track your app’s data. You can track usage data such as installations, active users, user retention, push notification open rate etc.
Building the App
You can find the code for the app we’re building here. If you have any problems with the code or dependencies, there are some extra guidelines here.We will create a simple note taking application that will enable a user to save and retrieve notes to the cloud, thus maintaining the same data across devices. We will also look at how to make the app work offline using the Parse Local Datastore feature which is a new feature added to the Parse Android SDK.
To start, you should first create an account on parse.com if you don’t already have one. Once logged in, access the Dashboard where you should be able to create a new app and also see a list of all apps you create.
Create an app called NoteApp.
After creating the app, you will see a window containing your app IDs and keys. These will be used later in the Android app.
Download the Parse SDK here. Extract its contents into your project’s
libs
folder.Create a new Android project. I named mine NoteApp and set the minimum required SDK to API version 11. In the Create Activity window, select Blank Activity.
Next we’ll create a custom
Application
class. The Application
class is what is first loaded on application launch. By default, Android uses the generic Application
class, but we will create our own to do some initial setup.Create a new class called
NoteAppApplication
and set android.app.Application
as its superclass.In the
AndroidManifest.xml
file, set the name of the application
attribute with the class name you just created.
1
2
3
4
5
6
| < application android:name = "com.echessa.noteapp.NoteAppApplication" android:allowBackup = "true" android:icon = "@drawable/ic_launcher" android:label = "@string/app_name" android:theme = "@style/AppTheme" > |
Parse-x.x.x.jar
file from your extracted download into your libs
folder. If you are using Android Studio, remember to add the dependency in the build.gradle
file.In your manifest file, add the following permissions before the
application
tag.
1
2
| < uses-permission android:name = "android.permission.INTERNET" /> < uses-permission android:name = "android.permission.ACCESS_NETWORK_STATE" /> |
Application
class, add an onCreate()
method with the following code.
1
2
3
4
5
6
7
8
9
10
| @Override public void onCreate() { super.onCreate(); Parse.initialize(this, APPLICATION_ID, CLIENT_KEY); ParseObject testObject = new ParseObject("TestObject"); testObject.put("foo", "bar"); testObject.saveInBackground(); } |
Organize the imports needed in the file with
Ctrl+Shift+O
. (You can setup Eclipse and Android Studio to do this for you
automatically to save yourself some typing. You’ll only have to set an
import yourself when there is a conflicting library). In the article, I
won’t state the imports needed unless there are potential conflicts.Run the app and navigate to the Parse dashboard in your browser. Select your app and click on the Data Browser tab. You should see a table data of the object that was created above.
On the Dashboard, you will find controls to add/remove rows and columns, set permissions, export the class and even drop the whole class. Drop the
TestObject
class as we won’t need it for our app. Drop it by clicking on the More button and selecting Drop Class. Also delete the code that creates the TestObject
when the application launches.Not only can you create objects programmatically as we did above, but you can also do so using the Dashboard. We are going to create a
Post
class and some test posts that will be loaded in our app.Click on the New Class button and name the class Post. Leave the type as Custom. Add two string columns:
title
and content
. Add a few rows of data, just fill the title and content fields.Apart from the
title
and content
columns,
other columns are also generated – objectId, createdAt, updatedAt and
ACL. The ACL stands for Access Control Lists. These are used to specify
the access control users and/or roles have on particular objects.Back in our app, we are going to load the data in a list view. The list view will show the titles of the post and when clicked on, the content of the selected post will be shown in another activity. To keep it simple we will use the same view for viewing and editing text.
Note that we’ll be creating a simple app with a design not very suited for multiple screens. To design for multiple screens, use fragments to implement a proper master-detail flow.
Remove the code in the
onCreate()
method above that creates the ParseObject
and saves it.Open the
activity_main.xml
file and delete the Hello World TextView
. Place a list view onto the activity. Change its id attribute to @android:id/list
. This is required since we will be using the ListActivity
with a custom layout for our view. Remove the padding that was set on
the layout and have the list view width and height set to match_parent
and wrap_content
respectively. The settings are shown below.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| android:layout_width = "match_parent" android:layout_height = "match_parent" tools:context = ".MainActivity" > < ListView android:id = "@android:id/list" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignParentLeft = "true" android:layout_alignParentTop = "true" > </ ListView > </ RelativeLayout > |
MainActivity
class so that it extends ListActivity
.
This is an activity that displays a list of items by binding to a data
source. It has a default layout with a single, full-screen list in the
center of the screen, but we will be using a custom layout and that is
why we set android:id="@android:id/list"
on the ListView element in the activity above.We will style the list items in our list view to display the title of the post and an icon. Create a layout file in the
res/layout
folder named list_item_layout.xml
. Place a TextView in it with the following settings
1
2
3
4
5
6
7
8
9
10
11
| <? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "fill_parent" android:layout_height = "wrap_content" android:gravity = "center_vertical" android:padding = "5dp" android:singleLine = "true" android:textSize = "20sp" android:ellipsize = "end" android:drawableRight = "@drawable/ic_action_edit" > </ TextView > |
ic_action_edit
icon is from the Action Bar Icon Pack on the Android developer website. I used the holo-light version. You can also use this tool to generate icons.Create a class that will hold our Post data. Name it
Note.java
. It’s just a simple app with the note id
, content
and title
fields and their getters and setters.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| package com.echessa.noteapp; public class Note { private String id; private String title; private String content; Note(String noteId, String noteTitle, String noteContent) { id = noteId; title = noteTitle; content = noteContent; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } } |
MainActivity.java
declare a class variable that will hold a list of notes.
1
| private List< Note > posts; |
onCreate()
method in the same file as shown below.
1
2
3
4
5
6
7
8
9
10
11
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); posts = new ArrayList< Note >(); ArrayAdapter< Note > adapter = new ArrayAdapter< Note >(this, R.layout.list_item_layout, posts); setListAdapter(adapter); refreshPostList(); } |
ArrayAdapter
here for that. This maps the toString()
method of the object to a TextView in the row layout. To support other views or to have data other than the object’s toString()
output, you have to create a custom adapter. We’ll use the given adapter and override our object’s toString()
method so that it gives the title of the note.In
Note.java
add the following method.
1
2
3
4
| @Override public String toString() { return this.getTitle(); } |
refreshPostList()
method in the class. When this is called, it will fetch data from Parse and assign it to the posts
List.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| private void refreshPostList() { ParseQuery< ParseObject > query = ParseQuery.getQuery("Post"); query.findInBackground(new FindCallback< ParseObject >() { @Override public void done(List< ParseObject > postList, ParseException e) { if (e == null) { // If there are results, update the list of posts // and notify the adapter posts.clear(); for (ParseObject post : postList) { Note note = new Note(post.getObjectId(), post.getString("title"), post.getString("content")); posts.add(note); } ((ArrayAdapter< Note >) getListAdapter()).notifyDataSetChanged(); } else { Log.d(getClass().getSimpleName(), "Error: " + e.getMessage()); } } }); } |
There is a brief pause while the data loads. We will give the user an indication of this by including a loading spinner.
Add the following after the
super.onCreate(savedInstanceState);
statement in the onCreate()
method.
1
| requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
refreshPostList()
add the following before the call to findInBackground()
1
| setProgressBarIndeterminateVisibility(true); |
done
method.
1
| setProgressBarIndeterminateVisibility(false); |
res/menu/main.xml
add the following.
1
2
3
4
5
6
7
8
9
10
11
12
| < item android:id = "@+id/action_refresh" android:icon = "@drawable/ic_action_refresh" android:showAsAction = "always|withText" android:title = "@string/action_refresh" > </ item > < item android:id = "@+id/action_new" android:showAsAction = "always|withText" android:title = "@string/action_new" android:icon = "@drawable/ic_action_new" /> |
res/values/strings.xml
file.
1
2
| < string name = "action_new" >New Note</ string > < string name = "action_refresh" >Refresh</ string > |
Next we’ll create a new activity for adding and editing notes. You can have separate activities for viewing and editing notes, but for simplicity, we’ll use one view for this.
Right-click on your project and select New -> Other to bring up the New file wizard. From the Android dropdown menu, select Android Activity. On the next page, select Blank Activity. Name the activity EditNoteActivity and change the title to Add/Edit. Click Finish to add the files to your project. This will add code and layout files for the activity as well as modify the
strings.xml
and AndroidManifest.xml
file with required data for the activity.In
MainActivity.java
add the following code to handle action bar item clicks.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); switch (id) { case R.id.action_refresh: { refreshPostList(); break; } case R.id.action_new: { Intent intent = new Intent(this, EditNoteActivity.class); startActivity(intent); break; } case R.id.action_settings: { // Do something when user selects Settings from Action Bar overlay break; } } return super.onOptionsItemSelected(item); } |
Edit the
res/layout/activity_edit_note.xml
file as shown below. We change the layout from RelativeLayout
to LinearLayout
and add two EditText
fields and a button.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| <? xml version = "1.0" encoding = "utf-8" ?> android:layout_width = "match_parent" android:layout_height = "match_parent" android:paddingLeft = "16dp" android:paddingRight = "16dp" android:orientation = "vertical" > < EditText android:id = "@+id/noteTitle" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:ems = "10" android:hint = "@string/note_title_hint" /> < EditText android:id = "@+id/noteContent" android:layout_width = "match_parent" android:layout_height = "0dp" android:layout_weight = "1" android:gravity = "top" android:hint = "@string/note_content_hint" android:ems = "10" /> < Button android:id = "@+id/saveNote" android:layout_width = "100dp" android:layout_height = "wrap_content" android:layout_gravity = "right" android:text = "@string/save_button" /> </ LinearLayout > |
strings.xml
file.
1
2
3
| < string name = "note_title_hint" >Title</ string > < string name = "note_content_hint" >Content</ string > < string name = "save_button" >Save</ string > |
EditNoteActivity
activity tag as shown.
1
2
3
4
5
6
7
| < activity android:name = ".EditNoteActivity" android:label = "@string/title_activity_edit_note" > < meta-data android:name = "android.support.PARENT_ACTIVITY" android:value = "com.echessa.noteapp.MainActivity" /> </ activity > |
onCreate()
method of the EditNoteActivity.java
file right after the call to super.onCreate(savedInstanceState);
.
1
| getActionBar().setDisplayHomeAsUpEnabled(true); |
In
MainActivity.java
we’ll enable a user to select a list item and see its details in the Add/Edit view. Add the following method to the file.
1
2
3
4
5
6
7
8
9
10
11
| @Override protected void onListItemClick(ListView l, View v, int position, long id) { Note note = posts.get(position); Intent intent = new Intent(this, EditNoteActivity.class); intent.putExtra("noteId", note.getId()); intent.putExtra("noteTitle", note.getTitle()); intent.putExtra("noteContent", note.getContent()); startActivity(intent); } |
EditNoteActivity
class. This is one of the ways you can pass data between activities.In the
EditNoteActivity.java
file, add the following fields.
1
2
3
4
5
6
| private Note note; private EditText titleEditText; private EditText contentEditText; private String postTitle; private String postContent; private Button saveNoteButton; |
onCreate()
method as shown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.activity_edit_note); Intent intent = this.getIntent(); titleEditText = (EditText) findViewById(R.id.noteTitle); contentEditText = (EditText) findViewById(R.id.noteContent); if (intent.getExtras() != null) { note = new Note(intent.getStringExtra("noteId"), intent.getStringExtra("noteTitle"), intent.getStringExtra("noteContent")); titleEditText.setText(note.getTitle()); contentEditText.setText(note.getContent()); } saveNoteButton = (Button)findViewById(R.id.saveNote); saveNoteButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { saveNote(); } }); } |
Add the
saveNote()
method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
| private void saveNote() { postTitle = titleEditText.getText().toString(); postContent = contentEditText.getText().toString(); postTitle = postTitle.trim(); postContent = postContent.trim(); // If user doesn't enter a title or content, do nothing // If user enters title, but no content, save // If user enters content with no title, give warning // If user enters both title and content, save if (!postTitle.isEmpty()) { // Check if post is being created or edited if (note == null) { // create new post ParseObject post = new ParseObject("Post"); post.put("title", postTitle); post.put("content", postContent); post.saveInBackground(new SaveCallback() { public void done(ParseException e) { if (e == null) { // Saved successfully. Toast.makeText(getApplicationContext(), "Saved", Toast.LENGTH_SHORT).show(); } else { // The save failed. Toast.makeText(getApplicationContext(), "Failed to Save", Toast.LENGTH_SHORT).show(); Log.d(getClass().getSimpleName(), "User update error: " + e); } } }); } else { // update post ParseQuery< ParseObject > query = ParseQuery.getQuery("Post"); // Retrieve the object by id query.getInBackground(note.getId(), new GetCallback< ParseObject >() { public void done(ParseObject post, ParseException e) { if (e == null) { // Now let's update it with some new data. post.put("title", postTitle); post.put("content", postContent); post.saveInBackground(new SaveCallback() { public void done(ParseException e) { if (e == null) { // Saved successfully. Toast.makeText(getApplicationContext(), "Saved", Toast.LENGTH_SHORT).show(); } else { // The save failed. Toast.makeText(getApplicationContext(), "Failed to Save", Toast.LENGTH_SHORT).show(); Log.d(getClass().getSimpleName(), "User update error: " + e); } } }); } } }); } } else if (postTitle.isEmpty() && !postContent.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(EditNoteActivity.this); builder.setMessage(R.string.edit_error_message) .setTitle(R.string.edit_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } } |
ParseObject
or retrieves one respectively. Data is saved with the saveInBackground()
method which saves data asynchronously so your app won’t be blocked
until the save is complete. If the network is slow you can navigate back
to the notes list view and the data will still get saved.Add the following to the
strings.xml
file.
1
2
| < string name = "edit_error_title" >Error!</ string > < string name = "edit_error_message" >You cannot save a note without a title</ string > |
super.onCreate(savedInstanceState)
call in the onCreate()
method.
1
| requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
post.saveInBackground()
call for both the create-new and update-post blocks.
1
| setProgressBarIndeterminateVisibility(true); |
done
method.
1
| setProgressBarIndeterminateVisibility(false); |
Note
object when a note is saved so that when the Save button is pressed again, note
will not be null
.In the block that creates a new note, add the following after a note has been successfully saved.
1
| note = new Note(post.getObjectId(), postTitle, postContent); |
final
modifier to the post
object so that it can be accessed in the inner class.
1
| final ParseObject post = new ParseObject("Post"); |
if
block.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| if (note == null) { // create new post final ParseObject post = new ParseObject("Post"); post.put("title", postTitle); post.put("content", postContent); setProgressBarIndeterminateVisibility(true); post.saveInBackground(new SaveCallback() { public void done(ParseException e) { setProgressBarIndeterminateVisibility(false); if (e == null) { // Saved successfully. note = new Note(post.getObjectId(), postTitle, postContent); Toast.makeText(getApplicationContext(), "Saved", Toast.LENGTH_SHORT).show(); } else { // The save failed. Toast.makeText(getApplicationContext(), "Failed to Save", Toast.LENGTH_SHORT).show(); Log.d(getClass().getSimpleName(), "User update error: " + e); } } }); } |
The app is saving and retrieving posts, but anyone can use it and see all the notes saved in the backend. We will restrict the app use to logged in users, and also make it so that a user can only access their notes.
We’ll first create a login activity. Create a blank activity as you did previously. Name it LoginActivity and set its title to Login.
Modify the
res/layout/activity_login.xml
file as shown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| android:layout_width = "match_parent" android:layout_height = "match_parent" android:paddingBottom = "@dimen/activity_vertical_margin" android:paddingLeft = "@dimen/activity_horizontal_margin" android:paddingRight = "@dimen/activity_horizontal_margin" android:paddingTop = "@dimen/activity_vertical_margin" tools:context = ".LoginActivity" > < EditText android:id = "@+id/usernameField" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignParentLeft = "true" android:layout_alignParentTop = "true" android:ems = "10" android:hint = "@string/username_hint" > < requestFocus /> </ EditText > < EditText android:id = "@+id/passwordField" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignLeft = "@+id/usernameField" android:layout_below = "@+id/usernameField" android:ems = "10" android:hint = "@string/password_hint" android:inputType = "textPassword" /> < Button android:id = "@+id/loginButton" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignLeft = "@+id/passwordField" android:layout_below = "@+id/passwordField" android:text = "@string/login_button_label" /> < TextView android:id = "@+id/signUpText" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_below = "@+id/loginButton" android:layout_centerHorizontal = "true" android:layout_marginTop = "69dp" android:text = "@string/sign_up_text" /> </ RelativeLayout > |
strings.xml
file. Some of the attributes will be used later.
1
2
3
4
5
6
7
8
9
10
11
| < string name = "username_hint" >Username</ string > < string name = "password_hint" >Password</ string > < string name = "email_hint" >Email</ string > < string name = "sign_up_button_label" >Sign Up</ string > < string name = "signup_error_message" >Please make sure you enter a username, password, and email address!</ string > < string name = "signup_error_title" >Error!</ string > < string name = "login_error_message" >Please make sure you enter a username and password!</ string > < string name = "login_error_title" >Error!</ string > < string name = "login_button_label" >Login</ string > < string name = "sign_up_text" >Sign Up!</ string > < string name = "logout_label" >Logout</ string > |
Modify the
res/layout/activity_sign_up.xml
file as shown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
| android:layout_width = "match_parent" android:layout_height = "match_parent" android:paddingBottom = "@dimen/activity_vertical_margin" android:paddingLeft = "@dimen/activity_horizontal_margin" android:paddingRight = "@dimen/activity_horizontal_margin" android:paddingTop = "@dimen/activity_vertical_margin" tools:context = ".SignUpActivity" > < EditText android:id = "@+id/usernameField" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignParentLeft = "true" android:layout_alignParentTop = "true" android:ems = "10" android:hint = "@string/username_hint" > < requestFocus /> </ EditText > < EditText android:id = "@+id/passwordField" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignLeft = "@+id/usernameField" android:layout_below = "@+id/usernameField" android:ems = "10" android:inputType = "textPassword" android:hint = "@string/password_hint" /> < EditText android:id = "@+id/emailField" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignLeft = "@+id/passwordField" android:layout_below = "@+id/passwordField" android:ems = "10" android:inputType = "textEmailAddress" android:hint = "@string/email_hint" /> < Button android:id = "@+id/signupButton" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_alignLeft = "@+id/emailField" android:layout_below = "@+id/emailField" android:text = "@string/sign_up_button_label" /> </ RelativeLayout > |
When the application starts, we’ll check if a user is logged in and if not, the app will redirect to the Login view.
Since the creation of user accounts is a common requirement in applications, Parse provides the
ParseUser
class which automatically handles much of the functionality required for user account management. The ParseUser
has properties which are used in typical user assignment and management i.e. username, password and email.In the
MainActivity
class, add a check for the logged in user in the onCreate()
method after the call to setContentView(R.layout.activity_main)
.
1
2
3
4
| ParseUser currentUser = ParseUser.getCurrentUser(); if (currentUser == null) { loadLoginView(); } |
loadLoginView()
method to the file.
1
2
3
4
| private void loadLoginView() { Intent intent = new Intent(this, LoginActivity.class); startActivity(intent); } |
MainActivity
comes before the LoginActivity
. So ‘back’ is pressed, the app navigates to the previous activity. We need to clear the stack history and set the LoginActivity
as the start of the history stack. Modify loadLoginView()
as shown.
1
2
3
4
5
6
| private void loadLoginView() { Intent intent = new Intent(this, LoginActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } |
Modify the
LoginActivity
class as shown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
| public class LoginActivity extends ActionBarActivity { protected EditText usernameEditText; protected EditText passwordEditText; protected Button loginButton; protected TextView signUpTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.activity_login); signUpTextView = (TextView)findViewById(R.id.signUpText); usernameEditText = (EditText)findViewById(R.id.usernameField); passwordEditText = (EditText)findViewById(R.id.passwordField); loginButton = (Button)findViewById(R.id.loginButton); signUpTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(LoginActivity.this, SignUpActivity.class); startActivity(intent); } }); loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = usernameEditText.getText().toString(); String password = passwordEditText.getText().toString(); username = username.trim(); password = password.trim(); if (username.isEmpty() || password.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(LoginActivity.this); builder.setMessage(R.string.login_error_message) .setTitle(R.string.login_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } else { setProgressBarIndeterminateVisibility(true); ParseUser.logInInBackground(username, password, new LogInCallback() { @Override public void done(ParseUser user, ParseException e) { setProgressBarIndeterminateVisibility(false); if (e == null) { // Success! Intent intent = new Intent(LoginActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { // Fail AlertDialog.Builder builder = new AlertDialog.Builder(LoginActivity.this); builder.setMessage(e.getMessage()) .setTitle(R.string.login_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } } }); } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.login, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } } |
TextView
.
When they attempt to login, we first check the fields to make sure that
data has been entered before attempting to log them in with Parse. If
login fails, they will be notified. If it succeeds, then they will be
directed to the list view with the notes. Notice that we clear and set
the history stack before MainActivity
is called. This will ensure that the user cannot navigate back to the login view by pressing the Back button.Modify the
SignUpActivity
class as shown.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
| public class SignUpActivity extends ActionBarActivity { protected EditText usernameEditText; protected EditText passwordEditText; protected EditText emailEditText; protected Button signUpButton; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); getActionBar().setDisplayHomeAsUpEnabled(true); setContentView(R.layout.activity_sign_up); usernameEditText = (EditText)findViewById(R.id.usernameField); passwordEditText = (EditText)findViewById(R.id.passwordField); emailEditText = (EditText)findViewById(R.id.emailField); signUpButton = (Button)findViewById(R.id.signupButton); signUpButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { String username = usernameEditText.getText().toString(); String password = passwordEditText.getText().toString(); String email = emailEditText.getText().toString(); username = username.trim(); password = password.trim(); email = email.trim(); if (username.isEmpty() || password.isEmpty() || email.isEmpty()) { AlertDialog.Builder builder = new AlertDialog.Builder(SignUpActivity.this); builder.setMessage(R.string.signup_error_message) .setTitle(R.string.signup_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } else { setProgressBarIndeterminateVisibility(true); ParseUser newUser = new ParseUser(); newUser.setUsername(username); newUser.setPassword(password); newUser.setEmail(email); newUser.signUpInBackground(new SignUpCallback() { @Override public void done(ParseException e) { setProgressBarIndeterminateVisibility(false); if (e == null) { // Success! Intent intent = new Intent(SignUpActivity.this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); startActivity(intent); } else { AlertDialog.Builder builder = new AlertDialog.Builder(SignUpActivity.this); builder.setMessage(e.getMessage()) .setTitle(R.string.signup_error_title) .setPositiveButton(android.R.string.ok, null); AlertDialog dialog = builder.create(); dialog.show(); } } }); } } }); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu.sign_up, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { // Handle action bar item clicks here. The action bar will // automatically handle clicks on the Home/Up button, so long // as you specify a parent activity in AndroidManifest.xml. int id = item.getItemId(); if (id == R.id.action_settings) { return true; } return super.onOptionsItemSelected(item); } } |
SignUpActivity
to be the LoginActivity
so we can navigate Up to the login view from the signup view.
1
2
3
4
5
6
7
| < activity android:name = ".SignUpActivity" android:label = "@string/title_activity_sign_up" > < meta-data android:name = "android.support.PARENT_ACTIVITY" android:value = "com.echessa.noteapp.LoginActivity" /> </ activity > |
In
res/menu/main.xml
add an action bar item.
1
2
3
4
| < item android:id = "@+id/action_logout" android:title = "@string/logout_label" > </ item > |
MainActivity.java
add the following case to the switch statement in onOptionsItemSelected(MenuItem item)
method.
1
2
3
4
| case R.id.action_logout: ParseUser.logOut(); loadLoginView(); break; |
Add the following to
EditNoteActivity
in the saveNote()
method. Add it to the if
block that creates a new note, after this statement post.put("content", postContent);
.
1
| post.put("author", ParseUser.getCurrentUser()); |
In
MainActivity
, add the following in the refreshPostList()
method after this statement ParseQuery query = ParseQuery.getQuery("Post");
.
1
| query.whereEqualTo("author", ParseUser.getCurrentUser()); |
Notes
We have looked at how to integrate the Parse SDK into an Android app. We’ve seen how to save and retrieve data to the backend and how to manage user accounts. There are other features you could include which I will mention here.Deleting Objects
You should include a way for users to delete notes. How you accomplish this is up to you. Common design patterns in Android for deletion include swiping on the list item to delete, long pressing on the item to bring up a confirmation dialog to delete, and you can also include a delete button on the Add/Edit detail view.To delete a note, use the following
1
| post.deleteInBackground(); |
Password Reset
When you expect users to have passwords to use your application, you should include a way for them to reset their passwords incase they forget them. Parse already has this functionality in place, so you only need to call the following when a user requests a password reset.
1
2
3
4
5
6
7
8
9
| ParseUser.requestPasswordResetInBackground(userEmail, new RequestPasswordResetCallback() { public void done(ParseException e) { if (e == null) { // An email was successfully sent with reset instructions. } else { // Something went wrong. Look at the ParseException to see what's up. } } }); |
Offline Use
The app that we created needs an internet connection to work as needed. It requires an internet connection to retrieve and save notes.Parse now offers the Parse Local Datastore which enables you to save data locally and query the data with the usual
ParseQuery
. You do this by pinning data to the local datastore and unpinning the data to remove it.When the app is offline it will still be able to retrieve and save data and when an internet connection is established again, it will sync the data in the cloud with the local datastore. For information on how to do this check the guides,
Conclusion
We have looked at using Parse as a BaaS system for your apps. Using such ready-made backend services has numerous advantages, but it also comes with some pitfalls which should be measured against the advantages when deciding whether to build from scratch or to use a BaaS solution. Even if you plan to build your own backend, it might be worth it to start with a BaaS to reduce the time it takes for your app to get to the market and also to test out the app with the market and validate functionality quickly based on usage data.Apart from Parse, there are several other solutions/services, for example Apigee, Backendless, Kii, built.io, Firebase and others. These provide different services at different prices and it’s worth looking at and comparing the different products to decide what best suits your needs.
Credit goes to http://www.sitepoint.com
No comments:
Post a Comment