füd5, a restaurant suggestion android application
DESCRIPTION
CSC 413 - Software Development Student Project Documentation San Francisco State University Summer 2015 Credits: Jagatdeep Anand, Alex Brown, Eric Black, Ivan Gonzalez, David Karapetian, Nicu Listana, and Anthony Wong Full source code available here: https://github.com/awong9/t5 Project's presentation slides are available here: http://www.slideshare.net/apebeast/fd5-a-restaurant-suggestion-android-application For more information and distribution permissions, please e-mail Mark Sosnick at: [email protected]TRANSCRIPT
A RESTAURANT SUGGESTION
ANDROID APPLICATION
BY TEAM 5
JAGATDEEP ANAND
ALEX BROWN
ERIC BLACK
IVAN GONZALEZ
DAVID KARAPETIAN
NICU LISTANA
ANTHONY WONG
SAN FRANCISCO STATE UNIVERSITY – CSC 413: SOFTWARE DEVELOPMENT – SUMMER 2015
2
Table of Contents
Section 1: Overview & Process ...................................................................................... 3
Executive Summary ........................................................................................................ 4
Specifications .................................................................................................................. 5
Initial concept .................................................................................................................. 7
Final project flow ............................................................................................................. 9
Weekly Meeting ............................................................................................................ 10
Collaborative Tools ....................................................................................................... 13
Section 2: Front End (User Interface) .......................................................................... 15
Splash Screen Activity .................................................................................................. 16
Main Activity (Search Page) ......................................................................................... 18
Result Page Activity ...................................................................................................... 20
Settings Activity ............................................................................................................. 23
Dev Playground ............................................................................................................ 24
Section 3: Back End ....................................................................................................... 25
Third-Party APIs ............................................................................................................ 26
RestaurantApiWrapper library ...................................................................................... 28
Restaurant Selection Algorithm .................................................................................... 41
Data Storage ................................................................................................................. 44
Utilities ........................................................................................................................... 46
Section 4: Conclusion & Future Goals ........................................................................ 47
Future stretch goals ...................................................................................................... 48
Monetization .................................................................................................................. 50
What We Would Do Differently Next Time ................................................................... 51
Bibliography/Resources ................................................................................................ 52
Section 5: Presentation Slides ..................................................................................... 53
SECTION 1: OVERVIEW & PROCESS
4
Executive Summary
Füd5 is an Android application that helps users find nearby restaurants. The
application has a simple interface that allows users to get a nearby restaurant
recommendation by pressing the "Füd Plz" button. The application displays the name of
the restaurant and an image provided by Yelp, and a Google map showing where the
restaurant is. The user can then accept or reject the recommendation. If the user
accepts the recommendation, the application provides directions to the restaurant. If the
user rejects the recommendation, the application will recommend another restaurant.
There are two types of rejections: "maybe later..." and "always ignore." The "maybe
later" button will hide the restaurant during this current search, and will display it in
future searches after a certain time period. The "always ignore" button will put the
restaurant in a rejection list which will prevent it from ever being a result in future
searches. The application remembers the choices made and will adjust its future
recommendations based on those choices. The app was designed to have an easy to
use interface which allows a user to go from start to finish using only their thumb.
The application was developed over the course of ten weeks by a team of seven
students. Each week the team met on Wednesday to discuss the project. In the
beginning the meetings were mostly brainstorming sessions to decide how the
application should look and feel. One of our use cases was “You are a hungry tourist in
a foreign country.” This meant designing an easy to use application that would display
results with a single tap. As the application took form towards the end, everyone
focused on technical issues such as how the result algorithm should be implemented or
fixing bugs. This made scrums very useful to avoid duplicate work or code conflicts as
everyone worked off the same codebase.
In the end, we believe we have produced an application that is intuitive and easy
to use, and in the process of development learned a great deal about Android
programming as well as collaborative tools and teamwork methodology for software
development.
5
Specifications
Our original specs are shown below. Items 1 through 6 and 14 through 22 were
set by Professor Sosnick. The rest were set by the team. Professor Sosnick’s notes are
visible on the spec sheet.
Figure 1 - Pre-development specification sheet with annotations by Prof. Sosnick
To fulfill item 6, we chose the following special features:
1. the ability to pull up restaurant menus with the OpenMenu API, and
2. the algorithm we would use to sort our recommended restaurants.
Unfortunately, OpenMenu did not grant our group a key to use their API. Luckily, we
were able to use the Locu API instead. This provided the added benefit of allowing us to
access and display a restaurant’s open hours.
6
Our final specifications evolved into the following:
Figure 2 – Specifications reflected in the final build
Item 9 was amended when during Milestone 1 when it became clear the UI allowed the
main functionality to be controlled on the bottom of the screen -- reachable with the
thumb.
7
Initial concept
Figure 3 and Figure 4 show early sketches made by the members of the team
before we collaborated on wireframe design and discussed special features. They show
the entire application lifecycle. Nicu and Eric both independently developed the idea
keeping track of the user’s opinion of different restaurants, and it was agreed that this
idea should be implemented. This was further refined into the Restaurant Selection
Algorithm (pg. 41) which could provide a ranked list of results based on the user’s
opinion of previously visited restaurants. This, in turn, informed the idea that, after the
user searches for restaurants, we should show only one result at a time.
Figure 3 - Pre-development sketch of application flow by Nicu
8
Figure 4 – Pre-development sketch of application flow by Eric
Compare these early sketches with Figure 5 on the next page; the end result
ended up aligning closely with our early concept. We felt that even if extra features were
incorporated into the application, we should make sure the base functionality of the
application is as simple to use as possible, without adding needless extra activities. In
the end, the application’s main functionality is contained within only two activities: The
Search Page and the Result Page.
9
Final project flow
Figure 5 – Diagram of final project flow
10
Weekly Meeting
This section lists the dates by which certain tasks were scheduled to be
completed, and what date they were actually completed. This portion also contains the
breakdown of the team's weekly scrum meetings, the feedback we received from
milestones we achieved, and the changes that we made:
6/17/15
Tasks completed
● We created our first “hello world” application on Android Studio.
● Researched Google and Yelp locations APIs.
TODO
● Familiarize ourselves with Github so that later on in the semester, things would
go smoothly.
6/24/15
Tasks completed
Researched SQLite,Yelp API and Location services
● Formalized each member’s role.
TODO
● Begin working on Google Maps API.
● Begin working on Location API and access/sort data.
● Focus on Wire frame design.
● Begin working on EULA (about page).
7/1/15
Tasks completed
● Tested sql for front end
● Completed Yelp API wrapper, uploaded test code to find user location.
● tested Yelp wrapper.
● Worked on Splash screen.
● Obtained Yelp key, searched for health inspection API.
● Began building class and interface for Google Maps API.
11
TODO
Begin implementing selector class which depends on database
● Try to get an activity to work with Google Maps API.
● Implement Splash Screen.
7/15/15
Tasks completed
Displaying Maps on result page activity.
● format the layout in order to display the map properly.
● Adjusted the splash screen to properly handle location services
● Displayed the End-User License Agreement.
● Merged Restaurant Test into GUI.
● Created a working "Red List".
● Added results page.
TODO
● Implement images of restaurants on results page.
● Applying food preference setting.
7/22/15
Tasks completed
● We designed the result page and the menu bar icons.
● There was some debugging for the GUI plus provided functionality to some
buttons.
● Added buttons to the result page of the application.
● We were able to load images in the main activity.
● Completed the End-User License Agreement component.
● Added markers to google maps and got the maps to function properly.
TODO
● Modify GUI to make it more user-friendly
● Integrate Locu Menu Dialog with the main activity.
● Preload the restaurant images and the rating images as well.
● Complete “About Application” portion
● Obtain Google Maps API attribution and display information
● Put finishing touches on selector and work on Google Maps.
12
7/29/15
Tasks completed
● Began working on the GUI portion of the application and “Let’s Go” button.
● Began to implement the results page
● Created Dialogues (Menu etc...)
● Optimization of front end and back end.
● Started working on the selector class.
● Got the image to pre-load
● Began working on documentation and fixing bugs.
● Implemented Google Maps API attribution
TODO
● Finish implementing the Google Maps API attribution.
● Debug and begin working on Documentation.
● Polish graphics icon
● Integration of Uber (a method of transporting user to restaurant)
● Attempt to integrate a Health Inspection API (short description)
● Maybe integrate some sort of animation.
13
Collaborative Tools
Slack
Slack1 was our primary mode of communication beyond direct communication
before and after class and the weekly meetings. We chose it over e-mail, text
messaging, or other means of communication because it offers group messaging that it
1) is direct and organized, 2) cross-platform compatible, and 3) allows integration with
the other services used during development. Essentially, Slack provides the immediacy
of text messaging if the Slack application is installed on each team members’ phones,
but can be selectively silenced. Initially, we posted group messages under the default
#general channel. As time progressed and we needed to focus conversations around
particular topics, we created channels specifically for front-end and back-end
development, resources, bugs and other issues, and documentation. If anyone in the
group needed to get in touch with multiple team members, direct messages or private
groups were available. We found creating a #commits channel to be particularly helpful,
as team members could coordinate when to push changes without creating
unnecessary merges or conflicts.
Google Drive
We used Google Drive to host and share content, including project specifications,
wireframe images, etc. In addition, we used shared documents to track the project
backlog and collaborate on this document and the final in-class presentation. We find
Google Drive to be a great tool for quick collaboration.
Microsoft Word
This document was finalized using a group-editable Microsoft Word document
hosted on OneDrive. A link was shared on Slack with the whole team so any team
member could make quick edits in Word Online, or more robust formatting edits on a
full-featured local copy of Word.
1 Slack: https://slack.com/
14
Git & GitHub
While we were required to use Git/GitHub for this project, we believe that we
utilized GitHub to its full potential. Though Git has a bit of a learning curve, by the end of
the semester we had become proficient with committing and pushing changes from
local to remote branches, adding and deleting new branches, and merging branches
together. In the first 6 weeks we opted to work individually in separate branches and
merge our progress into a production branch that contained only working code. As a
milestone was reached, we merged the production branch into the master branch. While
this created some issues in terms of merging cleanly, our experience with multiple
branches informed our final work on the project. During the last 4 weeks we worked off
a single branch, adjusting the root gitignore file so Android Studio local settings in the
.idea folder were not needlessly updated. In one of our weekly meetings, we discussed
best practices for using Android Studio with GitHub. While learning the command line
was helpful in becoming familiar with practices and terminology, and is undoubtedly
important for other projects where the development environment may not have such
tight GitHub integration, we find that it is definitely possible for 7 people to collaborate
on a single remote branch of a repository hosted on GitHub within Android Studio
without ever using the Git command line.
As for GitHub, we found several added features to be quite useful. For example,
the Wiki was helpful in that we were able to link to various resources, including our
whiteboard sketches, program specs, a task backlog, and API terms of service and
attribution requirements. In addition, we wrote extensive notes on various aspects of the
project, including the various activities, APIs, and libraries used. In addition to being
useful as a resource for everyone during the development process, these notes were
helpful as a starting point for this document.
Another useful feature was the issue tracker. Its helpfulness was not so apparent
until the later stages of development when the application was almost-fully
implemented. At that point, a new issue could be opened for a bug, wherein a team
member would describe the scope of the bug and how it could be duplicated. In turn, a
team member with the necessary time and knowledge to resolve the issue would
provide a fix. By including the issue number in the commit message (e.g. “issue #3”) the
commit could be marked as fixed. Finally, after verifying that the issue was fixed, the
issue could be closed.
SECTION 2: FRONT END (USER INTERFACE)
16
Splash Screen Activity
Sketches and Screenshots
Description
The splash screen is an activity which displays when the application is loading
from the first time or from an inactive state. It displays the application’s logo and allows
us to take care of several “housekeeping” tasks before launching the Main Activity.
First, the activity displays the logo for 3 seconds. The screen is empty other than
the FuD5 logo and acts as the entry point for the application if it was not already running
or its state was cleared from memory by the Android OS due to inactivity. During the
wait time, any restaurants in the database yellow list with an addition timestamp longer
Figure 7 - Screenshot of final version's Splash
Screen on Nexus 7
Figure 6 - Pre-development wireframe sketches of
Splash Screen and Result Page
17
than 1 week are deleted. (See Restaurant Selection Algorithm for a description of the
green, yellow, and red lists.)
After the 3 second pause, the activity checks whether the user has agreed to the
End-User License Agreement. If this is the first time the user has run the program, the
answer will be no, and the End-User License Agreement dialog is displayed. The dialog
cannot be cancelled and the user must press "I agree" to proceed. If this is not the first
time the user has run the application and they previously agreed to the terms of service,
the End-User License Agreement dialog is not displayed.
Finally, the activity checks whether location services have been enabled; they
are considered enabled if either GPS or network location services are active. If neither
are not enabled, the activity opens the "Use Google Location services?" dialog. If user
presses "Yes plz" they are directed to Android Location Settings; after user turns on
location services, if they press the "Back" button or otherwise return to the application,
they are taken to the main activity. If the user presses "Maybe later" they are taken to
the main activity.
Figure 9 – End User License Agreement dialog displayed on HTC One M7 (5”)
Figure 8 – Application asks user if he/she wishes to turn on Location Services. Displayed on HTC One M7 (5”).
18
Main Activity (Search Page)
Sketches and Screenshots
Description
The MainActivity was conceived with the idea that the user can simply press the
“Fuz Plz” button to find a great nearby restaurant to try today. As such, the button is
conveniently placed at the bottom of the screen within easy reach of the thumb. By
default, if Android Location Services is on, the application will determine the device’s
current location and use that as the center point of the search.
Figure 10 - Pre-development wireframe
sketch of Main Activity
Figure 11 - Screenshot of Main Activity at Milestone 1, with
annotations by Prof. Sosnick
19
The device’s location,
approximated as an address, is
shown as one of the fields at the top
of the activity. By filling out or
changing these fields, the user can
answer the question, “What would you
like to eat?” These fields are:
Choose Different Location:
By pressing the edit text field, the user
can type their own city or address to
use as a central point. Or, the user
can press the crosshair button to
again retrieve the device’s current
location (say, if they have moved
since loading the activity).
Search Terms: While optional,
entering in additional search terms will
help refine the results. For example,
one could enter “vegetarian” to restrict
the results to restaurants featuring
vegetarian cuisine.
Max Radius: A default
maximum radius of 0.5 miles was
chosen because the results would
consist of restaurants within easy
walking distance. Pressing on the
distance text allows the user to
choose a radius of up to 10 miles from
a pulldown menu.
Minimum Rating: Selecting a
rating from 0 to 5 stars in half-star increments allows the user to filter out results which
fall below the minimum rating. By default, this is set at 3½ stars.
Figure 12 - Screenshot of final version's Main Activity
on an HTC One M7 (5”)
20
Result Page Activity
Sketches and Screenshots
Early on in the application’s
development process, we
considered showing a handful of
restaurants and their relevant info
on the Result screen pages, as
shown in the pre-development
sketch. However, we eventually
decided on displaying one
restaurant at a time, in keeping
with the idea that after our
restaurant selection algorithm was
applied to a list of potential
Figure 14 - Pre-development wireframe sketch of
the Result Page Activity
Figure 13 - Screenshot of Result
Page at Milestone 1
Figure 15- Single-result Result Page concept
21
restaurants, the first result would be a good potential match. While implementing this
design, it became quickly apparent how limited space was on the screen. If we
constructed a UI exactly like the one in this mockup, screen real estate would have
been limited. We would not be able to present information about restaurants in a robust
way so as to make them seem instantly appealing.
Additionally, Anthony suggested we could save some space if we overlap the
restaurant name and star rating over the image, and to zoom the image so it wouldn’t
change size when the aspect ratio changed. Although the “Don’t Show” button is shown
in the picture above to be changed to “No Way!” we opted for another of Prof. Sosnick’s
suggestions, “Always Ignore.”
Description
After ResultPageActivity has an
ordered list of restaurants, it takes the
first one off the list and sets up a
Google Map with a pin at that
restaurant’s location, along with the
restaurant’s Yelp star rating. Then, it
displays the restaurant’s name. If the
name is too long to display fully, the font
is made smaller. If the restaurant has an
associated image, it will be loaded and
displayed.
If the next restaurant on the list
has an image, it will be also be
preloaded into memory. This means
that if the user rejects the first
restaurant, ResultActivity doesn’t have
wait to load that image; it can just grab it
from memory.
Figure 16 - Screenshot of final version's Result Page on
HTC One M7 (5”)
22
Dialogs
The Result Page Activity title bar contains two
buttons which open dialogs: one to display restaurant
menus and one to display more information about the
restaurant. The restaurant menus information and
open hours are obtained from Locu, if a venue which
corresponds to the current restaurant is found and
Locu provides that information. In the case of the
restaurant menus button, if menus are unavailable,
an alert dialog informing the user that menus were
not found is shown in lieu of menu data. In the case
of the more info dialog, the open hours of the
restaurant obtained from Locu is shown in addition to
additional information such as categories and
restaurant phone number obtained from Yelp.
For the purpose of optimization, we opted not
to perform Locu API queries as a batch process for
every single restaurant in the list of restaurants
returned by Yelp. This would necessitate up to 40
separate calls: one to
find a potential Locu
venue match for a
given restaurant and
another to load detailed data such as complete menus
for the matching venue, for each of up to 20 restaurants
returned by the Yelp API query. Rather, we perform on
demand the two queries necessary to find and evaluate
a match, which happens when one of the two title bar
buttons is pressed for the first time for an individual
restaurant. We prevent a duplicate query from being
made, thus saving API calls. The main drawback to this
technique in terms of user experience is that there may
be a delay of a few seconds in the case a large
restaurant menu must be parsed and written to an object
prior to rendering the dialog. If the query had been
performed in advance, there would not be a significant
delay. Even so, we found the tradeoff to be worthwhile,
as Locu only provides a limited number of free-of-charge
API calls per day—1,000, as of this writing, which could
easily be expended with as little as 25 searches.
Figure 17 – More Info dialog on HTC
One M7 (5”)
Figure 18 – Restaurant Menu dialog
on HTC One M7 (5”)
23
Settings Activity
The Settings Activity allows the user to configure and access information about the
application. Figure 20 describes the function of each settings component.
Figure 19 - Screenshot of final version's Settings Page on HTC One M7 (5”)
Settings Activity Components
Component Purpose
About this app Lists application name, version, authors, and relevant copyright and licenses.
End User License
Agreement
Explains the terms of use. This dialog is identical to the one displayed to the user the first time the application launches.
Clearing history
Allows the user to view and remove ignored restaurants, clear user settings, and clear either the ignored restaurant history (red list) or all restaurant history (green, yellow, and red lists).
Pressing the icon opens a tooltip that explains the purpose of each Clear History checkbox item so the user understands what data is being cleared.
Manage services
Takes user to Android Location Settings to turn location services on or off.
Figure 20 - Components
24
Dev Playground
The Dev Playground, included below the settings, contains several test activities
and featured which we opted not to include in the final version of the application. This
portion of the Settings Page will be removed in the event that the application is released
to the public.
The components of the Dev Playground were useful in demonstrating
functionality of backend code and to debug the application by compartmentalizing
certain features/ Much of the work on the Locu menu test activity transferred over to the
menu button on the results page, and the database test activity aided in fixing bugs
where the wrong result was added to user preferences, or was not added at all.
In addition, several features which were considered for the final application were
omitted, namely the “Let’s Go” Follow-up Interval and the Default Settings activity.
Dev Playground Components
Component Purpose
Restaurant Selector Demo Allowed us to test our Restaurant Selection Algorithm by showing, step by step, how a list of restaurants obtained from Yelp is refined to prioritize the displayed results.
Database Test Allowed us to display the contents of the green, yellow, and red lists as well as add and remove test entries.
Image Test Performs a Yelp API query and displays the dimensions of the images provided for each restaurant. This allowed us to determine that we needed to resize/crop images for display.
Locu Test Displays menus for 4 preset restaurants or allows the user to pull up a restaurant menu by Locu ID. A Locu API query is performed to obtain the menu information.
Google Map Test Simply displays a pin on a map.
Display Shared Preferences Displays the contents of UserSettings.xml, which are the Android Shared Preferences variables used throughout the application.
Figure 21 – Dev Playground Components
SECTION 3: BACK END
26
Third-Party APIs
This section details our use of third-party application programming interfaces
(APIs) to obtain information to display to the user based on his/her preferences. The
implementations of both Yelp and Locu API queries are contained within the library
RestaurantApiWrapper, which is described in detail in the following section.
Yelp
Yelp is a consumer-facing web-based service that allows users to find
information about business venues and write reviews. Businesses can register with Yelp
to update their listing(s) and respond to consumer reviews. We perform a Yelp API
query to obtain information about restaurants filtered by the user’s choice of location
(either the device’s current location or custom input), a maximum search radius, a
minimum Yelp-star rating, and optional search terms. Information displayed to the user
consists of the restaurant name, address, distance from search location, Yelp
categories, and business phone number. Internally, we use Yelp’s unique per-venue ID
strings to record the user’s history of liked and disliked restaurants in the database, and
use the provided latitude and longitude to display restaurant results on a Google Map
and provide directions. In addition, we use the URL provided by Yelp to download and
display an image for each restaurant displayed.
Locu
While Yelp’s website provides users with open hours and menus for many
restaurants, this information is not available through the Yelp API, so we opted to
supplement our information by using the Locu API. Locu is a developer and business-
owner facing web-based service that allows application and website developers to
obtain information about businesses worldwide, or facilitate business owners updating
their own information in Locu’s database. When the user requests additional information
about a restaurant, we use Locu API queries to find a likely match to the corresponding
Yelp venue and parse restaurant menus and open hours, if either are provided.
Google Play Services
We used two components of Google’s Play Services API framework: The
Location API and the Maps API.
Location API
The Location API was used to determine the device’s most recent location
(effectively, the user’s current location) as a latitude and longitude coordinate, and
approximate the coordinate as an address string which is used as a parameter in the
27
Yelp API query. To accomplish this, we set the application permission
ACCESS_FINE_LOCATION in the Android Manifest, and included Google Play
Services Location API as a compile dependency. During execution, we create an
instance of the GoogleApiClient class to act as an interface between the application and
Play Services. Once a connection to the service is established, we first check that a
network connection is available and that Location Services is active in order to prevent
a runtime exception. Then, we call Location Services to obtain a Location object
containing the device’s latitude and longitude. Following this, we use the Geocoder
class to produce a list of addresses that approximately match the Location. After testing,
we found that the first address in the list is invariably accurate enough to pass as a
parameter to Yelp, so that is the address we use. The address is compiled into a single-
line String which is placed in the Location EditText field of the Main Activity. (If, for some
reason, an address cannot be approximated, the address string is empty. When the
user presses the “Fud Plz” button to obtain search results, they are prompted to
manually enter in an address.)
Though integrating use of the Location API into the application did not require a
large amount of code, we found that the need to check network and Location Services
connectivity prior to all operations involving the API presented some challenges during
early debugging.
Maps API
The Maps API was used to display a map in the results page activity with a pin
on the location of the currently displayed restaurant with accompanying business name
and address. The map is displayed is contained within a GoogleMap fragment in the
Result Page activity’s layout file. Use of the API is very straightforward: as soon as the
results of the Yelp API query are returned from RestaurantApiClient to the activity, the
GoogleMap view is set to center on the latitude and longitude of the first restaurant,
where we place a red marker which includes a snippet displaying the restaurant’s name
and address. The map is set to zoom level 14, which we found provided the best
overview of the general area, as the map shows nearby neighborhoods and major
streets. If the user is close enough to the location of the marker, the device’s location is
shown as a blue dot. The map is interactive and can be scrolled by pressing and
moving a finger across the screen. If the user wishes, s/he may center on the device’s
location by pressing the map’s crosshair icon, or open the Maps application to see a
full-screen view of the map. At the point where a user chooses to see the next
restaurant result and the Result Page is updated, the map is recreated and centered
about a new marker.
28
RestaurantApiWrapper library
Overview
We created a RestaurantApiWrapper library to provide methods and classes
necessary to perform Yelp and Locu API queries and access the resultant data. It exists
as a separate module which is a compilation dependency of the main application
module. Separating the library from the rest of the project provides the following
benefits:
1. The APIs used to obtain information about restaurants are abstracted away from
the rest of the project. In the case that an API is no longer utilized, methods used
to perform API queries and access data need not change, only the underlying
implementation. The API key values represent the only API-specific information
that needs to be passed to the wrapper.
2. The implementation of the API queries and the method by which data is parsed
and stored is hidden from the client. The client in this case is anyone working on
the application’s front-end code.
3. The RestaurantApiWrapper library can be used with other Android applications
as a module or a *.jar archive without the need to modify any code.
The diagram on the following page summarizes the relationship between classes in the
library and how they interact with the application.
29
Figure 22 - Relationship between RestaurantApiWrapper components and calling Activity
30
Implementation
The class RestaurantApiClient is the interface between the
RestaurantApiWrapper library and the application module. An activity in the application
can use RestaurantApiClient.Builder to instantiate a new RestaurantApiClient and
specify search type and parameters. The instance is called in a background task in
order to perform the query and return the results stored in an object of the appropriate
data type, either a Restaurant or RestaurantList. The activity can then use the object in
the foreground to display the results. Two types of searches are available: Search and
Business Search, described below. In addition, the MenuAndHoursExtension class
can be used statically to update a Restaurant object in place to supplement data
obtained through RestaurantApiClient (Yelp, in our implementation) with data from a
secondary API (Locu).
The Scribe library2 was used to facilitate OAuth requests to both the Yelp and
Locu APIs. RestaurantApiClient performs a token-signed OAuth GET request to the
Yelp API. The MenuAndHoursExtension, by contrast, uses a simple HTTP GET request
to the Locu API with the API key included in the request URL.
Both APIs returned results in the form of a JSON String which required parsing to
extract data. Fortunately, both Yelp and Locu provided documentation which described
the expected format of the results, including what data type would be appropriate to
store each field, and provided API consoles which provided some limited testing
functionality prior to implementing our parsing algorithms. After examining different
methods to interpret JSON strings, we opted to use Google’s built-in classes
JSONObject and JSONArray to manually parse the results. When parsing a JSON
object, we obtained an array of field names (keys) for a given hierarchy level using
JSONObject’s names() method, then passed each field name to a switch statement,
where each case corresponded to a field value we wished to parse. Each case
statement then read the field value as its expected data type. We found that this offered
better performance than using JSONObject’s has(fieldName) method, which must
iterate over the field names in a given level to find a matching name.
2 Scribe OAuth Java library: https://github.com/fernandezpablo85/scribe-java
31
Usage
The RestaurantApiWrapper module has Javadoc documentation which can be
found at the permalink: http://bitacoustic.net/raw_javadoc/
Key
Since the wrapper is abstracted away from the application as a dependency, it
does not have direct access to strings containing the Yelp API key, key secret, token, or
token secret, which are required to perform the OAuth query. To this end, we can
instantiate a YelpApiKey object, which contains these values, to pass as the required
parameter of the Builder. In our implementation, these key values are stored in a
resource file within module app.
public class ResultPageActivity extends AppCompatActivity
implements MenuNotFoundDialogFragment.MenuNotFoundDialogListener { YelpApiKey mYelpKey;
@Override
protected void onCreate(Bundle savedInstanceState) { ...
mYelpKey = new YelpApiKey( getApplicationContext().getResources().getString(R.string.yelp_consumer_key),
getApplicationContext().getResources().getString(R.string.yelp_consumer_secret), getApplicationContext().getResources().getString(R.string.yelp_token),
getApplicationContext().getResources().getString(R.string.yelp_token_secret) ); ...
} }
Figure 23 - Instantiating a YelpApiKey object to pass to RestaurantApiClient
Business Search
Using business search, we can obtain a Restaurant object which contains useful
information about a specific restaurant. Aside from the API key, the only required
parameter is a unique Yelp ID. To obtain a Restaurant object, we instantiate a new
RestaurantApiClient object using the Builder and call the method getRestaurant() in a
background task:
32
private class RestaurantTestActivity extends Activity { ... protected Restaurant mRestaurant; ... @Override protected void onCreate(Bundle savedInstanceState) { ... String id = "new-tsing-tao-restaurant-san-francisco"; new RestaurantAsyncTask.execute(id); } private class RestaurantAsyncTask extends AsyncTask<String, Void, Restaurant> { @Override protected Restaurant doInBackground(String... params) { Restaurant response = null; String passedId = params[0]; try { response = new RestaurantApiClient.Builder(mYelpApiKey) .id(passedId) .build().getRestaurant(); } catch (JSONException | IOException e) { // handle JSON parsing exceptions } return response; } protected void onPostExecute(Restaurant response) { mRestaurant = response; } } ... }
Figure 24 - Obtaining a Restaurant object with RestaurantApiClient
The Restaurant object is now available for use elsewhere in the calling activity. It should
be noted that the RestaurantApiClient can throw an exception due to a JSON parsing
error; this can act as a flag to try the request again or perform some other operation to
recover gracefully from an error.
Business Search functionality was not utilized in the release version of the
application but a demonstration of its use can be seen in the RestaurantTestActivity in
app/src/java/tests.
Search
Using search, we can obtain a RestaurantList object which contains restaurants
near a specified location, sorted in order by shortest distance, highest rating, or best
match to optional search terms. A number of other optional parameters are available to
refine the search, as specified in the Javadoc. To obtain a RestaurantList object, we
follow the same methodology as for a Business Search but use the RestaurantApiClient
method getRestaurantList().
The following code is from the actual application and shows a network
connectivity check before the call is made. This is necessary to ensure the application
does not crash. Also, since the AsyncTask (background task) is available as an Activity-
33
scope variable, we can check elsewhere that the task has been completed before
performing other functions which are dependent on the data in the RestaurantList:
public class ResultPageActivity extends AppCompatActivity implements MenuNotFoundDialogFragment.MenuNotFoundDialogListener { GetResultTask getResultTask; RestaurantList mResultList; // user input parameters passed as extras from calling Activity String location, searchTerm; int maxRadius; double minRating; ... @Override protected void onCreate(Bundle savedInstanceState) { // ... store extras from calling Activity ... // start background activity to get the results if (ServiceUtil.isNetworkAvailable(this)) { getResultTask = new GetResultTask(); getResultTask.execute(); // display a "Getting info..." PopupWindow } else { // Close the results page immediately if there is no network connectivity; to // the user it will appear as if the results page never opened ToastUtil.showShortToast(this, getString(R.string.toast_network_unavailable)); finish(); } } private class GetResultTask extends AsyncTask<String, Void, RestaurantList> { @Override protected RestaurantList doInBackground(String... params) { try { return new RestaurantApiClient.Builder(mYelpKey) .location(location) .sort(RestaurantApiClient.SortBy.BEST_MATCH) .term(searchTerm) .radiusFilter(maxRadius) .build().getRestaurantList(); } catch (Exception e) { e.printStackTrace(); return null; } } protected void onPostExecute(RestaurantList result) { mResultList = result; // close the PopupWindow ... } } }
Figure 25 - Obtaining a RestaurantList object from RestaurantApiClient
Note that, as with the Business Search, RestaurantApiClient can return a number of
exceptions, namely JSONException and IOException during JSON parsing, as well as
ParameterRangeException due to a search parameter outside of its allowable range.
The possibility of a ParameterRangeException should entice those calling
RestaurantApiClient to perform a sanity check on the values of user-supplied
parameters. Examples of possible out-of-range parameters include an integral sort
34
order value other than the three available in the Yelp API (0 for best match, 1 for
distance, and 2 for highest-rated), or a search radius greater than 40,000 meters.
MenuAndHoursExtension
MenuAndHoursExtension accepts a Restaurant object populated with data from
the primary restaurant API (Yelp) and queries a secondary API (Locu) database for a
match. It has several methods which may be called statically to update the Restaurant
object in places with any available additional information.
getId() / getIdIfHasMenu(): Attempts to get the Locu ID for the given Restaurant,
using the Restaurant's geolocation, first word of the business name, street
address, and postal code.
updateFromMatchedId(): Parse information about a Locu venue and add it to
the Restaurant: open hours and menus, if either are available.
update() / updateIfHasMenu(): Attempts to match the specified Restaurant to a
venue in Locu's database, extracting menus and open hours for the restaurant if
either are available. These methods are essentially a combination of the two
methods above.
Similarly to RestaurantApiClient, the secondary API (Locu) key must be passed to
MenuAndHoursExtension. Then, the extension may be called from within a background
task initiated by the calling activity. The following example shows the extension being
used to query Locu for a match to a Yelp-populated Restaurant and display a dialog
containing the restaurant menus, if available:
public class LocuMenuTestActivity extends Activity implements MenuNotFoundFragment.MenuNotFoundDialogListener { Restaurant mRestaurant; ... private class DisplayMenuTask extends AsyncTask<String, Void, Restaurant> { @Override protected Restaurant doInBackground(String... params) { mRestaurant = new Restaurant(); // contains only default parameters MenuAndHoursExtension menuExtension = new MenuAndHoursExtension (mLocuKey); menuExtension.updateFromMatchedId(mRestaurant, params[0]); return mRestaurant; }
35
@Override protected void onPostExecute(Restaurant restaurant) { // show menu dialog or handle menus not found if (mRestaurant.hasMenus()) { mTxtDebug.setText("Loading menu..."); DialogFragment displayRestaurantMenus = DisplayRestaurantMenusFragment.getInstance(restaurant); displayRestaurantMenus.show(getFragmentManager(), "menus"); } else { mMenuNotFoundDialog = new MenuNotFoundDialogFragment(); menuNotFoundDialog.show(getFragmentManager(), "menuNotFound"); } ... } } ... }
Figure 26 – updating a Restaurant with information from Locu via the MenuAndHoursExtension
If a match was found, any of the following data may become available for access, in
addition to any data already obtained from Yelp: the restaurant’s open hours, menus
(e.g. the breakfast, lunch, and dinner menus), Facebook URL, and Twitter ID.
Data Types
Restaurant
The Restaurant class is a data type holding information about a single business
parsed from both the Yelp and Locu APIs. While an “empty” Restaurant can be
instantiated, it is not particularly useful unless it has been received as the result of an
API call through RestaurantApiClient. In this case, it is populated with data from Yelp
which can be obtained through a number of getter (accessor) methods which return the
data in a format appropriate to a given field. For example, a business name is returned
as a String, a website URL or phone number is returned as a Uri object that can be
passed to an Intent, a business address is returned as an Address object from the
Google API, and so forth. No setter (mutator) methods are provided. This ensures the
application’s contract with Yelp: we can be sure the data in a Restaurant comes from
the API, and not another source. Additional data members are provided for use by the
MenuAndHoursExtension, which updates the Restaurant with information obtained by
the Locu API. Here are some usage examples:
36
String name = mRestaurant.getBusinessName(); if (mRestaurant.hasImageUrl()) { displayImage(mRestaurant.getImageUrl()); } // dial the business's phone number Intent intent = new Intent(Intent.ACTION_DIAL); intent.setData(mRestaurant.getPhoneDialable()); startActivity(intent);
Figure 27 - Restaurant object getter examples
For abstraction purposes, it is not necessary for the user to know which API was
used to obtained the information so much as know what information is available. To this
end, primitive data types are initialized consistently such that they are empty, in the
case of a String; have a negative value, in the case of a number; or are null, in the case
of an object. If a data member was not populated from the result of an API query, it
retains its initialized state. In a number of cases, methods which return booleans are
provided to evaluate whether information is available, such as hasMenus(),
hasCategories(), etc.
Certain information obtained from Yelp and Locu exhibits complexity such that
custom data types were created for ease of access. Such include the YelpCategory,
which is a pairing of display name and Yelp internal identifier; OpenHours, which
contains an ArrayList of open hours ranges for each day of the week which can be
rendered in 12-hour or 24-hour format; and Menus, an object which can hold multiple
restaurant menus (e.g. breakfast, lunch, and dinner) containing sections, subsections,
subsection text, item descriptions and prices, and so forth.
Currently, most data returned in a Yelp API query about an individual business is
parsed and stored in the Restaurant object, with the most notable exceptions being gift
certificate availability and details, as well as details of special deals offered through
Yelp.
RestaurantList
A RestaurantList contains two components: a list of Restaurant objects (as
described above) and a MapBounds object. MapBounds provides a suggested center
and height and width for a map encompassing all the restaurants on the list. Yelp
returns this information for all searches producing one or more restaurants as a result.
As an extension of this, RestaurantList automatically recalculates the values in the
MapBounds object when a Restaurant is manually added to the list by the user.
RestaurantList implements the majority of methods from ArrayList to allow
addition and removal of Restaurants. These are some examples where familiar
methods are used:
37
mRestaurant = mRestaurantList.get(1); mRestaurantList.add(mRestaurant); mRestaurantList.add(1, mRestaurant); int listSize = mRestaurantList.size(); Iterator it = mRestaurantList.iterator(); mRestaurantList.removeAll(someCollection); Boolean hasRestaurant = mRestaurantList.hasRestaurant(mRestaurant);
Figure 28 - RestaurantList getter and setter examples
Here are some examples of methods created specifically to accommodate locating and
reordering restaurants in the list:
// search list for restaurant having specified Yelp ID Boolean hasRestaurant = mRestaurantList .hasRestaurant(("new-tsing-tao-restaurant-san-francisco"); // demote a Restaurant 2 places in the list mRestaurantList.demote("some-restaurant-id", 2); mRestaurantList.demote(someRestaurant, 2); // promote a Restaurant 2 places in the list mRestaurantList.promote("some-restaurant-id", 2); mRestaurantList.promote(someRestaurant, 2);
Figure 29 - RestaurantList matching and reordering method examples
Serialization of Data
RestaurantList and Restaurant objects are serializable. While as of this writing
Yelp generally prohibits users from caching restaurant data3, serialization may be useful
to pass the objects from an Activity to a Dialog, a Dialog to an Activity, Activity to
another Activity, etc. Here is an example where serialization is used to pass a
Restaurant from an Activity to a DialogFragment. The relevant code in the
DialogFragment is:
public class DisplayRestaurantMenusFragment extends DialogFragment { Restaurant mRestaurant; // the restaurant for which to display the menu ... public static DisplayRestaurantMenusFragment newInstance(Restaurant r) { DisplayRestaurantMenusFragment f = new DisplayRestaurantMenusFragment(); Bundle args = new Bundle(); args.putSerializable("restaurant", r); f.setArguments(args); return f; }
3 Yelp API Terms of Use: https://www.yelp.com/developers/api_terms
38
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // load the restaurant argument mRestaurant = (Restaurant) getArguments().getSerializable("restaurant"); } }
Figure 30 - Serialization of data between Activity and DialogFragment – DialogFragment code
The relevant code in the calling Activity is: public class LocuMenuTestActivity extends Activity implements MenuNotFoundFragment.MenuNotFoundDialogListener { Restaurant mRestaurant; ... private class DisplayMenuTask extends AsyncTask<String, Void, Restaurant> { @Override protected Restaurant doInBackground(String... params) { mRestaurant = new Restaurant(); // contains only default parameters LocuExtension locu = new LocuExtension(mLocuKey); locu.updateFromMatchedLocuId(mRestaurant, params[0]); return mRestaurant; } @Override protected void onPostExecute(Restaurant restaurant) { // show menu dialog or handle menus not found if (mRestaurant.hasLocuMenus()) { mTxtDebug.setText("Loading menu..."); // create an instance of DisplayRestaurantMenusFragment with // the restaurant result of doInBackground() as an argument DialogFragment displayRestaurantMenus = DisplayRestaurantMenusFragment.newInstance(restaurant); displayRestaurantMenus.show(getFragmentManager(), "menus"); } else { // handle menu not found } ... } } }
Figure 31- Serialization of data between Activity and DialogFragment – Activity code
Ideas for Refinement
Better matching of Yelp listings to their Locu counterparts
As described previously, the MenuAndHoursExtension attempts to find a Locu
venue by using the first word of the restaurant name as well as the restaurant’s street
address, postal code, and latitude and longitude in a Locu API search query; in addition,
we assume that the first venue in a non-empty result list is the match. This produces
correct matches often enough for the feature to be viable, even if the availability of a
corresponding match in Locu’s database to a given Yelp business is limited. (We find
that Locu’s data is not as comprehensive since it is not end-user sourced.) Admittedly,
39
we find that the current algorithm can sometimes fail to find a matching Locu venue
even if one exists and, very rarely, produce an erroneous match.
To further maximize the match success rate and eliminate the occasional
erroneous match, we could apply further checks to compare the Yelp business with
Locu’s list of potential matches. For example, approximate string matching techniques4
could be employed to compare the names of the restaurants in Locu’s list choose the
best-matching restaurant within some degree of confidence, even if it is not the first
result. Several Java libraries such as SimMetrics5 exist to find a degree of similarity
between two strings using such metrics as the Levenshtein distance, though this may
exclude restaurants where their name recorded in the Yelp and Locu databases differ
by a whole word, such as “Restaurant”. Even so, this may prove to be more reliable
than simply comparing two restaurant names based on just the first word. This is
especially true if that word is common, such as “The”, or two nearby restaurants
coincidentally share a first (or any) word in common and the provided latitude and
longitude are not enough to favor the correct result.
In addition to business name, we could apply an approximate string matching
technique to the street address as well. This would allow us to iterate over Locu’s
returned list of possible matches and cross-reference the business name with the
address. The Locu venue having the business name and address pair exhibiting the
highest degree of similarity with information provided by Yelp should, in theory, be the
correct match. Only with implementation and thorough testing would be know if this
technique actually increases match success.
Loose coupling of search and client
In the current implementation, RestaurantApiClient.Builder accepts a number of
parameters regardless of whether they apply to either a Business Search or a Search,
and then returns a data type corresponding to the result of one of these search types
based on the use of either the method getRestaurantList() or getRestaurantById().
Given further development time, the search and parsing of the results could be
abstracted away from RestaurantApiClient in the form of a class corresponding to a
Business Search, a Search (labeled LocalSearch in the diagram, to distinguish it from a
Search abstract class), or indeed any type of search provided by the API used. (For
example, Yelp also provides a Phone Search which was not implemented for this
project.) The following diagram may be helpful to visualize this concept.
4 https://en.wikipedia.org/wiki/Approximate_string_matching 5 SimMetrics: https://github.com/Simmetrics/simmetrics
40
RestaurantApiClient could receive the desired Search class as a parameter and
call the class’s search() method to obtain an Object of the appropriate type and return it.
Such a call might look like the following:
RestaurantList rList = (RestaurantList) new RestaurantApiClient(key, new LocalSearch() .location(“Los Angeles”).searchTerm(“diner”).build()).getResult();
Figure 33 - Possible construction of RestaurantApiClient call with search type abstraction
Conclusion
The RestaurantApiWrapper library functions as an adapter between the
application’s front-end code and the Yelp and Locu APIs. It demonstrates use of object-
oriented principles and a Builder pattern. Though not a Factory in the sense of taking
advantage of polymorphism, RestaurantApiClient can produce objects of various types.
The library demonstrates encapsulation of restaurant data and loose coupling to
separate implementation details from the rest of the application. Through further
refinement, the library could be more-easily extensible to accommodate other search
types. By adding further extensions, additional similar APIs could be used.
Figure 32 - Search type abstraction
41
Restaurant Selection Algorithm
The Restaurant Selection Algorithm utilizes the green, yellow, and red lists—the
user’s Restaurant History—in conjunction with randomization to sort the list of
restaurants queried from Yelp such that the first result in the list is likely to be a good
match for the user. Figure 37 on pg. 43 shows the Restaurant Selector Algorithm in
action.
To describe the algorithm in detail we find it best to describe each step taken
along with the specific code used.
Step 0: Query Yelp for a list of restaurants
We refer to this as “Step 0” since this is where the initial list is obtained. The
order of the restaurants in the resultant RestaurantList is unimportant, as we will be
reordering the list.
private class GetResultTask extends AsyncTask<String, Void, RestaurantList> { @Override protected RestaurantList doInBackground(String... params) { try { return new RestaurantApiClient.Builder(mYelpKey) .location(location) .sort(RestaurantApiClient.SortBy.BEST_MATCH) .term(searchTerm).radiusFilter(maxRadius) .build().getRestaurantList(); } catch (Exception e) { return null; } } ... }
Figure 34 – Restaurant Selector Algorithm: querying Yelp for a list of restaurants
Step 1: Filter unwanted restaurants
Specifically, we remove restaurants that the user has added to the red list by
pressing the “Always Ignore” button in the Result Page, and those restaurants whose
rating is less than the minimum rating indicated before the search is performed. In the
latter case, since Yelp does let us filter out restaurants below a certain rating, we must
apply the filter to the results.
for (int i = 0; i < mResultList.getSize(); ) { if (mResultList.getRestaurant(i).getRating() < minRating || db.isRestaurantInList(mResultList.getRestaurant(i), Constants.RED_LIST)) { Restaurant removed = mResultList.removeRestaurant(i); if (removed == null) i++; } else i++; }
Figure 35 – Restaurant Selector Algorithm: filtering unwanted restaurants
42
Step 2: Randomize & refine restaurant sort order
Each restaurant in the list is assigned a weight which determines the final sorted
order of the list of restaurants used to present suggestions to the user. For each
restaurant, this weight is initially a random integer between 1 and 100. For green- and
yellow-listed restaurants, this weight is further refined with a multiplier which influences
its position in the final list.
Green-listed restaurants are given a constant multiplier of 1.15. That is, they are
promoted relative to the otherwise random order.
In contrast, yellow-listed restaurants are given a multiplier which varies linearly
with the UNIX timestamp indicating when the restaurant was added to the list. The end
result is that restaurants just added to the yellow list are demoted relative to the
otherwise random order by 40%, whereas restaurants added to the yellow list nearly a
week ago are not demoted relative to the otherwise random order. (The Splash Screen
activity automatically clears restaurants from the yellow list which were added more
than a week before the current time.)
for (int i = 0; i < mResultList.getSize(); i++) { randomNum = rand.nextInt((100 - 1) + 1); Restaurant r = mResultList.getRestaurant(i); if (db.isRestaurantInList(r, Constants.GREEN_LIST)) { randomNum *= 1.15; } else if (db.isRestaurantInList(r, Constants.YELLOW_LIST)) { Timestamp timestamp = Timestamp.valueOf(db.getRestaurantTimeStampFromList(r, Constants.YELLOW_LIST)); long timeElapsed = System.currentTimeMillis() - timestamp.getTime(); final long ONE_WEEK_IN_MILI = 604800000; randomNum *= (0.6 + 0.4 * timeElapsed/ONE_WEEK_IN_MILI); } mResultList.getRestaurant(i).setWeight((int) randomNum); }
Figure 36 – Restaurant Selector Algorithm: randomizing and refining restaurant sort order
Step 3: Present restaurants to user in non-increasing weight order
The first restaurant displayed to the user will be the restaurant in the list having
the highest weight. Internally, that restaurant is removed from the underlying ArrayList
of Restaurants in the RestaurantList object before being presented to the user, so the
restaurant in the modified ArrayList having the highest weight will be the next result.
This fact is useful in that we can look ahead and pre-load the next restaurant’s Yelp-
provided image.
43
Figure 37 - Demonstration of restaurant selection algorithm on HTC One M7 (5”)
44
Data Storage
Shared Preferences
Introduction
We utilized Android’s SharedPreferences feature to store data in Key Value
pairs. This approach allows us to easily access data faster without having to make
database queries. Our Application utilized SharedPreferences by defining and storing
default application settings.
SharedPreferences also powers the application’s SharedPreferences feature to
let the end-user define default values for the application:
Data stored
Key Value Info
hasAgreedtoEula boolean When the user chooses to agree or disagree to the end user license agreement
defaultSearchRadius float The search radius where the application will obtain restaurants from
defaultMinStar float The minimum star rating for a restaurant to look for
lastGreenRestaurantTimestamp float The timestamp that determines when to display a restaurant feedback after choosing to go to the restaurant
lastGreenRestaurant String A name the last restaurant that the user chose to go to
Figure 38 - Data stored in application's Shared Preferences file
SQLite
Introduction
We leveraged Android’s built-in SQLite database feature to store more structured
data as compared to Key-Value pairs. For example, we wanted to use SQLite to store
data that powers our Selector algorithm which promotes and demotes restaurant
results. The SQLite table allowed us to store the restaurant’s Yelp business ID, as well
as the timestamp it was inserted in the database. In addition, SQLite’s structured data
45
allows us to extend the database to store more relevant restaurant data coming from
Yelp API that we might need to know about in the future.
Data stored
List Data
Green Restaurants where the user has eaten.
Red Restaurants that the user has chosen to ignore. These will not show up on subsequent searches.
Yellow Restaurants that the user has chosen to check out later.
Figure 39 – Application’s SQLite database tables
Conclusion
We utilized two of the five different ways to store data in Android6: SharedPreferences
and SQLite. Storing data in SharedPreferences is ideal for default application settings
because it forces data to be stored in key-value pairs making it faster to access when
compared to SQLite. And on the other hand, while SQLite can allow storage of more
complex and more structured data, it can be slower after adding more rows and
columns.
6 Android Storage Options: http://developer.android.com/guide/topics/data/data-storage.html
46
Utilities
Constants
Constant values used throughout the application were defined as static variables in a
class named Constants. Such values included database table numbers for the green,
yellow, and red lists, as well as strings representing the field names of stored Shared
Preferences. This served to make the main application code more readable. For
example: db = new dbHelper(this, null, null, Constants.RESTAURANT_LISTS_TABLE_ID) versus db = new
dbHelper(this, null, null, 1) –or– db.deleteRestaurantFromList(r, Constants.YELLOW_LIST); versus
db.deleteRestaurantFromList(r, 2);.
ServiceUtil
Since it is necessary to check for network connectivity prior to making any API call, and
it is necessary to check that Google Play Location Services is enabled prior to obtaining
the device’s current location, we created a class with static methods to check these
conditions.
ServiceUtil.isNetworkAvailable() checks the provided Context (usually, an
Activity or DialogFragment) to determine whether the Android Connectivity Service is
available and whether the device is connected to the network. If so, the method returns
true.
ServiceUtil.isLocationServicesOn() checks the Android Location Manager to
determine whether the GPS location provider and the network location provider are
available. If either are available, this is sufficient for Location Services to retrieve the
device’s last known location, and so the method returns true.
ToastUtil
During development it is necessary to display of feedback and/or information that
is needed to debug a specific portion of the application. Since developers are going to
need a lot of feedback, they need to write feedback code sparsely throughout the whole
application code base and can easily make code harder to read. In order to display such
in a clean and well defined manner, it is good to use Toasts. ToastUtil is a utility class
that makes writing toasts easier write therefore making the application’s code base
easier to manage and more loosely coupled.
Instead of writing: Toast.makeText(getActivity, “I am a toast”, TOAST_Length.SHORT).show(); one can
write: ToastUtil.showShortToast(getActivity, “I am a toast”); instead.
SECTION 4: CONCLUSION & FUTURE GOALS
48
Future stretch goals
Food Niche
Implementation
Food Niche is a feature that allows the user to collaborate with the application's
algorithm by actively participating in its restaurant selection training. In order to
implement, we need to utilize Java’s TreeSet data structure to create a collection of
possible food niches that the end-user might be interested in. The food niche collection
will be appended and modified on the fly with restaurant category values--obtained from
either Yelp or Locu API--on the green list, which allows the Selector class to learn which
restaurants the user tends to put on the green list. Then, the collection can be modified
via a Food Preferences dialog where the user can downvote and upvote niches. This
allows the user to collaborate with the Selector class to give more refined results based
on the user’s tastes. Finally, the collection can also be reset using the Application
Settings dialog where it wipes the entire collection of niches
Advantages
Food niche allows the user to maximize the accuracy of restaurant suggestions
because it allows both the application and the end-user to collaborate hand in hand to
build better suggestions.
Notes
We will use TreeSet to store food niches because sets do not allow duplicates
and it allows ordering of items as compared to HashSet. Since there are only a handful
of food niches (< 100) that user may care about so the time complexity for its ‘add’,
‘remove’, and ‘contains’ methods, O(log(n)), should be pretty trivial. Also, trees also
allow the application to “weigh” niches based on end-user’s upvotes or downvotes
under Food Preferences.
Push Notifications
Advantages
Push notification is a possible feature in our application that we can implement to
provide better user experience by provides another layer of interaction apart from what
the application already offers. Notifications drive the user to use the application even
more through notifications that drive retention such as sending a message when there is
a new restaurant nearby, or when the user is in a new location where there are lots of
restaurants nearby. In addition, notifications can also ease the user experience by
49
providing notifications if there is a new version of the application where bugs are fixed or
when new integrations have been added. Finally, notifications overall make the
application “smarter” by responding directly to events that happen both inside the
application (when the user clicks a specific button) or outside the application (when user
is nearby a restaurant)
Notes
Push notifications are event driven (i.e.: when you get an e-mail once you
subscribe to a newsletter), and must be called upon execution of a certain event. Some
push notifications can be delayed such as a notification that shows a “Rate my
restaurant” notification 30 minutes after the user presses “Go to this restaurant. Push
notifications are an integral part of any modern-day application.
Food Pairing
Implementation
Food pairing is another integral feature that can be useful for the application in
the future. By using the API available at http://developer.foodpairing.com/, we can
implement a way to suggest popular food pairs in the menu. By mining the Locu
restaurant menu as well as Yelp's top rated comments, we can identify popular food
choices by previous restaurant customers. Once all the food choices are obtained for
the specific instance of the restaurant object, we can store the choices in a data
structure such as a Map where we can begin pairing food choices using the Food
Pairing API.
Advantages
Just like push notifications and food niche features, food pairing allows the
application to become smarter and offer better user experience for the end-user. Not
only it suggests restaurants to go to, it also suggests possible appetizers, main entrees
and desserts using food pairing algorithms.
Notes
Although the data is available using designated APIs, string matching and
processing can still be a challenge. In order to handle this, it is necessary to utilize 3rd
party APIs such as SimMetrics.
50
Monetization
Early on in the program’s life, we would simply use banner ads on the main filter
page where there is room for it. An option can be offered in the program’s settings to
pay a small fee to disable ads, and expand the number of restaurant recommendations.
Later in its life, however, when the application becomes more popular, we could
have sponsored recommendations in lieu of ads, which would have a higher weight in
the randomizer. A higher weight means that the restaurant is more likely to become one
of the first results on the restaurant result page. This would be visible to the user as a
“Sponsored Result”, though users would still be able to add the restaurant to the ignore
list, if desired.
51
What We Would Do Differently Next Time
Had this team the opportunity to attempt this project all over again, knowing what
we now know, there are a few things we would have done differently. In the very
beginning, we all met to discuss our strengths and weaknesses. Following that, we
should have began listing what kinds of tasks we needed to accomplish and assign said
tasks to certain team members, or even pairs of team members. When a task was
completed, the team member who completed it could have checked to see if there was
any currently active task that he could have joined, instead of immediately moving on to
a new task. Our original focus was very vague, and our roles weren’t well enforced. As
a result, we’d often find ourselves either working on the same task or we’d skip around
and work many different tasks at once. That created a bit of a problem in that the more
experienced coders would finish tasks very quickly and leap ahead of the less
experienced individuals. Towards the end of the project, however, this was addressed
and we were much closer to attaining a better split of the workload.
Another aspect of the project that could have been handled much more neatly
was the weekly scrums. We lacked a proper focus during our scrums and would often
go on tangents during the process. These tangents were always relevant to our project,
but they were better suited for post-meeting discussion. This resulted in scrums that
should have lasted 5-10 minutes lasting well over 15 minutes. This became very
apparent when we scrummed with Marc (the ‘Boss’ of the ‘company’) because he would
control the pace well, and our meetings were extremely quick and efficient. To improve
our scrums, we should have had the note-taker for the meeting, or the team leader,
Alex, be the one to control the flow, to ensure that everyone got a chance to speak.
Team members could have all had notebooks in which they would take note of what
topics they’d want to elaborate on post-meeting, in order to prevent getting sidetracked
during the meeting.
Something that was an issue early on in our development process was
understanding Github and its integration with Android Studio. We should have brought
our laptops to a meeting and organize our Github and Android Studio integrations,
ensuring that we all had the same settings in order to prevent compatibility issues
arising from pushing and pulling each other’s code bases.
52
Bibliography/Resources
3rd Party Tools
Tool Purpose URL
Stack Overflow
General code help http://stackoverflow.com/
Slack Messaging, project management
http://slack.com/
Github Version Control http://github.com/
Github Wiki Documentation https://github.com/sfsucsc413/t5/wiki
Android Studio
Development Environment, Source Control
https://developer.android.com/sdk/index.html
EULA Generator
EULA Generation http://kuikie.com/tools/eula-generator/
String Formatter
Formatting text http://www.freeformatter.com/html-formatter.html
Licenses
Tool URL
Google Maps https://developers.google.com/maps/terms
Yelp https://www.yelp.com/developers/api_terms
Locu https://dev.locu.com/terms/
Android https://source.android.com/source/licenses.html
Android Studio/IntelliJ
http://blog.jetbrains.com/idea/2013/05/intellij-idea-and-android-studio-faq/#comment-4939
Gradle https://gradle.org/license/
SQLite https://www.sqlite.org/copyright.html
SECTION 5: PRESENTATION SLIDES
54
During the final week of class, each group in the class gave a brief ~10-minute
presentation about their app. The presentations explained how the apps worked, both
as a user, and how they worked on a technical level.
Figure 40 - Slide 1
55
Figure 41 - Slide 2
Figure 42 - Slide 3
56
Figure 43 - Slide 4
Figure 44 - Slide 5
57
Figure 45 - Slide 6
Figure 46 - Slide 7
58
Figure 47 - Slide 8
Figure 48 - Slide 9
59
Figure 49 - Slide 10
Figure 50 - Slide 11