Drag and Drog always throws error when dragging between resources in timeline view

I'm trying to implement drag and drop for the timeline view and every time I drag an appointment between rows (ie. resources) I get this error:

Unsupported operation: Cannot remove from an unmodifiable list

This is the view I'm working with. Dragging horizontally within a row (ie. changing the time but not the resource) works fine, but dragging vertically between rows always results in that same error.

Image_2901_1693837881121


The exception is caused by this code inside calendar_view.dart:

https://github.com/syncfusion/flutter-widgets/blob/686d0694069849b65d8d7326e6f5719bee17ab24/packages/syncfusion_flutter_calendar/lib/src/calendar/views/calendar_view.dart#L2276-L2287


Any help would be greatly appreciated! Thanks  and have a good day :)


Stacktrace:

══╡ EXCEPTION CAUGHT BY GESTURE ╞═══════════════════════════════════════════════════════════════════

The following UnsupportedError was thrown while handling a gesture:
Unsupported operation: Cannot remove from an unmodifiable list
When the exception was thrown, this was the stack:
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 288:49 throw_
dart-sdk/lib/internal/list.dart 134:5 remove
packages/syncfusion_flutter_calendar/src/calendar/views/calendar_view.dart 2281:22 [_handleLongPressEnd]
packages/syncfusion_flutter_calendar/src/calendar/views/calendar_view.dart 2515:7 [_handleDragEnd]
packages/syncfusion_flutter_calendar/src/calendar/views/calendar_view.dart 722:37 <fn>
packages/flutter/src/gestures/monodrag.dart 540:41 <fn>
packages/flutter/src/gestures/recognizer.dart 275:24 invokeCallback
packages/flutter/src/gestures/monodrag.dart 540:5 [_checkEnd]

6 Replies

IR Indumathi Ravichandran Syncfusion Team September 5, 2023 10:03 AM UTC

Hi grady,


As per the shared information, we have checked the mentioned issue “Drag and drop through errors between resources in the timelineviews in the Flutter Calendar” and it was working fine as expected from our end and we are able to dragdrop the appointment between the resources. Please find the sample and video from the attached link.


Please check the sample and let us know if you still facing the same issue? If not, please modify the sample based on your scenario and revert us with following details,


  • Code snippet

  • Replication procedure or video



It will be helpful for us to check on it and provide you solution at the earliest.



Regards,

Indumathi R



Attachment: resourceview_8677e978.zip



GR Grady September 9, 2023 04:35 PM UTC

Thank you for your quick response!

I have looked at the example and I am uncertain why my version doesn't work.

When I drag a shift, onDragStart gets called, but onDragEnd does not, because I end up with the error 

Unsupported operation: Cannot remove from an unmodifiable list 

during _handleLongPress


Please take a look and let me know if you can see what I'm doing wrong. Thanks!


Here is my code:

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:shift_shop/src/features/roles/data/roles_repository.dart';
import 'package:shift_shop/src/features/roles/data/test_roles.dart';
import 'package:shift_shop/src/features/roles/domain/role.dart';
import 'package:shift_shop/src/features/schedule/data/shifts_repository.dart';
import 'package:shift_shop/src/features/schedule/domain/shift.dart';
import 'package:shift_shop/src/features/schedule/presentation/shifts_data_source.dart';
import 'package:shift_shop/src/routing/router.dart';
import 'package:syncfusion_flutter_calendar/calendar.dart';

class ShiftScheduler extends ConsumerStatefulWidget {
const ShiftScheduler({super.key});

@override
ConsumerState<ConsumerStatefulWidget> createState() => _ShiftSchedulerState();
}

