Enable collaborative watchlists shared within investor groups, allowing members to collectively track symbols with real-time synchronization and price alerts.
Group-Based Watchlists extend investor group functionality by enabling teams to create and manage shared watchlists within a group context. Members can collaborate on tracking securities, with role-based access control to manage permissions.
investor_groups/{groupId}/
watchlists/{watchlistId}/
- name: string
- description: string
- createdBy: userId (creator/initial editor)
- createdAt: timestamp
- updatedAt: timestamp
- permissions: {
[userId]: "editor" // Only contains explicit editors
}
// Note: All group members not in permissions are "viewers" by convention
// This means new group members automatically have viewer access
symbols/{symbolId}/
- symbol: string (uppercase)
- addedBy: userId
- addedAt: timestamp
alerts/{alertId}/
- type: "price_above" | "price_below"
- threshold: number
- active: boolean
- createdAt: timestamp
Each watchlist enforces member-level permissions:
Permissions logic:
permissions map with “editor” role → editor accessRules validate at multiple levels:
All operations are protected with authentication checks:
// Create watchlist
createGroupWatchlist(data: {
groupId: string;
name: string;
description: string;
}): { success: boolean; watchlistId: string }
// Delete watchlist (creator only)
deleteGroupWatchlist(data: {
groupId: string;
watchlistId: string;
}): { success: boolean }
// Add symbol to watchlist
addSymbolToWatchlist(data: {
groupId: string;
watchlistId: string;
symbol: string;
}): { success: boolean }
// Remove symbol from watchlist
removeSymbolFromWatchlist(data: {
groupId: string;
watchlistId: string;
symbol: string;
}): { success: boolean }
// Create price alert
createPriceAlert(data: {
groupId: string;
watchlistId: string;
symbol: string;
type: "price_above" | "price_below";
threshold: number;
}): { success: boolean; alertId: string }
// Delete price alert
deletePriceAlert(data: {
groupId: string;
watchlistId: string;
symbol: string;
alertId: string;
}): { success: boolean }
// Set member permission
setWatchlistMemberPermission(data: {
groupId: string;
watchlistId: string;
memberId: string;
permission: "editor" | "viewer";
}): { success: boolean }
// Remove member permission
removeWatchlistMemberPermission(data: {
groupId: string;
watchlistId: string;
memberId: string;
}): { success: boolean }
The GroupWatchlistService in lib/services/group_watchlist_service.dart provides:
// Streams
Stream<List<GroupWatchlist>> getGroupWatchlistsStream(String groupId)
Stream<GroupWatchlist?> getWatchlistStream({
required String groupId,
required String watchlistId,
})
Stream<List<WatchlistSymbol>> getWatchlistSymbolsStream({
required String groupId,
required String watchlistId,
})
Stream<List<WatchlistAlert>> getSymbolAlertsStream({
required String groupId,
required String watchlistId,
required String symbol,
})
// CRUD Operations
Future<String> createGroupWatchlist({...})
Future<void> deleteGroupWatchlist({...})
Future<void> addSymbolToWatchlist({...})
Future<void> removeSymbolFromWatchlist({...})
Future<String> createPriceAlert({...})
Future<void> deletePriceAlert({...})
Future<void> updateWatchlist({...})
Future<void> setWatchlistMemberPermission({...})
Future<void> removeWatchlistMemberPermission({...})
GroupWatchlistsWidget
GroupWatchlistDetailWidget
GroupWatchlistCreateWidget
Add to investor group detail widget:
// In investor_group_detail_widget.dart
GroupWatchlistsWidget(
brokerageUser: widget.brokerageUser,
groupId: widget.groupId,
)
Watchlists use Firestore snapshot listeners for real-time updates:
Functions return specific error messages:
unauthenticated - User not logged innot-found - Watchlist, group, or member not foundpermission-denied - User lacks required permissioninvalid-argument - Invalid permission value or dataNo additional indexes required. Queries use indexed fields:
investor_groups.members (existing index)createdAt (compound with groupId)Deploy group watchlist functions with:
cd src/robinhood_options_mobile/functions
firebase deploy --only functions
Update Firestore rules:
firebase deploy --only firestore:rules
Unit tests for the service layer:
// Example test
testWidgets('Add symbol to watchlist', (WidgetTester tester) async {
final service = GroupWatchlistService();
await service.addSymbolToWatchlist(
groupId: 'test-group',
watchlistId: 'test-list',
symbol: 'AAPL',
addedBy: 'user123',
);
// Stream should emit updated watchlist
});
testWidgets('Permission denied for viewers', (WidgetTester tester) async {
// Test that viewers cannot add symbols
});