Exploring Indie Life: Reducing Friction by Analytics
One thing I wish I had added in the beginning itself when I sent the first TestFlight was analytics. It hardly took me 10 minutes to implement, and helps understand what the users use and what they do not.
I added analytics to Meshing today, my mesh gradient creation app. I have been using this particular service since it was AppTelemetry in 2021, I chose TelemetryDeck for this task.
Quick disclaimer: I am not affiliated with or sponsored by TelemetryDeck. These are my thoughts and experiences. However, you can use my referral code so we both get 100,000 signals free per month! To be honest, I won't mind them, haha.
Why Analytics?
I wonder why I would bother with analytics. Just ship fast, and break fast.
But I do want to know how the users, interact with Meshing. In the past, the data from the analytics has defintely been invaluable in helping me make the app better.
As an indie developer, I do not have a large team to bounce ideas off of or to test every feature extensively. It is mostly just me, my code, and my ideas. So, I need to reduce some friction here and insights from hundreds of users does help. A massive team of testers, all giving me feedback without saying a word.
When it came to choosing an analytics platform, I had a few requirements:
- It needed to be easy to implement. My time is precious, and I cannot afford to spend days integrating analytics, nor have a package that exponentially increases the build time.
- It had to provide meaningful stuff without overwhelming me with data.
Setting Up TelemetryDeck
Adding TelemetryDeck to Meshing was straightforward. I started by including it in my MeshingShared package:
dependencies: [
.package(url: "https://github.com/TelemetryDeck/SwiftSDK.git", branch: "main")
],
targets: [
.target(
name: "MeshingShared",
dependencies: [
.product(name: "TelemetryDeck", package: "SwiftSDK")
],
Next, I initialized TelemetryDeck in my app:
@main
struct MeshingApp: App {
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
init() {
TelemetryDeck.initialize(config: .init(appID: "045NO464-DNO3-3NOD-8NO4-D91FBNO7A32A"))
TelemetryDeck.signal("App.launched")
}
// ... rest of the app ...
}
I am also sending a signal to track each app launch, which gives me a basic idea of how often people are using Meshing.
What I'm Tracking
I am tracking most of the button clicks, but there are a few interesting ones:
Onboarding Duration: Based on a LinkedIn comment that got me curious, I decided to track how long users spend on onboarding:
public struct OnboardingView: View {
@Binding var hasCompletedOnboarding: Bool
@StateObject private var viewModel = OnboardingViewModel()
@State private var onboardingStartTime: Date?
public init(hasCompletedOnboarding: Binding<Bool>) {
self._hasCompletedOnboarding = hasCompletedOnboarding
}
public var body: some View {
// ... onboarding content ...
GetStartedButton(scale: $viewModel.buttonScale, opacity: $viewModel.buttonOpacity) {
withAnimation(.spring(response: 0.6, dampingFraction: 0.8)) {
hasCompletedOnboarding = true
if let startTime = onboardingStartTime {
let duration = Date().timeIntervalSince(startTime)
TelemetryDeck.signal("Onboarding.completed", parameters: [
"duration": String(format: "%.2f", duration)
])
} else {
TelemetryDeck.signal("Onboarding.completed", parameters: [
"duration": "unknown"
])
}
}
.onAppear {
onboardingStartTime = Date()
TelemetryDeck.signal("Onboarding.started")
}
}
}
This code snippet records when the onboarding view appears, tracks when the user completes onboarding and calculates and sends the duration of the onboarding process.
If I see that users are spending too long in onboarding, it might indicate that the process is confusing or too lengthy. On the other hand, if users are rushing through it, they might be missing important information.
Feature Usage: I am tracking is how users interact with different features in Meshing:
.onChange(of: viewModel.smoothsColors) { newValue in
TelemetryDeck.signal("SmoothColors.toggled", parameters: ["enabled": "\(newValue)"])
}
.onChange(of: viewModel.addNoise) { newValue in
TelemetryDeck.signal("NoiseEffect.toggled", parameters: ["enabled": "\(newValue)"])
}
.onChange(of: viewModel.showWireframe) { newValue in
TelemetryDeck.signal("Wireframe.toggled", parameters: ["enabled": "\(newValue)"])
}
.onChange(of: viewModel.blurRadius) { newValue in
TelemetryDeck.signal("BlurEffect.changed", parameters: ["radius": "\(newValue)"])
}
Each of these signals corresponds to a specific feature in Meshing:
- SmoothColors: This feature allows users to create smoother transitions between colors in their gradients.
- NoiseEffect: This adds a subtle noise texture to the gradient, giving it more depth and character.
- Wireframe: This feature overlays a wireframe on the gradient, which can be useful for certain design tasks.
- BlurEffect: This allows users to apply a blur to their gradient, creating interesting visual effects.
I want to see which features are popular and which ones might need improvement or better explanation.
But, Still, Why?
I will update the post when I have a data-driven answer to this question, but my assumption is that by seeing which features are used most often, I can prioritize my development efforts. If a lot of users are using the NoiseEffect, for example, I will add more options to it.
If I notice that many users are turning off a particular feature (like the Wireframe), it might indicate that the feature is not meeting their needs? Another assumption.
The onboarding duration data helps me ensure that new users are getting a smooth introduction to Meshing. It is a one-page onboarding so if they spending a few seconds on it, something is wrong.
While I have ont implemented it yet, in the future, I could use analytics to track app performance. For instance, I could measure how long it takes to generate complex gradients using Meshing AI.
Moving Forward
Well, this is just the beginning. As I learn more about what and which data are most useful, I will adjust what I am tracking to get better insights. Especially because I want to make Meshing AI a product on its own.
By reducing the friction, I can focus on making Meshing better for us.
Happy tracking!