Managing your monthly subscriptions shouldnโt be a mess of reminders and scattered notes. That's the idea behind SubXtract โ a thoughtfully designed subscription tracking app that helps users keep track of their recurring expenses in one simple place.
But behind the clean UI and user-friendly design lies a robust offline-first tech stack, built to ensure performance, reliability, and a delightful experience even without internet.
In this post, Iโll walk you through what SubXtract is, what tech powers it, and how I implemented key features using Expo, Zustand, MMKV, and Reanimated.
SubXtract helps users:
With an elegant UI and local-first architecture, SubXtract focuses on privacy, speed, and user control.
SubXtract is built with Expo for rapid development, native APIs out of the box, and easy deployment. I used the Bare workflow when necessary, especially for MMKV and notification features.
Why Expo?
SubXtract stores all user data locally on the device using a combination of Zustand (for state management) and MMKV (for super fast key-value storage).
// storage.ts
import { MMKV } from "react-native-mmkv"
export const storage = new MMKV()
import { create } from "zustand"
import { persist } from "zustand/middleware"
import { storage } from "./storage"
export const useSubscriptionStore = create(
persist(
(set) => ({
subscriptions: [],
addSubscription: (sub) =>
set((state) => ({
subscriptions: [...state.subscriptions, sub],
})),
toggleRenewal: (id) =>
set((state) => ({
subscriptions: state.subscriptions.map((s) =>
s.id === id ? { ...s, autoRenew: !s.autoRenew } : s
),
})),
archiveSubscription: (id) =>
set((state) => ({
subscriptions: state.subscriptions.map((s) =>
s.id === id ? { ...s, archived: true } : s
),
})),
}),
{
name: "subxtract-store",
storage: {
getItem: storage.getString.bind(storage),
setItem: storage.set.bind(storage),
removeItem: storage.delete.bind(storage),
},
}
)
)
This setup ensures lightning-fast access to your app's state, even when offline.
To give users a fluid and polished experience, I used React Native Reanimated for subtle animations across components like the add/edit modal, theme transitions, and button presses.
import Animated, {
useSharedValue,
withSpring,
useAnimatedStyle,
} from "react-native-reanimated"
const offset = useSharedValue(0)
const animatedStyle = useAnimatedStyle(() => {
return {
transform: [{ translateY: withSpring(offset.value) }],
}
})
Users can select between light, dark, or system themes. A Zustand store syncs the selected theme across the app, and I use context + MMKV to persist it.
export const useThemeStore = create(
persist(
(set) => ({
theme: "system",
setTheme: (theme) => set({ theme }),
}),
{
name: "theme-settings",
storage: {
getItem: storage.getString.bind(storage),
setItem: storage.set.bind(storage),
removeItem: storage.delete.bind(storage),
},
}
)
)
SubXtract notifies users about upcoming payments based on their billing cycle. This is implemented using Expo Notifications and scheduled jobs at the time of adding a subscription.
If you like what you see, don't forget to check out SubXtract on Product Hunt and show your support!
Iโm actively working on adding:
These features will make SubXtract even more powerful and insightful for users managing multiple recurring expenses.
SubXtract is built with care to provide a fast, offline-first, and privacy-respecting experience for tracking subscriptions. Whether you're a minimalist user or someone who manages a dozen recurring payments, this app has you covered.
If you're curious to try it out, download it now on the App Store.
Thanks for reading! ๐
Happy tracking!