Events/Invitation Manager on Android

The most important part of an excellent gathering is the people. This solution helped me keep track of the people.
I invite my friends to events one email, one text, one conversation at a time. It's a useful method in many ways, but it's difficult to track whom I've invited and how they've responded: Marcy is a maybe; no reply from Mary; Daphnie wants to bring drinks. I needed help and, when I couldn't find a solution that suited my needs, I built my own.
I developed the Events/Invitation Manager software in Android Studio (Ubuntu), using Flutter and Dart.
This project is inactive; previous progress is compiled below. Their original posts can be tracked via the Event Planner tag.
from: Project Updates, et al: 2018-01-01
Event Planning App
Everyone has their neuroses. One of mine: I keep slightly obsessive track of whom I've invited to things, if they've responded, if I need to forward them event details, etc.
I like to be a good host and make sure everyone's taken care of; beyond two to three invites, keeping everything in mind can be tricky. I also tend to contact people individually to invite them to things. Since I'm not using a calendar/Facebook event, I don't have anything automatically tracking invitations, responses, event details, etc.[1]
All of which is to say: I'm working on an app that will help me track people I've invited to things.
I've landed on the following flow for the wireframing process, which works fairly well for me:
- Very quick freehand sketches
- Pencil wireframes
- Balsamiq wireframes
At the start, I get to focus on high-level functionality and flow without getting lost in a wash of detail.
By the time I'm in Balsamiq and working through those details, I don't have to worry about accidentally spending too much working on a wireframe that I'm only going to cut from the project, later... those cuts happened in the prior two stages.
from: Project Updates, et al: 2018-04-01
Event Planning App
I created a draft database structure for the event planning app. At the moment, there are 3 types of people I want to keep track of:
- People I might invite out to group events,
- +1s at those group events,
- People I might network with one-on-one.
It didn't occur to me when I was beginning, but it turned out I wanted to store different types of information for all three groups of people. Trying to capture everything while keeping an elegant database design was an interesting challenge. At the moment, one-on-one and group event invitees will share a single database table and the +1s have their own table.[2]
My current goal is to use Google's Flutter UI framework to create the app, instead of just using Android Studio alone; before I can get started in earnest, I'll need to acquaint myself with Flutter.
from: Project Updates, et al: 2018-07-01
Flutter is useful and easy
Previously on CKDSN: I'm using Flutter to code my event planning app.
I installed Flutter and it was really easy![3] Installing Flutter was so easy, I jumped right into learning Flutter. And learning Flutter was so easy, I jumped right into coding my event planning app.
My current goal is to get a minimal version of the software running on my phone, as soon as I can. A) It'll help me track events and invites. B) Once I'm using it, I'll have a better idea of what data I actually want front-and-center.
I'll have more thoughts on Flutter and the app, later. (I occasionally drop updates into my Instagram story: @CKDSN.)
from: Project Updates, et al: 2018-10-01
Event and Invitation Managing Software
My phone now runs a beta alpha whatever-comes-before-alpha version of my event planning/invitation managing software.
I've been using the software for about 6 weeks and the results have been decent: it's replaced Google Keep as my invitation managing tool, and that was my primary goal. I'd like to gather 6+ months of event and invitation data before adding statistical data to the software; while I gather said data, I'll continue to build the interface and experience.
After I began using the software, the first big thing that needed fixing was sorting/alphabetizing my query results -- I was genuinely shocked at how infuriating it was to work with more than 4-5 unsorted records. I added basic sorting to most of the SQL queries, and used a case
statement to sort RSVPs in the following order: yes, maybe, no response, no, not contacted. Here's a simplified version of the case statement:
//note: I needed to include the single quotes in the SQL string
//in order for the case statement to work,
//hence the backslashes escaping them.
String tempSql =
[...]
' ORDER BY case when ' + RSVP + ' = \'yes\' then 1 ' +
' when ' + RSVP + ' = \'maybe\' then 2 ' +
' when ' + RSVP + ' = \'noresponse\' then 3 ' +
' when ' + RSVP + ' = \'no\' then 4 ' +
' when ' + RSVP + ' = \'notcontacted\' then 5 ' +
' else 6 ' +
' end asc, ' + FIRST_NAME;
I anticipate adding a calendar interface for choosing dates and archiving old events will be the next pain points, but we'll see.
from: Project Updates, et al: 2019-01-01
Event Planning/Invitation Managing App
I extended invitations to 60ish people for the mezcal tasting, which was a great workout for my invitation tracking app. I made two updates to the app specifically resolving some of the pain points I felt while planning the party.
One
The detail page for any given invitee already displayed global notes for that person, e.g. Is vegetarian, as well as event level notes that I used to track the snacks and drinks people offered to bring. I quickly realized that navigating to 40ish individual detail pages didn't give me a great sense of the party as a whole: by the time I had navigated to the next person's detail page, I'd forgotten what I'd just read.
My solution was bringing all of that information one level higher in the navigation scheme. The list of names that I use to access a person's detail page now displays global and event notes.
Two
The second major update was adjusting the invitee list so the groupings (i.e. replied yes, replied maybe, haven't contacted, et al.) had visual differentiation.
With over 60 invitees, being able to see the breaks between groups made it easier to find individual invitees and made it easier to consider the blocks as a whole. (e.g. "Who's still a maybe? Do I need to reach out to them??")
Exporting and importing databases in Flutter/Dart
Because I have a new phone, I needed to quickly figure out exporting my app's database from my Pixel 2 and importing it into my Pixel 3. It's not hard, but also I couldn't find anything online that explicitly said, "Here's how I did it."
As such, here's how I did it...
CURRENT_DATABASE_NAME = "/invitationDatabase.db";
void exportDatabase() async {
//File to be copied
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String mypath = documentsDirectory.path + CURRENT_DATABASE_NAME;
File activeDB = new File(mypath);
//Landing spot for new file
Directory _externalDocumentsDirectory = await getExternalStorageDirectory();
Directory _externalDownloadDirectory = new Directory(_externalDocumentsDirectory.path + "/Download");
String externalpath = _externalDownloadDirectory.path + CURRENT_DATABASE_NAME;
//The File object sends a copy of itself to the path indicated
activeDB.copySync(externalpath);
}
void importDatabase() async {
//File to be copied
Directory _externalDocumentsDirectory = await getExternalStorageDirectory();
Directory _externalDownloadDirectory = new Directory(_externalDocumentsDirectory.path + "/Download");
String externalpath = _externalDownloadDirectory.path + CURRENT_DATABASE_NAME;
File eventPlannerImportDB = new File(externalpath);
//Landing spot for new file
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String internalpath = documentsDirectory.path + CURRENT_DATABASE_NAME;
//The File object sends a copy of itself to the path indicated
eventPlannerImportDB.copySync(internalpath);
}
Downloads folder vs Downloads Manager
Note: When exporting, I threw the file into the Downloads folder so it would be easy to find. As it turns out, sometimes Android phones have two separate "downloads" spots: the Downloads folder is a folder that holds all of the files you put there; Downloads Manager is a virtual folder that only displays files that were downloaded from the internet.
So if you're looking for a file and you know you placed it in the Downloads folder, try to view the folder using the Files app instead of the Download Manager. (It took me a very long time to figure that one out; hopefully this saves someone else some time.)
from: Project Updates, et al: 2019-04-01
Adding tags to the events/invitations app
I made some updates to the events/invitations app this quarter, perhaps none more important than adding tags.
As the number of people and events in my database increased, it was becoming difficult to track which of my friends liked board games, which new person loved salsa dancing, etc. Tags are a great solution to that problem and, as a bonus, they were already on my list of things to add to the app... the database had tag and linking tables ready to go from day one.
My main focus was getting minimal functionality built as quickly as possible... more than anything I needed help filtering through my data, ASAP. There are still some things to tackle: not all of the results are sorting properly; I'd like to add tag info directly to contact cards; when adding people to events, I'd like to filter by tag.
The simplest solution[4] I found for adding tags to a database was:
- Identify your table that needs tags (i.e. A contacts table)
- Create a tags table
- Create a third table that only has three columns: its own key, the contact foreign key, the tag foreign key.
From there, it's just a bunch of searching the third table, either for a specific contact key or a specific tag key.
The toughest part was deciding what my SQL statements should look like, specifically when I needed to return a list of all tags, while marking which tags were already associated with a given person:
For a while I tried to research and write a perfect SQL command to make it happen. I thought I had it, then realized I was very far off. Eventually I decided to use two SQL commands to grab two sets of results, and manually combine them.
from: Project Updates, et al: 2019-07-01
App Updates
I've been using my Invitation Managing/Event Planning app more than I've been designing and building it, which is great... that means the software is working well. It's working so well, I've generated too much data to manually sort through all of it; as a result, my recent updates focused on making the data easier to manage.
Events in reverse chronological order
I've always had ambitions of creating an interface that would allow me to archive events; archiving events, in turn, would process final attendance statistics. To encourage this work, I intentionally listed my events in chronological order: my very first entry -- almost 11 months ago -- has always been the first event one sees when viewing the events list.
My rationale: building the archiving interface needed to get done, and if I happened to delay the work so long that scrolling to the bottom of the list became annoying, it would incent me to finally build the archiving interface.
The scrolling did, indeed, get annoying; however, building the interface did not need to get done. It's a cool idea and, as of today, its absence has no material impact on how I use the software or how efficiently it helps me invite people to events.
I updated a single SQL command, my events view has the most recent entry first, and now my life is much better.
Filtering Contacts
When there were very few people in my database, I could glance at all of my contacts, remember which ones are into board games (for example), and then decide which of the board gamers I wanted to invite to a particular event.
My contact list has grown large enough that it's finally difficult to do all of that work in my head. In response, I added an interface that allows me to limit my contact view to a particular tag when I'm adding people to an event.
Mass Adding Contacts to Tags
This update is still in progress.
Today, if I add a new tag to the database, and I want to associate 19 people with that tag, I have to navigate to 19 separate profiles and add the new tag to each person's profile. I'm working on an interface to add people en masse to a tag; in the above example, from a list of all my contacts I would select the 19 I want added to the tag and save.
from: Project Updates, et al: 2019-10-01
Event Planner/Invitation Managing App
I had a burst of productivity in July and added an entire grip of helpful features to my event planning app.
Previously on CKDSN...
Last quarter this was incomplete and now it's done. In the image below, I've added three baseball players to the baseball tag, all at the same time.
Dynamic Titles for Screens
There's a title at the top of most screens in Android. While developing my app, I got into the habit of "titling" each screen with the name of the dart file that produced the screen. So, in the picture below, the screen is titled "ShowEvents" and the code that produces that screen is saved as "route_showevents.dart".
At the time, it was a strictly utilitarian decision that made it easier to find the correct batch of code when debugging the software. And it worked well until I increased my actual usage of the app.
Eventually, I realized if I'm adding people to multiple events, sending text messages, and checking information in other apps, it's easy to lose track of which event I'm currently editing when the screen is simply titled something like "AddInvitees". (Am I currently adding invitees to the Mary Tyler Moore viewing party, or the 30 Rock viewing party?)[5] In those instances, I needed a more useful title.
For screens with dynamic content, I'm now using dynamic titles: e.g. instead of something like "AddInvitees" the screen below has "Watch MTM" as its title.
Date Picker
I added a calendar picker to speed up adding dates to events.
I didn't enforce any strict formatting rules for the field itself... the date column in the database is just a text field and I treat it like an open ended text field in the interface. There are some events I initially date as "this weekend" or "sometime in October" and it's convenient to store that data in the date field, as well.
Plus Ones: finally fixed
Guests bring friends to things. I knew right away I would need to add +1 functionality to the app. In fact, I thought adding the ability to quickly attach a +1 or +2 to a guest would be one of the easiest things in the world. If computers are good at nothing else, they're good at adding 1s to things. The core mechanism of a computer is adding 1s to things. There's an entire programming language named after the concept of adding 1s to things.[6]
All of this is to say: as someone who knows anything at all about making software, I know how to add 1 to things.
Except, of course, when I don't.
I believe my main problem was storing +1s as strings in my database and, subsequently, not remembering I'd done that. My secondary problem was not tracking well enough all the times I needed to do a type conversion between numbers and strings for the +1 data. [7] Once I took a second to figure out what was going on, it was easy to get +1s up and running.
Guest Counts
The masterpiece of the last quarter was adding guest counts to the app. Since my home isn't infinitely big, it can't hold infinitely many people. The guest counts allow me, at a glance, to compare the size of an event against how many people my place can hold for that type of event.
In the picture above, the Listening Party has:
- 1 Yes,
- 8 Maybes,
- 0 Nos,
- 0 people who still need to give me a response.
It's not perfectly implemented, but it's there and it's functional. (And the totals include +1s.)
from: Project Updates, et al: 2020-04-01
Events App: Hiding Old Contacts
I created a quick way to get old contacts out of sight and out of mind when choosing attendees for an event: adding "Archive" to a contact's notes field and filtering away those results. It was a great way to leverage data I was already accessing... implementing the solution only took a single line of SQL:[8]
' WHERE ' + DbS.COLUMN_NAME_CONTACT_GLOBAL_NOTES + ' NOT LIKE \'%Archive%\' ' +
The entire statement looks like this:
String tempSql =
'SELECT ' +
DbS.TABLE_NAME_CONTACTS + '.' + 'id' + DbS.COMMA_SEP +
' ' + DbS.TABLE_NAME_CONTACTS + '.' + DbS.COLUMN_NAME_CONTACT_LAST_NAME + DbS.COMMA_SEP +
' ' + DbS.TABLE_NAME_CONTACTS + '.' + DbS.COLUMN_NAME_CONTACT_FIRST_NAME + DbS.COMMA_SEP +
' ' + DbS.TABLE_NAME_CONTACTS + '.' + DbS.COLUMN_NAME_CONTACT_GLOBAL_NOTES + DbS.COMMA_SEP +
' ' + tempInviteeTableName + '.' + DbS.COLUMN_NAME_INVITEE_FOREIGN_KEY_CONTACT_TABLE +
' FROM ' + DbS.TABLE_NAME_CONTACTS +
' LEFT JOIN ' + tempInviteeTableName +
' ON ' + tempInviteeTableName + '.' +
DbS.COLUMN_NAME_INVITEE_FOREIGN_KEY_CONTACT_TABLE +
' = ' + DbS.TABLE_NAME_CONTACTS + '.' + 'id' +
' WHERE ' + DbS.COLUMN_NAME_CONTACT_GLOBAL_NOTES + ' NOT LIKE \'%Archive%\' ' +
' ORDER BY case when ' + tempInviteeTableName + '.' + DbS.COLUMN_NAME_INVITEE_FOREIGN_KEY_CONTACT_TABLE + ' then 2 ' +
' else 1 ' +
' end asc, ' + DbS.COLUMN_NAME_CONTACT_FIRST_NAME;
from: Project Updates, et al: 2020-07-01
Events App: Update
(When better technology = less technology)
A personal rule: if I'm at home, I turn off my phone.
The nerd reason
I really like my Pixel 3, and I'm hoping I can keep it until Pixel 6 comes out. The battery is likely to go first, and batteries fail as charge cycles increase.
I did some napkin math, and turning my phone off when I got home meant I'd start to reach my charge cycle max around year 3 instead of year 2. So, in napkin-math-theory, this will keep my battery operational until Pixel 6.
The social reason
I actively minimize the time I spend looking at my phone while in public. Instead of giving my attention to my phone, I want to give that attention to what's around me... especially when I'm with friends.
Using my phone at home trains my brain to give attention to my phone... I figure ignoring my phone when I'm home makes it easier to ignore it when I'm out.
So when I'm at home, my phone is off. However, my events app lives on my phone.
Although I'm not planning complex events right now, it is nice to occasionally see a friendly face for a round of online Codenames. When considering how to go about planning with my app unavailable, a few options immediately came to mind:
- Use my phone at home?
- Chromebooks can run Android apps... Is it possible to install the app, from Android Studio, directly to my Chomebook?[9]
- I have some old tablets I'm not using?
The perfect solution came to me after a good night's sleep:
My phone was a great location for the original solution because I often accessed the data on-the-go. Since I'm home most of the time, and the events are less complex, a good ol' notebook is actually the best solution.
A reminder to us all: digital can do some pretty rad things, and sometimes the best solution is analog.
final update: 2023-11-29
This project is inactive.
My post-pandemic solution is still a notebook... a massive, beautiful, 11" x 14" notebook, but a notebook all the same.
At the moment, I'm not organizing weekly bar hangs and weekly tennis meet-ups and biweekly game hangs. As such, an analog solution is still more convenient and more fun.
Not to mention the fact I blew up my Facebook account years ago, which was one of the best decisions I've made in my life. You should consider it. ↩︎
And I'll need to create a mechanism to transfer +1 records into my main table. ↩︎
Especially the second time I did it - I soft-bricked my laptop and had to reinstall Linux. ↩︎
I'm not sure if that speficic page is one I used when working through the problem, but it's representative. ↩︎
MTM is one of the best shows ever, go watch it; also, it's easy to see that the people who made 30 Rock also liked MTM. ↩︎
For the uninitiated: C++ is a coding language. It's a sequel, of sorts, to C (another programming language). In C++, "++" adds one to things, hence C becoming C++. So, for example, if you needed to add 1 to a variable called guest_list, you could simply type: guest_list++; ↩︎
Another one for the uninitiated: mileage varies, but it's not uncommon for a coding language to only accept official text data for display in a text field. Meaning, if I want to display the number 3, I may need to convert that data to a text '3' and then display the text version of '3'. If, later, I try to add 1 to my text '3', the language will call me a dummy and tell me I can't add numbers and text together. What this functionally means for this app: if I have a display up that shows me how many +1s I've added to an event, I need that information in two separate forms... a text version so I can display the information on screen, and a number version so I can actually use it to add it to other numbers. ↩︎
I had an idea to create a proper Archive tag... doing so would have involved incorporating a tag look up and tag filtering in an already too-long-for-my-tastes SQL statement. ↩︎
I'm thinking this would be wildly complicated... and I couldn't even find a website that addressed the existence of this concept, let alone provide any sort of hint of how to do it. ↩︎