Retail Therapy
Motivation
The motivation for this app is for all of the times that I go to the store and wonder, "Do I have enough [enter grocery]?". This app is supposed to answer that for you. Simply enter what you buy, when you buy it, and the next time you're at the store open the app. You'll be faced with a list of what items you need and when.
Furthermore, this will notify you if you will soon run out of an item. For example, if you buy trash bags only every few months, you may not realize you are out until you go to replace the liner and there are no remaining trash bags.
Finally, for especially regularly bought items, this makes it exceedingly easy to realize you can set up a subscription. In particular, there are services such as Amazon that offer a discount if you sign up for a subscription service, such as auto-buy food every week.
Views
There are four views here, listed below. Each is a Fragment, stitched together by a navigation pane on the bottom.
Shopping List
Item Entry
Map
Settings
Shopping List
This is the view that can be used for viewing items you've bought, when you bought them, and when you'll need more. The list highlights items that you are likely out of, and it is readily viewable when certain items will need to be replaced.
Item Entry
This is where most of the complexity lies. Here, you can enter items that you've bought. This form has several features. For the below, I used suggestion adapters that I wrote for each of three form fields that autocomplete, and I have two text watchers for three items that I validate data in (one is used for two different ones).
Entry Validation
This validates everything you've entered before you're allowed to enter. This includes text watchers on a few of the fields, including the price, which allows you to enter digits and it will automatically format it for you with a dollar sign and two decimal places. The way to type in here is digits typed will push everything left, maintaining everything to its right exactly as it was. This way, starting at $0.00, typing on the right a '4' leads to $0.04. Putting the cursor to the left of the decimal place and typing a '4' will instead make this $4.00.
In this list, you can click on an item. This will display the last time it was purchased and for how much. You can also "quickadd". For example, you may be at the store and bought four more oranges. Simply go to the list, find oranges and quickadd 4. The list will update and re-sort.
Autocomplete
For the fields which may have recurring values, like the store it was bought from, the brand and item name, there is autocomplete. For the item and brand name, the suggestions are simply all items slurped up from the database. For the store, I use the database but I also use the Google Places API to supplement these suggestions with locations nearby. I monitor the current location of the user and set a rectangle nearby to bias the results.
One sorta cool thing is if you autocomplete an item, and the item had a brand (visible in the list), then clicking it will also fill in the brand. I wanted to additionally incorporate barcode scanning, which would be easy to pre-populate everything associated with the barcode. This was another nice-to-have that I ultimately didn't end up doing.
Extrapolation
On entering a new item, I punt the new item to a service I created, the Extrapolator. This takes all items and, on each modification to the list of purchases, recomputes when it will need to be replenished. I set a notification for when you are three days out from needing to replenish something.
Maps
The maps view is underwhelming. Here, I just have a plain map and, upon clicking on a location "Point of Interest", a dialog appears with the place's hours and phone number. The phone number can be clicked to call the place. For this, I use the Google Places API. I wanted to do more with this, because Google exposes business types. I could use that to effectively say "I know you bought XYZ from this store, which according to Google is grocery. That means this other store which is also grocery might have XYZ". Turns out this is somewhat involved, and I didn't end up doing it.
Settings
I ended up saving settings for last because I wasn't sure how many settings there were, and aimed not to "future-proof" -- waste time on features that may or may not be useful. In the end, the only setting that made sense was "how long before you need something do you want a notification?". Because I ran out of time, I didn't implement this.
APIs
Google Maps API
This uses the Google Maps API to view the map.
Google Places API
This uses the Google Places API to get information about places. The two main use cases for this are within the map, to view details about points of interest, and in item entry to autocomplete suggestions of stores upon typing.
Services
Location Manager
I use the location manager to center location on the map as well as to tailor autocomplete suggestions in the item entry phase.
Notification Manager
The notification manager is used to push a notification whenever it's nearing time to replenish something.
Alarm Manager
The alarm manager is used to set a timer for myself for three days before the next item to be replenished comes. If replenishing that item occurs first, then I set a new alarm for the next item to be replaced.
SQLite
Everything that I store that persists is stored in a SQL database. There are five tables: brands, items, purchases, skus and stores. These together track what has been bought and when, and are used to figure out when they will need to be bought again.
For this, I wrote my own wrapper that does a lot of the heavy lifting. This is found in the DbHandler class.
Extrapolator
This is a service I wrote myself. It sits idly in the background waiting for a new purchase item. When that item is purchased, I ship it off to this service, which stores it and then re-computes when the next time you will have to buy that is. If this was also the next item needing to be bought, this computes what item needs to be bought next and sets notification timers accordingly.
General
There are a few common items that I have in a common directory. One is a Constants class, which just serves as a centralized place for constants. I have a utilities class which ended up only implementing permissions checks.
The most interesting thing here is my GenericData class which I use as a container to pass around data in a genericized form. This made inheritance easier and defining then implementing abstract classes ant interfaces.
File Structure
Retail Therapy
common - package with common/miscellaneous items
Constants.java - constants
GenericData.java - implements a generic data class so I can objectify data types. I did this before realizing I could have used Long, Integer, etc to do the same thing.
Utilities.java - this mainly just has permissions checks
services - services I wrote along with a database helper
db - my database wrapper
DbHandler.java - the database wrapper that implements various operations done on different tables in the database
DbTableBase.java - base class for database tables. Each table is its own class which allowed me to generalize some common functions
LastPurchaseInfo.java - retrieves from the database the last purchase. This is what drives the dialog in the shopping list view
tables - package with tables in it, that all inherit from DbTableBase
Brand.java - brands of items
Item.java - an item. Several same items can come from different brands
Purchase.java - defines a purchase (store, item, brand, price, etc)
SKU.java - this was going to be used with the barcode scanner. A SKU is a very specific item, down to number of units in each package
Store.java - a store. This contains mostly results from Google Places, but also manual entries (e.g. Google Places doesn't have anything for online retailers)
extrapolate - package with extrapolation service
ExtrapolatorService.java - the service used for extrapolating next purchase
ReplenishmentInfo.java - basically just a struct that holds the various items for knowing when something will need to be bought again
ReplenishmentNotification.java - a convenience class that handles notifications
ui - package with all the fragments and UI in it
item_entry - package with items used in item entry fragment
adapters - package with all the adapters used in it
BrandSuggestionAdapter.java - fills autocomplete for brands text view
BrandSuggestionItem.java - an item that goes into the brands text view autocomplete list
ItemSuggestionAdapter.java - fills autocomplete for item text view, and autofills brand if there's an associated brand on click
ItemSuggestionItem.java - an item that goes into the list and is used by the adapter
StoreSuggestionAdapter.java - fills autocomplet for store text view. This also interfaces with Google Places for injecting Google's suggestions also
StoreSuggestionItem.java - an item that goes into the store suggestions list
text_watchers - package with text watchers
PriceTextWatcher.java - maintains $#.## format on price
QuantityTextWatcher.java - validates quantities and makes sure that the number of items bought and the number of items per pack are not zero
ItemEntryFragment.java - the main class that controls the item entry fragment
map - package with map fragment and associated items
MapFragment.java - has the map and the logic for showing point of interest details
settings - package that was supposed to have settings
SettingsFragment.java - the main settings fragment class
SettingsViewModel.java - populates the only view with "There are no settings"
shopping_list - package that contains items in the shopping list view
adapters - package with adapters in it for shopping list
ReplenishmentItemAdapter.java - the adapter used to populate the list of items that need to be bought soon
ShoppingListFragment.java - the logic for the shopping list view
MainActivity.java - the main activity. this file is almost completely empty because most logic is spread over the fragments
The next items show using the app. Here I enter the same item twice to show autocomplete.
Here I simply close the app and reopen to demonstrate that data is saved.
Here I add an item and go to the list. Notice the item is red because we still need some. I open a dialog to show previous purchase, and then quickadd more.
Here you see the map, clicking on a store you see the hours. You can click on the phone number and it will call.
Challenges
The main challenges were getting everything to work.
Google APIs don't work on my phone, so I had to get the emulator to work. They also kept failing and I spent 6-8 hours figuring out that the solution was to wipe the phone. I've never made an adapter before and it was especially challenging for 2-line items. Getting the fragments to all talk to each other was challenging. The SQL stuff was also hard because I didn't know what I was doing half the time, and I ran into a hair situation where I was accidentally deleting ID's without the changes cascading, so I was getting a lot of null dereferences.