u/Illustrious_Rain_387

I built this because I thought it could be useful for me.

I have tried journaling many times in my life and quit every single time within 1–2 weeks. By 10pm I was tired and the last thing I wanted to do was sit down and write. The friction wasn't the journaling itself — it was the writing.

So I built a voice journaling app.

How it works

- Tap the orb, speak for 30 seconds to 3 minutes about your day
- The app turns it into a clean journal entry written in your own words
- It detects your emotions (calm, anxious, proud, tired, etc.)
- You get a daily mood score from 0 to 100
- Over time, a 90-day heatmap appears on your home screen widget so you can spot emotional patterns

It works for people who hate writing journals (like me) but still want the benefits of reflection.

Pricing

- 3 free entries to test
- €4.99/month or €34.99/year after that
- 7-day free trial if you commit to a plan

App Store
Voice Journal — AI Voice Diary

I am open to feedback. The app is really new and I know it still needs to get better — what would you want to see in something like this?

Thank you all.

Important note: If you start the free trial just to test the app, don't forget to cancel it from AppStore → Profile (top right) → Subscriptions so you don't get charged later.

u/Illustrious_Rain_387 — 22 days ago
▲ 9 r/expo

Posting this because I lost like 2 hours on it last week and Google was useless.

I'm using expo-widgets for an iOS home screen widget. iOS works fine. First Android EAS build I made — app dies the second it opens:

FATAL EXCEPTION: mqt_v_native
com.facebook.react.common.JavascriptException: Error: Cannot find native module 'ExpoWidgets'

The widget code never runs on Android. I never call syncWidget from anywhere Android touches. But the import statement at the top of the file is enough to blow it up — expo-widgets calls requireNativeModule('ExpoWidgets') at module load, doesn't matter if you never use the export.

First instinct was the usual ugly hack:

if (Platform.OS === 'ios') {
  // can't even put the require here, top-level imports already happened
}

That doesn't help. By the time you check Platform.OS, the import at the top of the file already ran and crashed.

Second instinct, also ugly:

let syncWidget = (_: Entry[]) => {}
if (Platform.OS === 'ios') {
  syncWidget = require('./widgetSync.ios').syncWidget
}

Works but every callsite has to know about it, and Metro can't statically analyze it.

The clean fix is something I forgot RN had: platform extensions. Metro's resolver picks the right file based on Platform.OS automatically. So:

lib/widgetSync.ios.ts       <- imports expo-widgets, real impl
lib/widgetSync.android.ts   <- no-op stub

widgetSync.ios.ts:

import MindScoreWidget from '@/widgets/MindScoreWidget'
import type { Entry } from '@/types'

export function syncWidget(entries: Entry[]) {
  // ... actual widget update
}

widgetSync.android.ts:

import type { Entry } from '@/types'
export function syncWidget(_entries: Entry[]): void {}

Every callsite just keeps doing import { syncWidget } from '@/lib/widgetSync'. No suffix. Metro resolves widgetSyncwidgetSync.android.ts on Android, widgetSync.ios.ts on iOS. Native module never gets referenced on Android. Bundle is smaller too because the iOS-only deps are tree-shaken out of the Android build.

Same trick works for anything iOS-only that does native registration at import time. I think I have it now on expo-widgets, the WidgetKit-related parts of react-native-purchases-ui if you ever touch them, and probably expo-apple-authentication would be a candidate too if you didn't have the official conditional support.

Posting in case someone hits the same wall.

reddit.com
u/Illustrious_Rain_387 — 22 days ago

Voice Journal is a daily journaling app where you talk instead of write.

I built it because I tried written journaling 10+ times and quit every single time within 2 weeks. The friction wasn't the journaling itself — it was the writing. So I made the version I actually wanted to use.

How it works

  1. Tap the orb, speak 30s to 3 min about your day
  2. The app transcribes your voice
  3. It rewrites your thoughts as a clean journal entry (in your own words)
  4. Detects emotions (calm, anxious, proud, tired, etc.)
  5. Gives you a daily mind score from 0 to 100
  6. Builds a 90-day mood heatmap on your home screen widget so you can spot patterns

Why the widget matters

The widget surfaces your last 90 days of entries as a heatmap on your home screen. Brighter cells = better days. After a few weeks you start seeing patterns ("Sunday evenings I crash", "I feel better after gym days") — patterns you'd miss if entries were buried in an app.

Pricing

  • 3 free entries to test (no card required)
  • €4.99 / month or €34.99 / year
  • 7-day free trial for paid plans

Links

Stack (for the curious)

React Native + Expo SDK 55, Supabase Edge Functions, ElevenLabs Scribe (STT), Gemini 2.0 Flash (analysis), RevenueCat (IAP).

