Post on 10-Aug-2015
Android Well-Being Application Natalie Sanders
I. Problem Presented
Researchers were in need of a native Android application which would allow employers to gauge
their employees’ emotional and physical state throughout the day. This would be accomplished by
having the subjects complete multiple surveys during certain days at specific times. Thus, the
application would be an interface for completing various surveys. This required the application to
have the following capabilities:
Store the surveys
Receive updates from the administrator/s
Notify the user when a survey becomes available
Have a display listing all surveys and distinguish which surveys are open
Send completed survey data to a web database (Parse)
II. Implementation
When the application is first installed, the class Wellbeing initializes the application to connect
to the Wellbeing project in the Parse website. This will allow the application to communicate with
Parse for updates and storing survey responses. Next, the class queries Parse to pull all of the active
surveys from a table called SurveySummary. Each entry in the table corresponds to a survey and lists
the name of a separate Parse table which specifies all of the survey’s questions and metadata. Thus,
additional Parse queries are needed for each survey in order to retrieve all of the survey data. Each
survey and its data are then stored in a SQLite database and alarms are set for each time that the
survey is active.
The home screen of the application is implemented in an Android activity called StartScreen.
StartScreen first checks if the user’s email is saved in the application’s SharedPreferences. If no
email is found, StartScreen calls the activity EmailDialog which creates a pop-up alert dialog asking
the user for their email. The pop-up cannot be dismissed until the user enters a valid email address.
Figure 1: Email Dialog
The StartScreen activity also sets a daily alarm for the intent service UpdateService which updates
the application every night. The StartScreen activity and the UpdateService communicate via a
broadcast receiver so that the home screen’s ListView can be updated dynamically. Meanwhile, the
home screen displays a ListView that is populated with surveys from the database. Each entry has its
own icon, the name of the survey, the day abbreviations for which it is active, and the times for
which it is active. When a survey is active, its entry is displayed in black and when it’s inactive, its
entry is greyed out. Clicking on a survey entry while it is active will take the user to the survey itself,
implemented in SurveyScreen.
Figure 2: Start Screen
The SurveyScreen activity is responsible for the survey itself. The previous activity sends the
survey ID along with the intent. This allows the SurveyScreen activity to retrieve all the data for the
corresponding survey from the SQLite database. Using this data, the activity can display four types
of questions: multiple-choice button questions, checkbox questions, slider rating questions, and
textbox input questions. One checkbox, textbox, or button question may be displayed per page but
up to three slider questions can be displayed on a page. A question is displayed by programmatically
adding the question and its answer method to a TableView in the RelativeLayout. To retrieve the
user’s responses, the activity implements a listener on the current question/s in the view. When a
user inputs their answer, the answer value and the timestamp at which it was chosen is stored in
the SQLite database. To move between questions the user uses the 'back' and 'next’ buttons at the
bottom of the screen. When switching between questions, the activity clears the TableView and
then programmatically builds the next question. Pressing ‘next’ on the last question brings the user
to the end screen, implemented by the activity FinishScreen. Likewise, pressing ‘back’ on the first
question brings the user to the home screen.
Figure 3: Button, Slider, Textbox, & Checkbox Questions (Clockwise)
The ending screen is implemented by the activity FinishScreen. In this view, the user is informed
of how many questions they have completed out of the survey’s total question count. They are able
to return to the survey by clicking the ‘back’ button or they can submit their survey to Parse by
clicking the ‘submit’ button. When the user chooses to submit, the activity checks that there is Wi-Fi
access. If there is not, the activity notifies the user with a Toast notification and returns to the home
page without submitting. Otherwise, all of the survey data is sent to Parse, question by question.
Each submission to Parse contains:
Question response
Unix timestamp for the question
Email of the user
Device IMEI
Application version, e.g. ND-WB-SAV-2015-05-05
Question number
Survey version
Application installation ID
After submission, the activity clears out the user responses and timestamps for the current survey in
the database and sets its completion flag as true (denoting that the survey has been completed). A
Toast notification informs the user that the survey has been submitted and the application returns
to the home screen.
Throughout the SurveyScreen and FinishScreen activities, I implemented a self-created method
called the last touch paradigm. As long as the user is actively filling out the survey, it will remain
open even if its active period ends. This prevents the user from filling out most of the survey only to
find that it can no longer be submitted. Every time the user interacts with the application, the
activity checks if the survey has since expired. If it has, the current time is compared to the last time
the user interacted with the survey. If this time is less than five minutes, the ‘lastTouch’ time is
updated to the current time and the survey remains open. Otherwise, the user is notified that the
survey has expired with a Toast notification and the application returns to the main page.
Figure 4: Finish Screen
All survey notification reminders are handled by the NotificationService. This service is triggered
by the alarms set for the surveys earlier in the code. Each active period of a survey gives four
reminders to the user. These reminders are counted by an iteration number. Thus, an alarm will
pass the NotificationService the alarm iteration number, the survey ID and the starting time of the
active period, amongst other information. Before it does anything, the service checks that the
current time is indeed during an active period for the specified survey; an update the night before
could have changed the data. If the time is valid, the service proceeds; otherwise, the service is
ended. If it is the alarm’s first iteration, the active period has just begun and the following steps are
taken:
In preparation for the new active period, the service changes the specified survey’s
completion flag to false.
Iteration one of a new alarm is set for the next time the active period occurs, one week from
the current time.
If the iteration is less than four, meaning it is not the last iteration, the following steps are taken:
The service sets the next iteration of the alarm for the current active period.
The service then creates an Android Notification reminding the user to take the specified
survey. If the user clicks on the notification and the survey is still open, the application will
go to the SurveyScreen activity where the user can complete the survey. Otherwise, the
application goes to the StartScreen activity where a Toast notification informs the user that
the survey is no longer active
If the iteration is four, meaning it is the last iteration, a more intrusive reminder is created. The
service calls ReminderDialog to create an alert dialog pop-up. The user has the choice to either
dismiss the pop-up or go to the specified survey is it is still open.
Figure 5: Survey Notification (left) & Survey Pop-Up Dialog (right)
The intent service UpdateService is called once every day at a random time between 12:00 AM
and 4:00 AM to update the application. Since Parse can only handle 30 requests at one time, all
users of the application cannot update at once, hence the random call times. Before it begins the
update, the service checks that there is Wi-Fi connectivity; without it the update cannot occur. So, if
there is no Wi-Fi, the service exits. Otherwise the SQLite database is cleared in preparation for the
newly imported surveys. Next the service imports all active surveys from Parse, stores them in the
database, and sets alarms for all of them as specified in the discussion of the Wellbeing class. If the
alarm for the UpdateService was created by the StartScreen activity, then after each survey is
imported, the service sends a broadcast to the receiver in StartScreen. Once all anticipated imports
have finished, the start screen can repopulate its ListView with the new surveys.
The OnBoot broadcast receiver is called when the phone has restarted. All pending intents are
lost once a phone shuts down. Thus, the alarms for the survey notifications and the update service
are also lost. To work around this, the OnBoot broadcast receiver is called once the phone boots up.
In OnBoot the alarms for all of the surveys in the database are reset as described previously and an
alarm for the update service is also set. When OnBoot sets the update service alarm, it passes it a
flag ‘FROM_BOOT’ so that the update service does not try to communicate with the receiver in the
StartScreen activity. However, if the application is opened after a reboot but before the update
occurs, the alarm for the update service is reset such that the update service will communicate with
the receiver.
III. Difficulties: Solved & Unsolved
One of the major difficulties I faced while writing this application was integrating the update
service with the rest of the application. Once the update had finished I had to find a way to alert the
start screen so that the ListView could be invalidated and then repopulated with the new surveys.
What complicated the problem further was that all Parse queries are asynchronous. Although these
queries have a ‘done’ method which is called when the query is finished, my code must handle
nested Parse queries. More specifically, inside the ‘done’ method of the query for the
SurveySummary table, I have a for-loop which queries each survey’s data table. As a result, there is
no easy way to definitively say that the Parse queries have finished. Eventually I came to a solution.
After the first Parse query, I know how many surveys I must import. So after each import of a
survey’s data table, I send a broadcast to a receiver in the StartScreen activity along with a Bundle
containing the total number of surveys to be imported. On the StartScreen side, once it receives a
broadcast, the activity increments a variable counting how many broadcasts it has received thus far,
which is equivalent to the number of surveys the update service has imported. The count variable is
then compared to the total number of imports to expect, the number sent with each broadcast. If
they’re equal, then all of the imports have finished and the StartScreen activity can repopulate its
ListView with the updated surveys.
Another difficulty I faced was implementing an alarm system for the survey notifications that
was compatible with the update service. Each time an update occurs, all of the surveys are cleared
from the database and the imported surveys are stored in their place. This means that a survey A
might have an ID of 5 in the database before the update. But once it is reimported during the
update, survey A might be given a new ID of 2. If an alarm for survey A was set before the update, it
thinks that survey A has an ID of 5. For obvious reasons, this complicates matters. So in order for the
alarm system to be compatible with the update service, all previously set alarms must be canceled
when an update occurs. However, this presented a difficult problem. To cancel an alarm in Android,
you must also cancel its pending intent. To do so, you must recreate the same exact pending intent
from the same context and then call ‘cancel’ on the created intent. Unfortunately, I had to cancel
my alarms in a different context from the one in which they were created. I initially planned on
somehow retrieving the same context and using it to both create and cancel the alarms but this fell
through. And there was no way to save a pending intent in a database. To solve my dilemma, I
decided not to cancel my alarms at all. Instead of setting repeating alarms, I set one-time alarms for
the start of every active period. Once these alarms triggered the NotificationService, I set a new
alarm for the current active period for the next week (as well as the next iteration of the alarm). To
make sure that an alarm hadn’t triggered the NotificationService with the wrong ID after an update,
I check that the current time is valid for the survey corresponding to the given ID. This is all
explained above in the implementation section.
Fortunately, there does not seem to be unsolved problems at the moment although there are
definitely areas that could be improved upon. Currently, if there is no Wi-Fi available, the user is
unable to submit a survey. It would be preferable if the application could somehow queue these
surveys and submit them when Wi-Fi did become available. Also, it would be better if a more
straightforward solution could be found for my alarm-canceling dilemma described above.
IV. Continuation of Code: Portability & Readability
In order to make it easier for a future developer to expand on my code, I have logically separated my
code into well-named Java classes. For example, all code for the start screen of the application is
contained in the StartScreen class whereas all code for the survey itself is contained in the
SurveyScreen class. Within these various classes, I’ve also tried to separate the code into as many
functions as possible to make it more modularized. Every class and every function is preceded by a
block comment explaining its functionality and throughout the code I’ve added extensive comments
explaining the implementation. I’ve also tried to give all variables and functions descriptive names to
clarify their purpose. If a future developer has any concerns about my code, I’m happy to answer
their questions.
V. Compatibility
This Android application is compatible with Android 4.1 (Jelly Bean) and higher
VI. Compilation
To compile the project in Android Studio, complete the following steps:
1. Open ‘Android_WellBeing’ as a project in Android Studio 2. A pop-up may tell you that the Gradle project files have changed and ask if you wish to sync
Gradle, choose ‘Sync Now’ 3. Build > Make Project
VII. Further Information
Contact information:
Natalie Sanders
(810) 355-8408
School email : nsander1@nd.edu
Personal email : nsanders0702@gmail.com
GitHub Repository:
https://github.com/nsanders1994/Android_WellBeing_v2