I’m Artem, one of the Android interns at Strava in San Francisco during Summer 2019. I’m from Canada and have studied at the University of Manitoba for my Bachelors of Computer Science for the past four years.
Team I’m on:
During the summer I was on the Experience team, which works on the experience for athletes including improving app startup time, increasing upload and recording reliability, etc.
What was my project?
My main project for the summer was working on adding a way to upload FIT files into our current uploader service for Android. It sounds pretty straightforward — if the current service can handle JSON, don’t you only have to change parts to FIT and you’re done? No, because the original service wasn’t designed to support JSON and FIT uploads at the same time.
Why switch to FIT?
FIT files are Garmin’s proprietary file type that contains information about an activity. We want to switch to FIT because it would help reduce the failures that can come from uploading an activity. This includes failures due to a poor network connection — a FIT file in size is smaller than a JSON blob of the same activity meaning that it won’t need as good of a connection.
Why is uploading an activity hard?
Uploading an activity is hard because it’s more than uploading a FIT file, or a JSON blob. From a high level, it’s a 2 step process. The first step is the activity upload, and the second is when the server recreates the activity from the upload. The clients have to handle the success, failure, and error cases for both steps.
What does a successful upload look like?
- Activity has been recorded and is written into the database.
- Upload Service starts and fetches activities to upload.
- For each activity to upload, convert it to either a FIT file or a JSON blob.
- Upload the activity in the FIT or JSON format. We receive an upload ID.
- Use the upload ID to poll the backend for the state of the activity being created.
- We receive an activity ID in the response once it’s created, then we delete the activity on the client and show a successful upload notification.
Why we aren’t using the current service:
We went with a completely new upload service, rewriting most of the logic used for uploading for several different reasons:
- Testing the current service is hard, and requires hacky solutions to inject dependencies for the tests to work.
- It’s not easy to understand what happens in our upload flow since we use two different queues that are managed by two separate classes. Our service has a queue for uploads, and our sync looper has the other which is used for polling the status of the uploaded activity. It requires remembering intricate details on which queue is used for a specific state, and when it’ll be switched between the two.
- We wanted something that’s more flexible in terms of being able to upload either a FIT file or a JSON blob.
- It’s hard to introduce any design changes.
- We wanted to avoid the risk of introducing any errors to the current service from changing the current functionality to accommodate FIT files.
New upload service requirements:
We had a couple requirements for our new upload service:
- Must be format agnostic, in the sense that we should be able to support both FIT file and JSON blob uploads
- JSON and FIT uploads should be independent
- Must be able to support different retry strategies in the case of failure
New upload service/flow:
Our new upload service and the upload flow now looks something like this:
For a rundown of our new flow, our new service is used only for starting our UploadManager.
Our UploadManager controls the upload flow across multiple threads. It handles the queue of activities to upload, and enqueueing upload and activity check status tasks into the TaskRunner. It also handles the results from the TaskRunner callback for if a task should be retried, if a success/failure notification should be shown, and more.
The TaskRunner manages a queue of Tasks that should be run. It acts as an intermediary step between receiving the result from a Task, and passing it to the listener that enqueued the Task.
What are Tasks?
Tasks are an abstract class that we use to define classes that can be run by our TaskRunner. There are 3 functions that are the core components of a Task:
- complete(result) — Indicates that the Task has finished running. It passes a message to the TaskRunner’s Handler with the result from running the Task.
- abstract run() — Used for allowing the specified task to start, with an expectation that it calls ‘complete’ once it’s finished running with an optional result.
- abstract getId() — Used to retrieve a unique String identifier for the type of Task. It’s important so that we can identify the different types of tasks in our UploadManager.
Types of Tasks:
We have 3 separate types that all use RxJava, and depend on helper classes to create an observable that handles the logic.
- UploadTask: We use JSONActivityUploader that converts an Activity into a JSON blob, which then is uploaded, and then we handle and return the result.
- FITUploaderTask: We use FitFileUploader that converts an Activity into a FIT file blob, which is then uploaded, and then we handle and return the result
- ActivityCheckStatusTask: We use ActivityCheckStatus which does an API call to check the status of an activity, and then handles and returns the result of the status.
With this new flow we’ve been able to have everything split up well enough that we can easily test any combination of pieces as needed without any hacky solutions. We’re also able to make JSON and FIT uploads completely independent, allowing us to change retry strategies for every task and result without any headaches.
My Strava Experience
Overall, my experience at Strava was fantastic. I learned a lot due to the amount of support that you receive even as an intern. I had a mentor that I could talk to about the issues I’m having, the way I’m approaching a problem, design choices, or how I should tackle an issue. The other great thing in my opinion is the weekly 1:1s with your manager, which is really helpful for feedback, asking questions, and anything else you’re interested in. Most importantly, the best part is that no matter the team, no matter the office people, are willing to help you.
Another part that stood out to me about Strava was that there are a lot of opportunities to learn — whether it’s through weekly tech talks by other engineers or weekly platform specific tech talk lunches. One of the most surprising things was what my main project for the summer, which made this experience fantastic by itself since I learned about the specific design choices. Finally, Strava Jams are a fantastic experience in having freedom to explore things you’d like to see in the app, and for being able to see the amazing ideas others can bring to life over 4 days.
Shoutout to my mentor Dave, Josh, Jason, Kevin, the Experience team, and all of the other Android devs that helped me a bunch throughout this internship.