Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blog: How we built mobile replay #9376

Merged
merged 23 commits into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions contents/blog/mobile-session-replay.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
---
title: How we built mobile replay (and why it took so long)
date: 2024-09-18
author:
- ian-vanagas
- manoel-aranda-neto
rootpage: /blog
featuredImage: >-
https://res.cloudinary.com/dmukukwp6/image/upload/posthog.com/contents/images/blog/posthog-engineering-blog.png
featuredImageType: full
tags:
- Guides
---

[Session replay](/session-replay) is one of the most powerful tools for understanding user behavior. Web session replay has been a core part of PostHog for a long time now (it was built in our first hackathon), but mobile teams have had to wait longer.
Lior539 marked this conversation as resolved.
Show resolved Hide resolved

Fortunately, it's finally here on [iOS](/docs/session-replay/ios), [Android](/docs/session-replay/android), and [React Native](/docs/session-replay/react-native) (with [Flutter](https://github.com/PostHog/posthog-flutter/issues/69) coming soon).
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

What took so long? Although we had the structure to ingest and playback replays, recording them in mobile apps is much trickier than in web apps. This post goes over why and how we finally managed to overcome them.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

## What's so difficult about mobile session replay

Others have complained about [the lack of good mobile replay options](https://medium.com/goodones/15-years-later-there-is-still-no-good-session-replay-for-ios-f8d335999737). Why is that the case?
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

### 1. Multiple platforms

The industry's big secret about web session replay is that it largely relies on a single open source library to work: [rrweb](https://github.com/rrweb-io/rrweb). It includes tools for recording web interactions and state changes, structuring session data, and playback.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

Unfortunately, rrweb for mobile doesn't exist. To build mobile session replay, we needed to do all the work ourselves, and when compared to the web, this is a lot of work. This is because, instead of a single JavaScript library, language, and SDK, mobile requires multiple (like iOS, Android, React Native, and Flutter).
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

There are even breaking differences within platforms. For example, Jetpack Compose uses a compositional model for UI, which is different from Android's traditional view-based model. This means you need to develop separate ways of doing replays when using it. iOS has a similar problem with SwiftUI versus UIKit.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

### 2. Performance

Phones are much less powerful than desktops. Because of this, we need to be much more sensitive about performance.

If you've ever tried to record your phone screen, you would know its impact on performance. Apps take longer to load, animations become choppy, reactivity degrades, and your phone heats up.

Anything that degrades your user experience is not an option for many developers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could do with a bit more caveating and context. Something about how the vast majority of phones aren't top-of-the-range iPhones. Your app needs to work just as well on $150 Android phone as a $1,500 iPhone 16 etc. That screen recording example is not universally true, so probably not a great example.


### 3. Privacy

A big difference between web and mobile apps is the DOM.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

In web apps, this provides a hierarchical tree structure that represents and uniquely identifies elements. The web also has standardized elements, like `<input type="password">`. Combining these makes it easier to identify, mask, and exclude sensitive elements on the web.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

Mobile doesn't have standardized structures or elements. Accessibility identifiers are also inconsistently implemented. This means identifying, masking, and excluding content is a lot trickier.

### 4. Testing
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

We're big fans of [dogfooding](/product-engineers/dogfooding) at PostHog. Often, we are our own best users, but we don't have a mobile app.

This meant that during development we relied on demo and open source apps. This risks creating something that doesn't work well for larger, production-quality apps.

Developing a high-quality mobile replay product means relying more on our users and their feedback.

## The prerequisites for mobile session replay

First, none of this would be possible without [Manoel](/community/profiles/30206). We had mobile experience, but not the dedicated mobile SDK experience needed for this big of a feature. Manoel had that experience and this was only possible thanks to him.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

When Manoel joined the first thing he worked on was [rewriting](https://github.com/PostHog/product-internal/issues/506) the SDKs in Kotlin and Swift, removing code we didn't use, improving tests, automating deployments, and making sure they worked with the latest platforms.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

We already had the other prerequisite, our existing session replay infrastructure. We had the data structure, the way to store replays, as well as a complete product for playback and analysis. All this could be reused for mobile replay.
andyvan-ph marked this conversation as resolved.
Show resolved Hide resolved

![Replay](https://res.cloudinary.com/dmukukwp6/image/upload/replay_03a8c56981.png)

Importantly, Paul and Manoel realized the mobile data needed to be transformed into a format the rrweb player could use. They wanted to keep the rrweb schema to ensure the fewest changes possible to our API and player. To do this, they wrote a [validation and testing tool](https://github.com/PostHog/mobile-replay-data-transformer) to rapidly test the transformations before deploying it in our main app.
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

Once this was done, everything was ready for the mobile replay capture to be worked on.

## How we built mobile session replay
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

Work started by developing a [proof of concept](https://github.com/PostHog/posthog-android/pull/69) for Android session replay. This developed from sending anything from an Android app and being replayed to basic components like text and images.

![PoC](https://res.cloudinary.com/dmukukwp6/image/upload/mobile_b85c032c93.png)

From here, there was wireframe capture along with logs and network requests. Afterwards, there were standard Android components like radios, checkboxes, Calendar, [Toggle](https://github.com/PostHog/posthog/pull/19279), RatingBar, and more.

These need to be transformed to render as an HTML wireframe (as rrweb expects). Many of them required custom transformation and components to render properly. For example, radio buttons didn't group, padding wasn't applied, and positioning was wrong.

Beyond this, fitting the data into a service meant for web caused many challenges. For example, click events weren't showing even though the types and data were the same as data that worked. After some investigation, we found that rrweb expects touch events to be associated with specific elements. Setting the ID to the `body` element was enough to fix it.

Once we got consistent and useful results from what we built, we recruited our first test users and iterated from there. Later, we followed a similar process for [iOS](https://github.com/PostHog/posthog-ios/pull/115).
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

## Solving mobile replays big problems
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

So how did we solve some of the big problems we identified earlier? Two were relatively simple:

1. **Multiple platforms:** Do the work to develop mobile replay for all the platforms (which is still ongoing).
2. **Testing:** Use open source and test apps to develop a proof of concept. Once complete, use our large user base to find users willing to test our prototype. Luckily, this feature had massive demand and there were users who were willing to try the earliest versions of it.

The other two have more clever solutions.

### 1. Performance

Not slowing down people's apps was core to our mission with session replay.

Our strategy to do wireframes is much less performance-intensive than others tools' reliance on screenshots. We still built screenshot mode, which provided a more accurate representation; however, we mostly focused on making our wireframe mode as good as possible.

![Screenshot vs Wireframe](https://res.cloudinary.com/dmukukwp6/image/upload/wireframe_78ce94bd4b.png)

Many performance issues users faced were either caused by screenshot mode or unsupported data being captured. Both of these were solved by making wireframe mode better.

On top of this, we try to offload as much work to our servers as possible. For example, the transformation to the rrweb schema happens on the server side.

### 2. Privacy
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

As for privacy, we built the ability to mask all text inputs and images as well as redact certain views with `ph-no-capture` like this:
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

<MultiLanguage>

```xml
<ImageView
android:id="@+id/imvProfilePhoto"
android:layout_width="200dp"
android:layout_height="200dp"
android:tag="ph-no-capture"
/>
```

```swift
let imvProfilePhoto = UIImageView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
imvProfilePhoto.accessibilityIdentifier = "ph-no-capture"
```

</MultiLanguage>

We added this functionality on both wireframe and screenshot mode. Although as nice as it would have been to have automatic masking like we do on the web, the ability to have masking at all enables privacy-sensitive teams to actually use replay.

There is more challenges to solve, specifically around [SwiftUI](https://github.com/PostHog/posthog-ios/issues/162) and Jetpack Compose, since the way they transpile code causes the representation to not be a 1:1 match and properties are lost.

## Making mobile session replay available for everyone
ivanagas marked this conversation as resolved.
Show resolved Hide resolved

In many ways, mobile has been neglected by the analytics industry. Tools like session replay have either not existed, been locked behind enterprise plans, or been too expensive for most developers.

We want to change this. Mobile replay is free while in beta, and once it's out of beta, we'll follow [our pricing principles](/handbook/engineering/feature-pricing), making it as affordable as possible.

This enables us to help more developers have the tools they need to build successful products.

## Further reading

- [The 80/20 of early-stage startup analytics](/founders/early-stage-analytics)
- [We decided to make session replay cheaper](/blog/session-replay-pricing)
- How to set up [Android](/tutorials/android-session-replay), [iOS](/tutorials/ios-session-replay), and [React Native](/docs/session-replay/react-native) session replay
8 changes: 8 additions & 0 deletions src/data/authors.json
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,14 @@
"link_type": "linkedin",
"link_url": "https://www.linkedin.com/in/igorkotua/"
},
{
"handle": "manoel-aranda-neto",
"name": "Manoel Aranda Neto",
"role": "Mobile Engineer",
"image": "../../static/images/authors/manoel.png",
"link_type": "twitter",
"link_url": "https://twitter.com/marandaneto"
},
{
"handle": "mathew-pregasen",
"name": "Mathew Pregasen",
Expand Down
Binary file added static/images/authors/manoel.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading