Slopes Diaries #38: Android
Slopes Diaries is my ongoing journey to turn my indie app into a more sustainable part of my business. First time reading? Catch up on the journey so far.
What is Slopes? Think Nike+, Runkeeper, Strava, MapMyRun, etc for skiers and snowboarders.
Well, it finally happened. The "1.0" for Slopes launched in the Play Store yesterday.
I've tweeted here and there about the journey over the last year+, even chatted about it on a podcast, but I haven't written much about it since I started. I thought now, with hindsight, would be a good time to talk about how it went.
Buckle up.
Going Native
Before the project had even started around Aug of 2019, I knew I wanted to avoid React as a cross-platform shortcut. Part of what makes Slopes on iOS great is, as a user put it, it's "the ski app apple would write" (high praise, I don't think Slopes is that good, but thanks!). Spit-and-polish on the UI, tight integration with the system through things like CoreLocation HealthKit and more, and feeling 100% at home with native controls.
Plus, having to run everything through a JS interpreter isn't exactly great for my 12hr battery life target 😅
(To avoid a #holywar, I do think React apps can be good, and I do think they have their place. Tragically React is often presented as a cost-savings solution, which means the polish needed for a great-feeling app is usually not a priority to begin with.)
Slopes on Android was going to have the same polish it does on iOS. I wanted to build the app Google would write. I think Android users get the short end of the stick a lot, and they deserve quality apps that aren't just shortcut-fueled copy/pastes of an iOS app. So Google's Material design framework, Kotlin language, everything you'd expect from an Android native app.
I did briefly debate porting my recording engine to C++ or some such so I could share some of that cross-platform, but ultimately didn't. Nothing stood out as great for cross-plafrom code (Kotlin native was interesting, but I'd need it to run on WatchOS and it was all still alpha level stuff) and that would add a lot of risk to the iOS app if adopted.
Android MVP
With the stack chosen, the next step was figuring out "what is the 2020 MVP of Slopes?" I wasn't naive, I didn't think I'd be able to get all of Slopes out as a 1.0 on Android. I'm a big fan of iterating and learning, so I knew I wanted to get a core version out and go from there. Plus I wanted to launch it for the 2020/21 season, so unless I had a full-time dev, that just wasn't do-able.
This meant cutting a lot. Some things were easy, there was no platform equivalent. The Photo Library integration on iOS, the heart rate / HealthKit integration, Siri integration. 3D was absolutely off the table, it was all written in Apple's SceneKit, same with AR / ARKit. And more.
But even without all that, there is still a lot to Slopes. Thankfully I'm pretty good at sniffing out what has to be in release vs what is optional. Examples of things I didn't need for v1?
- The timeline editor letting users add/remove lifts/runs, incase they leave Slopes running when they drive away. They can always fix this data later. Having this in place really helps customer support, lets users fix a lot of their own problems, but I can absorb that temporary hit to support.
- The entire social share card generator. People are more than happy to screenshot the app itself to share stats (and many iOS people do this despite my share creator lol).
- The run comparison feature I added last season. It is unique to Slopes, letting you play back two runs side-by-side on maps and watch stats.
- My entire "trip" support. Letting users group multiple days into a single trip, so they could see stats aggregated for multiple days.
- The lifetime & season screens. Similar idea of showing stats for multiple days.
- My recording reminder notifications (geofence and time-based).
- Localization. I lived for 7 years without it on iOS, Android could go a year or two.
- Not to mention a bunch of the infrastructure things in Slopes on iOS like migrations between versions, what's new highlighting, and stuff like that.
I figured I needed:
- The ability to record (and the entire Slopes recording engine)
- To view those recordings
- The sync engine to save the recordings online
- The social stuff I had built out for leaderboards (adding/removing friends)
- Account management
- And purchases / IAPs (thankfully all my purchase logic is server-side, so it was mostly just getting the order through Google Play and pushing it up to the server).
Takeoff, Then a Crash Landing
So summer of 2019 I put out a tweet talking about needing a native Android developer. The pickings were a little slim though. I think as a byproduct of Android being an afterthought for many companies, there isn't as healthy of a consulting ecosystem on Android, especially when looking at going all-native / Kotlin / etc. Had a lot of very part-time hobbyists that hadn't really shipped much, or only had a few hours a week to offer.
One resume stood out though, was a senior engineer who just left their job to start freelancing. I'd be his first client. We chatted, he seemed to have a good head on his shoulders and wasn't setting off any of my red flags. Apps he referenced working on were pretty good. So we agreed that 2019 would be partial hours to start, with more ramping up in 2020 (once the season started and Slopes earned $$ again). We got started with the first commit to the project on July 19 2019.
I ran the project like I normally lead - I have opinions, but ultimately I like to trust people to do what they are hired to do. I like to avoid dictating everything and instead defer to them to tell me how they want things to work. After all, they are the expert. So he was given a list of v1 features, but I left it to him to pick what to work on first, etc. He set his own hours of course. I stayed pretty low-touch, making myself available for any questions, and only asking for a twice-a-month call to go over progress to start.
Between August and end of December I was pretty distracted with iOS (social features, pricing experiments, etc) so I wasn't watching like a hawk, but things seemed to be moving along. Communication wasn't the best, and time was wasted down rabbit holes because he didn't want to bother me even though I told him to, but whatever. He did oversell himself a bit on his ability to help translate the iOS design to Google's Material design framework, so I took up that responsibility.
In January things started turn sour though. We switched to weekly progress reports since the contracted hours increased a lot, near full-time. I was stood up for those meeting 5 or 6 times before the end of March? Progress was feeling a behind where I'd expected it to be (which I didn't make a big deal of, he's the Android senior engineer and I was trusting him. Software always slips). But annoyingly deadlines I asked him to set himself were being missed too.
All along I was asking him to rectify all of this, but I was too soft. I don't like conflict, so I was too eager to trust him when he said things would improve for the 4th time.
Where shit hit the fan though was an opportunity with Google. Slopes got invited to a Material workshop to be hosted in late March, helping a few apps make the most of their design framework in-app. This was huge for me being able to make Slopes feel like a Google app. Since the workshop was both code and in person, we agreed we needed the major 3 screens (logbook, daily summary, timeline) mostly in place code-wise for feedback by the workshop.
Leading up to the in-person workshop, things kept slipping to get those 3 screens done. But then COVID happened, and the workshop turned virtual and was pushed back a few weeks, buying more time. The week of the new date (workshop was on Thurs) we were to have a call Monday to finalize having everything finished in time (which was still "totally doable" w/ the weekend before) and ... he stood me up. Worse, I couldn't get a rescheduled date out of him. I couldn't get any comments about the code out of him. I chased him for a day trying to reschedule the meeting for the next day and got ... nothing.
So here I was 24hrs before the Google workshop, with one screen completed, another "close to finished" but hardly working and the code wasn't even pushed to GitHub for me to see or present myself to Google. At that point I didn't even know if my developer would show up to the Google meetings, hell I had ever right to assume he wouldn't.
All this for the low-low price of $74,713.
So I did what I should had done probably some time before then - issued a stop-work the morning of the workshop. He was introducing too much additional risk to the project, and crippled me for a major Google opportunity. I had my designs to get feedback on, but half the workshop was code, and I had to completely miss out on that.
It was super embarrassing.
But that stop-work finally got him to call me 😅
I found out that his personal life was spiraling with someone he cared about suffering from new metal health issues, and not getting help. All those missed meetings were either him finally getting sleep, or him dealing with caring for them. And as he was new to consulting, he did what many first-timers do: assume if they just work extra hard "this week" they'll be able to catch up. No need to raise a flag and possibly piss off the client, just work harder and they'll never know the difference.
So I get it. It is an honest mistake many people make, myself included.
This failure was on me, though.
When I interviewed originally I was focused on finding a good engineer - one that knew how to write good code, but wouldn't over-engineer and knew how to balance product + code. And he was certainly, well mostly, that.
But my failing was assuming everyone had the same respect and communication habits I had built up when I was an iOS consultant. I respect the hell out of people's time, and if you set a meeting, I won't miss it (I'm the guy to show up 1min early to Zoom calls). If I have to miss the meeting, I let you know ASAP and don't leave you hanging on a zoom call for 20min, and I'm the one proactively offering reschedule times/dates, you don't have to chase me to reschedule. If I'm worried a feature is slipping, I raise that flag early so my lead/boss/client can factor that into their plans.
But that's me, a pretty seasoned consultant. I was stupid to not look for the red flags that'd affect project management. Especially since I knew I was going to be his first client, of course these were a risk.
I got through the workshop, learned a lot of frustrating things about designing for Android (buy me a drink sometime), and had to regroup.
I felt super deflated to be honest. We had moved to a fixed-price monthly contract in Jan 2020, and he didn't have to track hours anymore. I think with his personal life spiraling he might not have come close to the hour commitment (it is easy to over-estimate if you worked 35hrs this week without a normal schedule and your life upside-down).
Honestly, I think this whole fiasco wasted me $20,000. But hey, one major screen was done and I had a working sync engine (or so I thought).
Getting Back on Track
At this point it is May and COVID is a full-swing thing. Ski resorts shut down in mid March, months early, costing me a lot of revenue. Who knew what 2020/21's season would be like? 2019/20 was already rough due to terrible early-season conditions + COVID, for all I knew ski resorts could be shut down for all of 2020/21's season too. I had enough in the Slopes bank to keep the business running until Oct 2021, even if the upcoming season was a total bust. But that wasn't not enough to get me to Jan 2022 when I'd get my first major check from Apple for the 2021/22 season.
That impending doom actually made the decision to give Android another go a bit easier - either way, Android costs or no Android costs, Slopes was looking at an empty bank account mid-2021 if the season was a COVID bust. Android alone wasn't going to kill me. 😅
What also helped was when I put out the call for Android developers (again) I got 2 much better candidates this time. A good agency promising Android for ~$80k, and a part-time independent dev promising delivery for a little over half that. He was double the original developer's rate, but at the same time the amount of time he thought it would take to finish was surprisingly low. I was skeptical as a result, seemed classic under-estimation, but had a call anyway because he was impressive on paper.
And we hit it off, like "I want this guy to be my coworker" level.
Thankfully it turned out a friend of Paola knew him from his previous job (he works full-time on a popular meditation app now) and was able to get me references from 3 people that were all glowing including in the areas of communication, project management, and estimation. One reference was from a director-level person (when a director knows about an individual contributor, that's a good sign). That put my mind at east and over-the-top in moving forward with him.
His first commit was June 25th 2020.
Delivering
There were a few surprises as we got moving, for example that sync engine I thought I had on Android was a read-only sync engine. The whole "upload new activities, upload changes to activities, and delete activities" bit wasn't even written yet. 😅 But the foundation the first dev had built was at least good, code didn't have to be thrown out. Just a bit more had to be written than expected.
But DeRon got to work, and those 3 hero screens I wanted for the Google workshop came together fast. He was moving fast, but importantly what he was building worked great. He was as good as I had been lead to believe. He was even able to step in and take the reigns on translating the iOS design to Material (within the theme of what I had already worked out, but still, I got to stay hands of there and didn't have to spell out everything). He even care about the little things, like transition animations.
And let me tell you it felt good to have some screens working on my Pixel 3a that I could play with. It went from almost giving up to "OMG this is Slopes" inside a month.
Most of the app, sans the recording screen / engine, was done by early October. I had originally hoped to launch Slopes in Early Access (Android's public TestFlight) on the Play Store early Nov along side the big iOS update that added the resorts tab, but we ended up having to push that beta a bit. The recording engine landed in time, but we wanted a few more weeks for that "last 10%" that always takes longer than expected (which of course meant a bit more budget).
But honestly, this was playing out exactly how I'd expect a software project to play out. A little bit of under-estimation of how hard something would be, a little over-estimation of how much the original developer had completed, and sprinkle in my high standards for polish. But all within my expectations running software projects.
We launched Slopes into Early Access in the first few days of December. That gave us (finally) lots of real-world tests of the recording engine and insights into the hardware differences with Android phones and GPS. Of course it a few bugs, which took 3 updates to get all the major ones. Users were super happy to see Slopes on Android, very forgiving of the bugs, and excited to see us committed to shipping updates and fixing things quickly.
The nice surprise was that I asked DeRon how he felt about getting the Resorts tab (the thing that just launched on iOS) in as part of the "1.0" for Android in late Dec / early Jan, and he said totally lets do it.
And he did it.
It wasn't even on the original scope (it didn't exist on iOS during scoping 😅), but this additional tab really helped flesh out Slopes on Android. It really felt complete with it in place. And honestly, I think the premium offering needed it (offline trail maps for resorts if you are a subscriber). Without resorts, the premium offerings felt a little lacking.
1.0 and Beyond
Yesterday (Jan 5, 2021) Slopes for Android left beta and released its "1.0". After starting the project back in fall of 2019, and many ups and downs, we're here.
The 1.0, including the first dev fiasco and increased scope, ended up costing $150k (he still came in under the agency quote, even with more scope 🥳) over the course of a year and a half. My hope for Android all along has been getting it to the point where it can pay for itself. It won't be a big money-maker like iOS, but I felt it could probably make enough to pay for the dev working on it. Out of the gate though, this is all iOS paying for Android, and I knew the 1.0 was a sunk cost.
There's a lot of catching up with iOS to do, this won't be one of those "ship it and forget it" Android apps that my competitors have shipped. You saw the list above of how much was left on the editing floor. I wanna see Slopes on Android hit all those things, and more. 1.0 + a bugfix release in two weeks is not the end of it.
My experience working with DeRon has been really good to date, and he is clearly a senior engineer that's capable of a bit more product leadership than I think he's been given opportunity for in the past. So I proposed something...
- Slopes/iOS will guarantee a minimum amount per year to him, which pays for a (pretty large) retainer of hours for the year at a discount. That retainer is enough to move Android forward: bug fixes and maybe a feature or two. But it only covers about 1/3 the hrs per month he had been working to ship 1.0.
- Assuming he works any more hours than those, he gets the Android revenue (up to a cap we agreed on, he won't be making 1mil/yr off this 😅, eventually I get some of the revenue again). That helps soften the blow to him of Android being in its early years (since subscription businesses can take a bit to get off the ground). But gives him the room to earn more than he could consulting alone, and start to divorce the time <-> money relationship that comes with consulting.
- I can't give him the risk of Android's performance without responsibility, so he gets to figure out the roadmap. I get veto power, but he's shown to have a pretty good eye for managing this so far. And he gets the iOS roadmap as a cheat guide to accelarate the Android business.
This works out for me because iOS is now just responsible for the retainer (which is quite a chunk, but a lot more managable than $150k). And then I just don't rely on Android money going to me anytime soon. It'll take a year or two to get there, but he has the headway to make a lot more than he could consulting.
We negotiated all this over the course of December and came to an agreement we're both happy with. 🥳
So yeah, I'm pretty jazzed. Not only did I ship a 1.0, I have a plan forward for Android so those users aren't neglected. Hopefully now that Slopes it out of Early Access it'll get some more attention / downloads and things will start getting a move on over there.
Honestly didn't expect to be in such a good position after where I was in April.
But I'm glad I didn't give up, my calculated risk of continuing Android seems to have worked out. The season is going OK so far, thank goodness, so I'll likely survive 2020/21's winter and last until 2021/22 when things should be back to normal. And now I'm in a much stronger position for next season with Android in place and launched. Having Android unlocks a lot of opportunities I couldn't have touched before when it comes to working with other businesses (like for example the Red Bull partnership Slopes did).
It was a very scary, very calculated risk to take, but it worked out. 🎲