class _ShiftSchedulerState extends ConsumerState<ShiftScheduler> {
_ShiftSchedulerState();

final CalendarController _calendarController = CalendarController();

final List<CalendarView> _allowedviews = const <CalendarView>[
CalendarView.day,
CalendarView.week,
CalendarView.month,
CalendarView.timelineDay,
CalendarView.timelineWeek,
CalendarView.schedule
];

// we will use this for recurrence (see V4 ShiftScheduler)
// ignore: unused_field
late List<DateTime> _visibleDates;

late List<Shift> _shifts;

late List<Role> _roles;

late List<CalendarResource> _rolesCalendarResources;

late ShiftsDataSource _events;

@override
void initState() {
_calendarController.view = CalendarView.timelineWeek;
_events = ShiftsDataSource(
[],
[],
);
_shifts = [];
_roles = [];
_rolesCalendarResources = [];
super.initState();
}

void _onViewChanged(ViewChangedDetails visibleDatesChangedDetails) {
_visibleDates = visibleDatesChangedDetails.visibleDates;
}

Future<void> _onCalendarTapped(
CalendarTapDetails calendarTapDetails,
) async {
debugPrint('_onCalendarTapped called');

/// Condition added to open the editor, when the calendar elements tapped
/// other than the header.
if (calendarTapDetails.targetElement == CalendarElement.header ||
calendarTapDetails.targetElement == CalendarElement.viewHeader ||
// remove if you want to do something when clicking on a role
calendarTapDetails.targetElement == CalendarElement.resourceHeader) {
return;
}


/// Navigates the calendar to timelineDay view when we tap on month cells.
if (_calendarController.view == CalendarView.month) {
_calendarController.view = CalendarView.timelineWeek;
return;
}

Shift? selectedV4Shift;
if (calendarTapDetails.appointments != null &&
calendarTapDetails.appointments!.isNotEmpty) {
selectedV4Shift = calendarTapDetails.appointments!.firstOrNull as Shift;
}

final selectedDate = calendarTapDetails.date ?? DateTime.now();

Role? role;
if (selectedV4Shift != null) {
role = kTestRoles.firstWhere(
(r) => r.id == selectedV4Shift!.roleId,
orElse: Role.anyRole,
);
} else {
role = calendarTapDetails.resource != null
? Role.fromCalendarResource(calendarTapDetails.resource!)
: Role.anyRole();
}

final shift = selectedV4Shift ??
Shift.create().copyWith(
startTime: selectedDate,
endTime: selectedDate.add(const Duration(hours: 8)),
roleId: role.id,
resourceIds: [role.id],
color: Color(role.colorInt),
);

// Consider sending an object containing all required data to ShiftEditScreen
// ie. shift, visible dates, etc
final updatedV4Shift = await context.pushNamed<Shift>(
AppRoute.shiftEdit.name,
pathParameters: {'id': shift.id},
extra: shift,
);
debugPrint('UPDATED SHIFT: $updatedV4Shift');
}

@override
Widget build(BuildContext context) {

final shiftsRepository = ref.watch(shiftsRepositoryProvider);

final shiftsListStream = ref.watch(shiftsListStreamProvider);
final rolesListStream = ref.watch(rolesListStreamProvider);

shiftsListStream.when(
loading: () {
return const CircularProgressIndicator();
},
error: (error, stackTrace) {
return ErrorWidget(error);
},
data: (data) {
_shifts = data;
},
);

rolesListStream.whenData(
(value) {
_roles = value;
_rolesCalendarResources =
_roles.map((role) => role.toCalendarResource()).toList();
if (!_roles.contains(Role.anyRole())) {
_rolesCalendarResources.add(Role.anyRole().toCalendarResource());
}
},
);

_events = ShiftsDataSource(_shifts, _rolesCalendarResources);

return SfCalendar(
dataSource: _events,
controller: _calendarController,
allowedViews: _allowedviews,
showDatePickerButton: true,
onTap: _onCalendarTapped,
onViewChanged: _onViewChanged,
allowDragAndDrop: true,
onDragStart: (AppointmentDragStartDetails appointmentDragStartDetails) {
debugPrint('DRAG START CALLED');
debugPrint(
'SHIFT BEFORE DRAG: ${appointmentDragStartDetails.appointment}',
);
onDragEnd: (appointmentDragEndDetails) {
debugPrint('DRAG END CALLED');
final shift = appointmentDragEndDetails.appointment as Shift?;
debugPrint('DRAGGED SHIFT: $shift');

final targetResource = appointmentDragEndDetails.targetResource;
final resourceIds = [targetResource!.id];
shiftsRepository.updateShift(shift!.copyWith(resourceIds: resourceIds));
},
);
}
}




IR Indumathi Ravichandran Syncfusion Team September 11, 2023 10:17 AM UTC

Hi Grady,


Based on the shared code snippet, we are unable to run the sample. But, using our sample we have added the onDragStart(), onDragEnd() callbacks and ensured, there is no error from our end, it was working fine as expected from our end. Also, can you please ensure once the convertAppointmentToObject() method is implemented and this method must be implemented for appointment drag and drop.


Tested version:


  • Flutter – Channel version 3.13.0 (Stable)

  • Syncfusion Flutter calendar version – 22.2.11


If possible, can you please check with the above details once and share the error details clearly. It would be helpful for us to analyze and provide you a solution at the earliest. Also please find the sample from the attached link.


Regards,

Indumathi R


Attachment: resourceview_9d7b50a0.zip


GR Grady September 11, 2023 04:03 PM UTC

So the issue seems to be that the resourceIds field on appointment does not contain a List<Object> but an EqualUnmodifiableListView<Object>.


I am not sure why this happens, as I am only ever declaring List<Object>. 

Any insights on this?


Image_4692_1694447724059



GR Grady September 12, 2023 01:27 PM UTC

I figured it out. Turns out that unless you declare a list using the [] syntax, Dart makes it immutable by default. 



IR Indumathi Ravichandran Syncfusion Team September 12, 2023 01:47 PM UTC

Hi Grady,


We are glad to know that the issue resolved at your end. Please get in touch with us if you would require any further assistance.


Regards,

Indumathi R


Loader.
Up arrow icon