Implemented per stream subscription recovery with polling fallback
This commit is contained in:
@@ -12,14 +12,22 @@ import 'profile_provider.dart';
|
||||
import 'supabase_provider.dart';
|
||||
import 'user_offices_provider.dart';
|
||||
import 'tasks_provider.dart';
|
||||
import 'stream_recovery.dart';
|
||||
|
||||
final officesProvider = StreamProvider<List<Office>>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return client
|
||||
.from('offices')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('name')
|
||||
.map((rows) => rows.map(Office.fromMap).toList());
|
||||
|
||||
final wrapper = StreamRecoveryWrapper<Office>(
|
||||
stream: client.from('offices').stream(primaryKey: ['id']).order('name'),
|
||||
onPollData: () async {
|
||||
final data = await client.from('offices').select().order('name');
|
||||
return data.map(Office.fromMap).toList();
|
||||
},
|
||||
fromMap: Office.fromMap,
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
return wrapper.stream.map((result) => result.data);
|
||||
});
|
||||
|
||||
final officesOnceProvider = FutureProvider<List<Office>>((ref) async {
|
||||
@@ -128,16 +136,38 @@ final ticketsProvider = StreamProvider<List<Ticket>>((ref) {
|
||||
profile.role == 'dispatcher' ||
|
||||
profile.role == 'it_staff';
|
||||
|
||||
// Use stream for realtime updates. Offload expensive client-side
|
||||
// filtering/sorting/pagination to a background isolate via `compute`
|
||||
// so UI navigation and builds remain smooth.
|
||||
final baseStream = client.from('tickets').stream(primaryKey: ['id']);
|
||||
// Wrap realtime stream with recovery logic
|
||||
final wrapper = StreamRecoveryWrapper<Ticket>(
|
||||
stream: client.from('tickets').stream(primaryKey: ['id']),
|
||||
onPollData: () async {
|
||||
// Polling fallback: fetch all tickets once
|
||||
final data = await client.from('tickets').select();
|
||||
return data.cast<Map<String, dynamic>>().map(Ticket.fromMap).toList();
|
||||
},
|
||||
fromMap: Ticket.fromMap,
|
||||
);
|
||||
|
||||
return baseStream.asyncMap((rows) async {
|
||||
// rows is List<dynamic> of maps coming from Supabase
|
||||
final rowsList = (rows as List<dynamic>).cast<Map<String, dynamic>>();
|
||||
ref.onDispose(wrapper.dispose);
|
||||
|
||||
// Process tickets with filtering/pagination after recovery
|
||||
return wrapper.stream.asyncMap((result) async {
|
||||
final rowsList = result.data
|
||||
.map(
|
||||
(ticket) => <String, dynamic>{
|
||||
'id': ticket.id,
|
||||
'subject': ticket.subject,
|
||||
'description': ticket.description,
|
||||
'status': ticket.status,
|
||||
'office_id': ticket.officeId,
|
||||
'creator_id': ticket.creatorId,
|
||||
'created_at': ticket.createdAt.toIso8601String(),
|
||||
'responded_at': ticket.respondedAt?.toIso8601String(),
|
||||
'promoted_at': ticket.promotedAt?.toIso8601String(),
|
||||
'closed_at': ticket.closedAt?.toIso8601String(),
|
||||
},
|
||||
)
|
||||
.toList();
|
||||
|
||||
// Prepare lightweight serializable args for background processing
|
||||
final allowedOfficeIds =
|
||||
assignmentsAsync.valueOrNull
|
||||
?.where((assignment) => assignment.userId == profile.id)
|
||||
@@ -160,7 +190,6 @@ final ticketsProvider = StreamProvider<List<Ticket>>((ref) {
|
||||
|
||||
final processed = await compute(_processTicketsInIsolate, payload);
|
||||
|
||||
// `processed` is List<Map<String,dynamic>> — convert to Ticket objects
|
||||
final tickets = (processed as List<dynamic>)
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map(Ticket.fromMap)
|
||||
@@ -246,32 +275,73 @@ final ticketsQueryProvider = StateProvider<TicketQuery>(
|
||||
final ticketMessagesProvider =
|
||||
StreamProvider.family<List<TicketMessage>, String>((ref, ticketId) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return client
|
||||
.from('ticket_messages')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('ticket_id', ticketId)
|
||||
.order('created_at', ascending: false)
|
||||
.map((rows) => rows.map(TicketMessage.fromMap).toList());
|
||||
|
||||
final wrapper = StreamRecoveryWrapper<TicketMessage>(
|
||||
stream: client
|
||||
.from('ticket_messages')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('ticket_id', ticketId)
|
||||
.order('created_at', ascending: false),
|
||||
onPollData: () async {
|
||||
final data = await client
|
||||
.from('ticket_messages')
|
||||
.select()
|
||||
.eq('ticket_id', ticketId)
|
||||
.order('created_at', ascending: false);
|
||||
return data.map(TicketMessage.fromMap).toList();
|
||||
},
|
||||
fromMap: TicketMessage.fromMap,
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
return wrapper.stream.map((result) => result.data);
|
||||
});
|
||||
|
||||
final ticketMessagesAllProvider = StreamProvider<List<TicketMessage>>((ref) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return client
|
||||
.from('ticket_messages')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('created_at', ascending: false)
|
||||
.map((rows) => rows.map(TicketMessage.fromMap).toList());
|
||||
|
||||
final wrapper = StreamRecoveryWrapper<TicketMessage>(
|
||||
stream: client
|
||||
.from('ticket_messages')
|
||||
.stream(primaryKey: ['id'])
|
||||
.order('created_at', ascending: false),
|
||||
onPollData: () async {
|
||||
final data = await client
|
||||
.from('ticket_messages')
|
||||
.select()
|
||||
.order('created_at', ascending: false);
|
||||
return data.map(TicketMessage.fromMap).toList();
|
||||
},
|
||||
fromMap: TicketMessage.fromMap,
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
return wrapper.stream.map((result) => result.data);
|
||||
});
|
||||
|
||||
final taskMessagesProvider = StreamProvider.family<List<TicketMessage>, String>(
|
||||
(ref, taskId) {
|
||||
final client = ref.watch(supabaseClientProvider);
|
||||
return client
|
||||
.from('ticket_messages')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('task_id', taskId)
|
||||
.order('created_at', ascending: false)
|
||||
.map((rows) => rows.map(TicketMessage.fromMap).toList());
|
||||
|
||||
final wrapper = StreamRecoveryWrapper<TicketMessage>(
|
||||
stream: client
|
||||
.from('ticket_messages')
|
||||
.stream(primaryKey: ['id'])
|
||||
.eq('task_id', taskId)
|
||||
.order('created_at', ascending: false),
|
||||
onPollData: () async {
|
||||
final data = await client
|
||||
.from('ticket_messages')
|
||||
.select()
|
||||
.eq('task_id', taskId)
|
||||
.order('created_at', ascending: false);
|
||||
return data.map(TicketMessage.fromMap).toList();
|
||||
},
|
||||
fromMap: TicketMessage.fromMap,
|
||||
);
|
||||
|
||||
ref.onDispose(wrapper.dispose);
|
||||
return wrapper.stream.map((result) => result.data);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user