Skip to content

Instantly share code, notes, and snippets.

@Jimgerneer
Created March 30, 2026 16:20
Show Gist options
  • Select an option

  • Save Jimgerneer/96fd1092c556dc61efe018ae08f3f8bd to your computer and use it in GitHub Desktop.

Select an option

Save Jimgerneer/96fd1092c556dc61efe018ae08f3f8bd to your computer and use it in GitHub Desktop.
Unsafe list access patterns audit - bzzr-flutter

Unsafe List Access Patterns - Remaining Issues

These are non-API-related instances of firstWhere (without orElse) and unsafe .first/.last access that could crash if lists are empty or don't contain a matching element. These are lower priority than the API-related ones (which have been fixed) but should still be addressed.

Hypes Screen Notifier

File: apps/bzzr_flutter/lib/features/Home/presentation/screens/hypes/hypes_screen_notifier.dart:137

final activity = state.valueOrNull?.activities.firstWhere(
  (a) => a.id == activityId,
);

Risk: Called when user likes a hype. If activityId doesn't exist in the activities list, this crashes. Fix: Use firstOrNullWhere (from dartx) or firstWhereOrNull and handle the null case with an early return.


Episode Worksheet Screen

File: packages/bzzr_sidecast_shared/lib/features/Sidecast/presentation/screens/EpisodeWorksheet/episode_worksheet_screen.dart

Lines 315, 321, 327, 568, 595, 623 - multiple instances like:

_hostsOptions.firstWhere((a) => a.displayNameWithHandle == value)
_guestsOptions.firstWhere((a) => a.displayNameWithHandle == value)
_producersOptions.firstWhere((a) => a.displayNameWithHandle == value)

Risk: Used in chip removal callbacks. If the selected chip value doesn't match any option (state desync), this crashes. Fix: Use firstWhereOrNull and skip the removal if no match is found.

Also at line 106:

_producersController.chips.first

Risk: Crashes if chips list is empty. Fix: Use firstOrNull with a null check.


Show Worksheet Screen

File: packages/bzzr_sidecast_shared/lib/features/Sidecast/presentation/screens/ShowWorksheet/show_worksheet_screen.dart

Lines 139, 492-493, 513-514:

.firstWhere((a) => a.displayNameWithHandle == producerName)

Risk: Same pattern as Episode Worksheet - chip builder assumes selected value always exists in options. Fix: Use firstWhereOrNull and handle gracefully.

Also at line 102:

_producerController.chips.first

Risk: Crashes if chips list is empty. Fix: Use firstOrNull with a null check.


Sidecast Studio Notifier

File: packages/bzzr_sidecast_shared/lib/features/Sidecast/presentation/screens/SidecastStudio/sidecast_studio_notifier.dart:197

final audioDevice = audioDevices.firstWhere((e) => e.id == deviceId);

Risk: Crashes if user's audio device disconnects between enumeration and selection. Fix: Use firstWhereOrNull and show an error or fall back to default device.


Sidecast Livestream Screen

File: packages/bzzr_sidecast_shared/lib/features/Sidecast/presentation/screens/SidecastLivestream/sidecast_livestream_screen.dart

Lines 85, 100:

final rtcMediaDevice = devices.firstWhere((d) => d.id == deviceId);

Risk: Crashes if RTC device disappears during operation. Fix: Use firstWhereOrNull and handle missing device gracefully.


Sidecast Room Notifier

File: packages/bzzr_sidecast_shared/lib/features/Sidecast/presentation/screens/SidecastRoom/sidecast_room_notifier.dart

Lines 264, 294:

roles.first,

Risk: Passed to updateMe - crashes if participant has no roles. Fix: Use firstOrNull and provide a default role or skip the update.


RTC Local Track Extension

File: packages/bzzr_sidecast_shared/lib/extensions/rtc_local_track_ext.dart:14

final rtc.MediaStreamTrack newTrack = newStream.getTracks().first;

Risk: Crashes if getTracks() returns an empty list during stream initialization. Fix: Use firstOrNull and handle the null case.


Home Screen State

File: apps/bzzr_flutter/lib/features/Home/presentation/screens/home/components/home_screen_state.dart:35

return HomeScreenFeed.values.firstWhere((e) => e.group == value);

Risk: Crashes if value doesn't match any enum group. Fix: Use a try/catch or manual lookup with a fallback default.


Chips Input Autocomplete

File: packages/bzzr_shared/lib/thirdparty/chips_input_autocomplete/chips_input_autocomplete.dart:343

final lastChip = _chipsAutocompleteController.chips.last;

Risk: Crashes if chips list is empty during backspace handling. Fix: Use lastOrNull and skip deletion if null.


General Fix Pattern

For all of these, the fix follows one of two patterns:

Pattern A - firstWhere to firstOrNullWhere:

// Before (crashes)
final item = list.firstWhere((e) => e.id == targetId);

// After (safe)
final item = list.firstOrNullWhere((e) => e.id == targetId);
if (item == null) return; // or handle gracefully

Pattern B - .first/.last to .firstOrNull/.lastOrNull:

// Before (crashes)
final item = list.first;

// After (safe)
final item = list.firstOrNull;
if (item == null) return; // or handle gracefully

firstOrNullWhere comes from the dartx package (already a dependency). firstOrNull/lastOrNull are available natively in Dart 3+.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment