●FUNDING — Rork raises $15M, drawing fresh attention to its mobile-first no-code AI positioning●MAX-NATIVE — Rork Max reaches native territory React Native can't: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and on-device Core ML●MOBILE-FIRST — While Bolt and Lovable focus on web apps, Rork builds mobile apps — production-ready from a plain-language description●WWDC — WWDC26 wraps with AI becoming a core OS capability; the iOS 27 generation raises the value of widgets and Live Activities●PRICING — Free to start, paid plans from $25/mo, Rork Max at $200/mo — ship fast on Expo, then go native with Max where it pays off●ALL-APPLE — Rork Max generates pure Swift covering iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●FUNDING — Rork raises $15M, drawing fresh attention to its mobile-first no-code AI positioning●MAX-NATIVE — Rork Max reaches native territory React Native can't: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and on-device Core ML●MOBILE-FIRST — While Bolt and Lovable focus on web apps, Rork builds mobile apps — production-ready from a plain-language description●WWDC — WWDC26 wraps with AI becoming a core OS capability; the iOS 27 generation raises the value of widgets and Live Activities●PRICING — Free to start, paid plans from $25/mo, Rork Max at $200/mo — ship fast on Expo, then go native with Max where it pays off●ALL-APPLE — Rork Max generates pure Swift covering iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage
When and How to Remove Features Nobody Uses — Auditing and Safely Retiring Functionality in Rork-Built Apps
Unused features quietly make an app harder to maintain. A field-tested playbook from running six wallpaper apps in parallel — how to measure feature usage, decide what to retire, and remove functionality in three safe stages with Remote Config.
One morning last week, I was triaging crash reports for one of the wallpaper apps I operate, and I had to stop and stare. Near the top of the list: crashes in the screen transitions of the slideshow feature.
I pulled up the analytics to check. The feature's 30-day usage rate was 0.7%. I was spending my best hours of the morning debugging something almost nobody used.
Building with Rork has made adding features dramatically cheaper. One prompt, one new screen. But the cost of removing a feature is as heavy as it ever was.
Running six wallpaper apps in parallel forced me to turn feature removal into a repeatable system rather than an occasional agonizing decision. Here is that system: how I measure usage, how I decide between removing, keeping, and burying a feature, and the three-stage retirement flow I drive through Remote Config.
Features Are Not Free Inventory — Why Deletion Is the Hard Part
When a feature ships, the cost is not just one screen's worth of code.
It is the combinations with existing features, the surfaces you re-verify on every OS update, the area reviewers look at, and the scope you have to explain in support. These multiply rather than add.
My slideshow feature touched four subsystems: navigation, timers, image prefetching, and sleep prevention. Every major iOS release, one of those four would crack a little. A feature with 0.7% usage was consuming roughly a tenth of my pre-release verification time.
At some point I started counting features on the liability side of the ledger instead of the asset side. The question becomes: "Is this feature worth its maintenance fee?" Reframing it that way sharpens the audit considerably.
In the era of AI builders like Rork, this question matters more, not less. When adding is easy, features pile up without anyone deciding they should.
Start With a 10-Minute Feature Inventory — Before Writing Any Code
Before instrumenting anything, do an audit you can finish with nothing but a text editor. Three steps:
List every screen and menu item in the app (most apps land between 10 and 20)
Next to each, note the last date you personally used that feature
Add any crashes, support tickets, or review mentions from the past three months
Doing this across six apps gave me 47 entries. About a third of them had no instrumentation that could tell me whether anyone used them.
A feature I have not touched in three months is usually a feature users barely touch either. That heuristic has rarely failed me — but gut feeling alone is a dangerous reason to delete something, so any uninstrumented feature gets the measurement code below before I judge it.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A minimal implementation for measuring per-feature usage with one event and a parameter, not an event explosion
✦Decision criteria for remove vs keep vs bury, plus working code for a three-stage retirement flow driven by Remote Config
✦A pre- and post-removal checklist covering deep links, orphaned data, and store listing assets
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
The Minimal Usage Measurement — One Event, Split by Parameter
When measuring feature usage, I avoid creating a dedicated analytics event per feature. The analytics console turns into an event junkyard, and six months later you cannot read it.
I fix the event name to a single feature_use and distinguish features with a parameter. This code pins the measurable features down with a type and keeps a monthly local tally:
// featureUsage.ts — feature usage tracking (one event + parameter)import AsyncStorage from '@react-native-async-storage/async-storage';import analytics from '@react-native-firebase/analytics';// Lock the measurable features into a type (prevents typos and stray events)export type FeatureId = | 'slideshow' | 'favorite_export' | 'double_wallpaper' | 'search_filter' | 'widget_config';const KEY_PREFIX = 'feature_usage_v1';function monthKey(date = new Date()): string { const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); return `${KEY_PREFIX}:${y}-${m}`;}export async function trackFeatureUse(id: FeatureId): Promise<void> { try { // 1) Send to the analytics backend (fixed event name, feature as parameter) await analytics().logEvent('feature_use', { feature_id: id }); // 2) Keep a monthly count on-device too (handy in a debug menu) const key = monthKey(); const raw = await AsyncStorage.getItem(key); const counts: Record<string, number> = raw ? JSON.parse(raw) : {}; counts[id] = (counts[id] ?? 0) + 1; await AsyncStorage.setItem(key, JSON.stringify(counts)); } catch { // Never let measurement failures break the app itself }}
The call site is a single line at the feature's entry point:
Why only the entry point? Because a retirement decision needs exactly one number: the share of users who open the feature. Once you start measuring every interaction inside the feature, the measurement itself becomes a maintenance burden.
On the analysis side, break feature_use down by feature_id and divide by monthly active users. My first cut line is a 30-day usage rate of 1%.
After one or two months of data, it is decision time. My criteria are deliberately not a single axis of usage rate.
Under 1% 30-day usage, and the people who do use it use it rarely — a removal candidate. It is working for no one
Under 1% usage, but its users open it almost daily — keep it. Move it out of prime menu real estate and into the depths of settings. Features carried by a few heavy users hurt your reviews far more than the raw usage rate suggests
Low usage, but featured in your store listing — resolve the mismatch with screenshots and description first, then decide
Any usage rate, but it breaks on every OS update — the only options are removal or a rebuild. Half-hearted life support is the most expensive path of all
The slideshow fell into the first bucket: 0.7% usage, and even its users opened it less than twice a month. Meanwhile, a dual-screen wallpaper setting with similar usage stayed — its users touched it five or more times a week, so it moved deeper into settings instead of dying.
Numbers carry the decision most of the way, but the final push is a more personal question: do I want to keep maintaining this? In indie development, your own motivation is a finite resource too.
Retire in Three Stages — A Remote Config Driven Flow
Even once a feature is condemned, I never delete it in the next release. The retirement happens in three stages:
deprecated (notice period) — a "retiring soon" badge appears on the entry point, and opening the feature shows the planned end date. Two to four weeks
removed (entry point gone) — the menu item disappears. The code stays
code deletion — the next store release physically removes the code and its dependencies
Driving the stage through Remote Config means you can advance without waiting on store review — and roll back instantly if something goes wrong.
// useFeatureStage.ts — control a feature's lifecycle stage via Remote Configimport { useEffect, useState } from 'react';import remoteConfig from '@react-native-firebase/remote-config';export type FeatureStage = 'active' | 'deprecated' | 'removed';export function useFeatureStage(featureKey: string): FeatureStage { const [stage, setStage] = useState<FeatureStage>('active'); useEffect(() => { remoteConfig() .fetchAndActivate() .then(() => { const value = remoteConfig() .getValue(`stage_${featureKey}`) .asString(); if (value === 'deprecated' || value === 'removed') { setStage(value); } }) .catch(() => { // On fetch failure, stay active — never kill a feature over a network hiccup }); }, [featureKey]); return stage;}
The entry point renders according to the stage:
const stage = useFeatureStage('slideshow');if (stage === 'removed') { return null; // Hidden from the menu (the code still exists)}if (stage === 'deprecated') { return ( <MenuItem label="Slideshow" badge="Retiring soon" onPress={openSlideshowWithSunsetNotice} /> );}return <MenuItem label="Slideshow" onPress={openSlideshow} />;
Why three stages? Because deleting in one step turns even a barely-used feature into "it vanished overnight" distrust in your reviews. Since adopting the notice period, across all six apps I have not received a single one-star review caused by a feature retirement.
The failure default matters too: falling back to active on a failed fetch is deliberate. Flip it the other way and users in airplane mode watch features disappear. Defaults always sit on the "keep things as they are" side.
The Removal Pitfalls — Debris Outside the Menu
Removing the menu item does not remove every door into the feature. These are the ones I have actually tripped over.
Deep links and widgets. Old announcement notifications and home screen widgets can still navigate straight into a retired screen. Catch it at the routing entry point and redirect home gracefully instead of crashing in silence:
// Route requests into retired screens back to homeif (route === 'slideshow' && stage !== 'active') { router.replace('/'); showToast('The slideshow feature has been discontinued'); return;}
Stored data. Feature-specific data — playback intervals, playlists — becomes orphaned the moment the code is gone. It is small, but if a migration routine keeps referencing dead keys, it will confuse every future debugging session. My rule: purge the orphaned storage two versions after the code deletion ships.
Store listing assets. Screenshots or descriptions showing a removed feature mislead users and can draw reviewer attention during app review. The asset audit happens together with the removal submission, every time.
Release notes. It is tempting to ship the deletion quietly. I write one honest line instead: "The slideshow feature has been discontinued. To those who used it, I am sorry to see it go." Telling people first has consistently produced calmer reviews than being found out later.
Measure After Deletion — Success Looks Like Nothing Happening
Retirement does not end at deletion. For about 30 days, I watch four numbers:
Related crash count — it should be zero now
Support tickets and review mentions — for the slideshow, two tickets, one of them a restoration request
Retention and DAU — unchanged means success; a drop means you missed heavy users your measurement could not see
Dependency count and build size — I was able to drop the sleep-prevention dependency entirely
The success signal of a removal is not a dramatic improvement. It is the absence of any change: retention flat, crashes down, verification time shorter. Unglamorous, but that is the profit of a feature audit.
The reclaimed time goes back into the features people actually use. The hours that used to keep the slideshow alive shipped two improvements to search — a feature with 45% usage. Investment in a 45% feature repays far more reliably than investment in a 0.7% one.
The first step is neither measurement nor architecture. It is writing the list. Sometime this week, write down ten features in your app and recall the last time you touched each one. If even one of them is older than three months, this playbook has work to do.
If you have read an article about deleting features all the way to the end, you are almost certainly someone who intends to grow an app for the long term. I hope your audit turns out to be a satisfying tidy-up.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.