An offline-first Flutter app for saving links you actually come back to. Built with a bold Neo-Brutalist design system and powered by Firebase and Hive.
Saving a link is easy. Every app does it. The hard part is returning to it, which is why most saved links quietly rot.
LinkHive treats return as the product, not capture:
- Capture costs one gesture. Share to LinkHive and the link is saved instantly, no form, no decisions. It lands in an Inbox you can triage later, or never.
- The app brings links back to you. A daily notification surfaces one link worth revisiting, a Today screen makes acting on it a single tap, and an Android home screen widget keeps it visible without opening the app at all.
- Everything works offline. Writes hit local storage immediately and sync to the cloud when a connection appears.
- One-gesture save: Share a URL from any app and it is saved immediately, with no form to fill in.
- Inbox: Instantly-saved links land in a separate Inbox so they never clutter your organized collection. Add details whenever you feel like it, or don't.
- Background enrichment: Title, image and description are fetched from the page after the save, so the save itself is never blocked on the network.
- Undo: A confirmation bar offers both "Add details" and "Undo" right after saving.
- One link a day: A 9am local notification surfaces a single link worth revisiting, chosen from what you haven't read.
- Today screen: Shows that one link with three actions β Open, Archive, Snooze β each advancing to the next.
- "When should this come back?": Optionally schedule a link to resurface at a chosen time instead of joining the general pool.
- Spaced ordering: Links that have never been shown come first, then the least recently surfaced, so coverage spreads instead of repeating.
- Android home screen widget: Today's pick plus Inbox and unread counts, tappable straight into the link. Built with Jetpack Glance.
- Offline CRUD: Create, update and delete links and categories with zero latency.
- Global search: Find links by title, description or URL.
- Smart filtering: Filter by custom categories or priority (High, Normal, Low).
- In-app browser: Links open in a Chrome Custom Tab /
SFSafariViewController, so they keep your signed-in sessions and keep you inside the app. - Up Next: The oldest unread links surfaced on Home, so forgotten saves stay visible.
- Secure Sign-In: Powered by Firebase Authentication.
- Google Integration: One-tap sign-in with Google OAuth.
- Email/Password: Traditional secure account creation and login.
- Guest Mode: Full functionality for local-only use without an account.
- Bold Aesthetics: High-contrast colors, thick 2px borders, and hard-edge shadows (zero blur).
- Custom Components: Bespoke widgets including
NeoBrutalistButton,LinkCard, andCommonAppBar. - Light & dark themes: Including the home screen widget, which follows the system theme rather than the app's.
- Multi-Language Support: Full localization for English, Arabic (RTL support), Gujarati, and Hindi.
- Locale Persistence: Remembers your language preference across sessions.
Platform support: Android is the primary target and iOS is supported. The share-sheet integration is mobile-only (guarded at runtime), and the home screen widget is currently Android-only.
The project follows a Feature-based Clean Architecture, ensuring high modularity and testability.
lib/
βββ core/ # Shared kernel
β βββ constants/ # Firebase, Hive, and enum constants
β βββ extensions/ # BuildContext extensions (l10n, theme)
β βββ localization/ # i18n logic and Cubits
β βββ services/ # Auth, Firestore, sync, metadata,
β β # share intent, notifications, widget
β βββ theme/ # Neo-Brutalist design tokens
β βββ utils/ # Service locator (GetIt), routing, helpers
β
βββ features/ # Independent modules
β βββ links/ # Core link domain
β β βββ manager/ # LinkManager β every link action
β β βββ repository/ # LinkRepository β Hive + sync queue
β β βββ models/ # LinkModel, CategoryModel, exceptions
β β βββ bloc/ # LinkBloc, AddLinkBloc
β β βββ ui/ # Add / Edit link screen
β βββ inbox/ # Instantly-saved links awaiting triage
β βββ today/ # Daily Resurface focus screen
β βββ sync/ # Conflict resolution UI
β βββ authentication/ # Login / signup flow
β βββ home/ # Link list, search, Up Next strip
β βββ account/ # Profile, data management, sign-out
β βββ splash/ # Brand introduction
β
βββ sharedWidgets/ # Global Neo-Brutalist component library
βββ l10n/ # ARB translation files (en, ar, hi, gu)
βββ firebase_options.dart # Auto-generated Firebase config
βββ main.dart # Entry point
βββ my_app.dart # UI Root
android/app/src/main/kotlin/com/link/hive/widget/ # Glance home screen widget
Two layers, with one job each:
| Layer | Owns | Example |
|---|---|---|
LinkRepository |
Persistence. Hive writes, the Firestore sync queue, conflict resolution. | addLink, markLinkAsRead, pullFromCloud |
LinkManager |
Actions. The composites that combine a launch with bookkeeping. | openLink, archiveLink, snoozeLink |
Every surface that can act on a link β the Today screen, link cards, the Up Next strip, the home screen widget β goes through LinkManager, so "open a link" is defined exactly once. Before this existed it was implemented four times with four different behaviors, and one of them forgot to mark links as read at all.
The sync machinery (SyncEngine, SyncService, conflict resolution) talks to the repository directly, since it is persistence infrastructure rather than a user action.
- Framework: Flutter 3.38.3
- Language: Dart 3.10.1+
- Local Storage: Hive (NoSQL, high performance)
- Cloud Backend: Firebase (Firestore, Auth)
- State Management: flutter_bloc & RxDart
- Routing: go_router
- Dependency Injection: get_it
- Share Target: receive_sharing_intent (Android intent filters + iOS Share Extension)
- Notifications: flutter_local_notifications + timezone
- Home Screen Widget: home_widget + Jetpack Glance
- In-App Browser: url_launcher (Custom Tabs /
SFSafariViewController)
LinkHive uses a sophisticated two-way sync strategy between Hive and Firestore:
- Writes: All changes are committed to the local Hive box immediately. If the user is online, the change is mirrored to Firestore asynchronously.
- Outbox queue: Writes that can't reach Firestore are queued as sync operations and replayed once connectivity returns.
SyncServicewatches connectivity and auth state, and also ticks every 5 minutes. - Conflict Resolution: On pull, a local edit that collides with a cloud edit is recorded as a conflict rather than silently overwritten. The Conflicts screen lets you keep either version.
- Sync Status: Every link tracks an
isSyncedflag, displayed in the UI (cloud-off icon for local-only links), andSyncEngineexposes a status stream (idle / syncing / failed / conflict).
| Tool | Version | Install |
|---|---|---|
| Flutter | 3.38.3 | Managed via FVM |
| FVM | latest | dart pub global activate fvm |
| Xcode | latest | Mac App Store (required for iOS) |
| CocoaPods | latest | sudo gem install cocoapods |
| Android Studio | latest | Download (required for Android) |
| Firebase Project | β | Firebase Console |
Android builds need JDK 17 or 21. JDK 25 (bundled with recent Android Studio releases) is not supported by this project's Gradle version.
# Point Flutter at a supported JDK
fvm flutter config --jdk-dir "<path to JDK 17 or 21>"In Android Studio, set Settings β Build Tools β Gradle β Gradle JDK to the same JDK.
| Platform | Minimum Version |
|---|---|
| Android | API 24 (Android 7.0 Nougat) |
| iOS | 16.0 |
git clone https://github.com/Kunjshingala/LinkHive.git
cd LinkHive
# Install the correct Flutter version for this project
fvm install
fvm use
# Get Dart dependencies
fvm flutter pub get
# iOS only β install native pods
cd ios && pod install && cd ..LinkHive uses --dart-define-from-file to inject Firebase config at build time. No native config files (google-services.json, GoogleService-Info.plist) are needed.
-
Copy the example config:
cp firebase_config.json.example firebase_config.json
-
Open
firebase_config.jsonand fill in your values:Key Where to find it FB_PROJECT_IDFirebase Console β Project Settings β General β Project ID FB_MESSAGING_SENDER_IDFirebase Console β Project Settings β Cloud Messaging β Sender ID FB_STORAGE_BUCKETFirebase Console β Project Settings β General β Storage bucket FB_API_KEY_ANDROIDFirebase Console β Project Settings β Your Apps β Android β API key FB_APP_ID_ANDROIDFirebase Console β Project Settings β Your Apps β Android β App ID FB_API_KEY_IOSFirebase Console β Project Settings β Your Apps β iOS β API key FB_APP_ID_IOSFirebase Console β Project Settings β Your Apps β iOS β App ID FB_IOS_BUNDLE_IDYour iOS bundle identifier (e.g., com.link.hive)FB_WEB_CLIENT_IDFirebase Console β Authentication β Sign-in method β Google β Web client ID (not the Android client) FB_IOS_CLIENT_IDFirebase Console β Project Settings β Your Apps β iOS β OAuth client ID firebase_config.jsonis gitignored β your secrets stay local.
Terminal:
# Run on Android
fvm flutter run --dart-define-from-file=firebase_config.json
# Run on iOS Simulator
fvm flutter run -d iPhone --dart-define-from-file=firebase_config.jsonVS Code: Launch configs are pre-configured in .vscode/launch.json β just hit Run/Debug (F5).
Android Studio: Select the linkhive run configuration from .run/ β just hit Run (Shift+F10).
If you don't have a Firebase project yet:
- Go to the Firebase Console and create a new project.
- Authentication β Go to Authentication β Sign-in method and enable:
- Email/Password
- Firestore β Go to Firestore Database β Create database β Start in test mode (or configure security rules for production).
- Register Apps β Go to Project Settings β Add app:
- Add an Android app with package name
com.link.hive - Add an iOS app with bundle ID
com.link.hive
- Add an Android app with package name
- Copy the generated config values into your
firebase_config.json.
| Issue | Solution |
|---|---|
No Firebase App '[DEFAULT]' has been created |
Make sure firebase_config.json exists and you're passing --dart-define-from-file |
google-services.json not found (Android build error) |
Make sure you removed the com.google.gms.google-services plugin from android/app/build.gradle.kts |
| Google Sign-In fails on iOS | Verify the reversed client ID URL scheme is in ios/Runner/Info.plist |
pod install fails |
Run cd ios && pod repo update && pod install |
| Wrong Flutter version | Run fvm install && fvm use in the project root |
All project documentation lives in the docs/ directory:
| Document | Description |
|---|---|
| Product Direction: The Return Engine | Why the app exists, the problem it's actually solving, and the reasoning behind Instant Capture and Daily Resurface |
| Project Plan | Phase 1 scope, architecture decisions, and feature roadmap |
| Dart Define & Firebase Setup | How --dart-define-from-file works, Firebase project setup from scratch, and config file reference |
| Receive Sharing Intent | Android & iOS share sheet integration β how the app receives shared URLs from other apps |
Contributor rules live in .claude/rules/ (workflow, code patterns, naming) and fuller context in .ai/.
# Run the suite
fvm flutter test
# Must pass with zero warnings before any change is considered done
fvm flutter analyzeAll Flutter and Dart commands go through FVM (fvm flutter, fvm dart) so everyone builds against the pinned SDK.
Copyright Β© 2026 Kunj Shingala. All rights reserved. The source is public so it can be read, but it is not open source: see LICENSE. Third-party fonts keep their own licenses in assets/licenses/.