@@ -430,11 +430,7 @@ class ShowcaseView {
430430 _activeWidgetId = id;
431431
432432 if (_activeWidgetId! >= _ids! .length || _activeWidgetId! .isNegative) {
433- _cleanupAfterSteps ();
434- onFinish? .call ();
435- for (final callback in _onFinishCallbacks) {
436- callback.call ();
437- }
433+ _finishShowcase ();
438434 } else {
439435 // Add a short delay before starting the next showcase to ensure proper state update
440436 // Then start the new showcase
@@ -505,6 +501,15 @@ class ShowcaseView {
505501 _changeSequence (type);
506502 return ;
507503 }
504+ if (! skipIfTargetNotPresent &&
505+ controllerLength == 0 &&
506+ type == ShowcaseProgressType .forward) {
507+ // A forward step with no rendered target cannot be displayed.
508+ // Finishing here preserves expected UX and avoids progressing through
509+ // non-existent showcase indexes when next() is tapped.
510+ _finishShowcase ();
511+ return ;
512+ }
508513
509514 final firstController = controllers.firstOrNull;
510515 final isAutoScroll =
@@ -567,6 +572,18 @@ class ShowcaseView {
567572 if (autoPlay) _cancelTimer ();
568573 }
569574
575+ /// Finishes the showcase and triggers all finish callbacks.
576+ ///
577+ /// This centralizes completion behavior so all finish paths are consistent,
578+ /// including edge cases where a target is missing during forward progression.
579+ void _finishShowcase () {
580+ _cleanupAfterSteps ();
581+ onFinish? .call ();
582+ for (final callback in _onFinishCallbacks) {
583+ callback.call ();
584+ }
585+ }
586+
570587 /// Cancels auto-play timer if active.
571588 void _cancelTimer () {
572589 if (! (_timer? .isActive ?? false )) return ;
0 commit comments