Built solo in 3 weeks. Brutally honest feedback welcome — what works, what feels gimmicky, what features would make it a daily-use app for you?

Thanks 🙏

u/Illustrious_Rain_387 — 23 days ago

Just shipped my voice journaling app to appStore and on testing for android after 3 weeks solo dev. Sharing the non-obvious traps I actually hit — every one of these cost me hours or a rejected build.

Stack

  • React Native via Expo SDK 55
  • Supabase Edge Functions (Deno) for backend
  • ElevenLabs Scribe (STT) + Gemini 2.0 Flash (analysis)
  • RevenueCat for cross-platform IAP
  • expo-widgets for iOS WidgetKit
  • Reanimated 3 + react-native-svg for animations

1. iOS-only native modules crash Android at import time

Added expo-widgets for an iOS home screen widget. Worked great on iOS. First Android build → instant crash:

FATAL EXCEPTION: mqt_v_native
Error: Cannot find native module 'ExpoWidgets'

The widget code was never called on Android. The import alone at the top of MindScoreWidget.tsx was enough — expo-widgets calls requireNativeModule at module load.

Cleanest fix: platform-specific file extensions. Metro auto-picks based on Platform.OS.

lib/widgetSync.ios.ts      → full impl, imports expo-widgets
lib/widgetSync.android.ts  → no-op stub, no import

Existing call sites stay identical: import { syncWidget } from '@/lib/widgetSync'.

Cleaner than Platform.OS === 'ios' && require(...) because the bundler can statically analyze and the native module never gets referenced on Android.

2. RevenueCat with one shared API key silently breaks Android

Had this in my code for months:

const API_KEY = process.env.EXPO_PUBLIC_RC_API_KEY ?? ''
Purchases.configure({ apiKey: API_KEY })

On iOS, the appl_xxx key worked. On Android, same key → RC threw:

ConfigurationError: None of the products registered in the
RevenueCat dashboard could be fetched from the Play Store.

The iOS key cannot fetch Google Play products. Different stores need different SDK keys (appl_xxx for iOS, goog_xxx for Android).

const API_KEY = Platform.OS === 'ios'
  ? process.env.EXPO_PUBLIC_RC_IOS_KEY!
  : process.env.EXPO_PUBLIC_RC_ANDROID_KEY!

Spent an hour blaming Google Play permissions. It was a 1-line code bug.

3. Google Play Billing doesn't work on sideloaded builds

Burned more time on this. Built a dev APK via EAS, installed via direct download link, tap Buy → "Achat impossible" with no useful error.

Google Play Billing only validates apps installed via Play Store (internal testing track is fine). Sideloaded EAS builds get silently rejected because the package signature doesn't match what Play knows.

Workflow that works:

  1. eas build --platform android --profile production (production AAB)
  2. Upload to Internal Testing track in Play Console
  3. Add your test Google account to the testers list
  4. Open the opt-in link on the phone, "Become a tester"
  5. Install from Play Store, not from EAS link
  6. Now sandbox purchases work

4. iOS-Android pricing parity breaks because of VAT

Set the same €34.99/yr in Apple Connect and Google Play. Result:

  • iOS user in France sees €34.99 (Apple includes VAT in displayed price)
  • Android user in France sees €41.99 (Google adds 20% VAT on top)

Same nominal price, +20% gap. To match iOS displayed price across stores:

Google Play base price = iOS_price / (1 + local_VAT)
                       = 34.99 / 1.20 = €29.16

Apply per region: France/UK 20%, Germany 19%, Italy 22%, US 0% (federal).

5. Silent buttons get auto-rejected on Apple review

Apple rejected v1.0 with "No action when tapping Analyze my entry".

Reviewer recorded for 1 second. My validation:

if (!isValidDuration(recordedDuration)) return  // 15s minimum

Silent return. Button looked dead. Rejected under Guideline 2.1(a).

Rule: every visible button must produce visible feedback. Toast, alert, shake animation — anything. If validation fails, tell the user why:

>"Recording too short. Please speak for at least 15 seconds."

Apple reviewers test with minimum effort. Plan for the laziest possible reviewer interaction.

6. Google Play "Personal account" closed testing requirement

For Personal Google Play accounts created after Nov 2023: 12+ testers, 14 consecutive days on Closed Testing before you can promote to Production. Plan launch timeline accordingly.

Workaround: Organization account (D-U-N-S verification, 1-2 weeks) bypasses this requirement. Most indies just eat the 14 days.

Stack overall worked well. Expo SDK 55 is in a great spot for solo devs — EAS handles signing/keystore/credentials so you don't have to. The hardest parts were store-specific gotchas, not the framework.

Happy to expand on any of these if useful.

reddit.com
u/Illustrious_Rain_387 — 24 days ago