bdlukaa / fluent_ui

Implements Microsoft's WinUI3 in Flutter.
https://bdlukaa.github.io/fluent_ui/
BSD 3-Clause "New" or "Revised" License
2.79k stars 435 forks source link

🐛 TabView: RangeError (index): Invalid value: Not in inclusive range 0..1: 2 #1035

Closed TENX-S closed 3 months ago

TENX-S commented 3 months ago

Describe the bug An exception occurred when removing a tab:

══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following RangeError was thrown building:
RangeError (index): Invalid value: Not in inclusive range 0..2: 3

When the exception was thrown, this was the stack:
#0      List.[] (dart:core-patch/growable_array.dart:264:36)
#1      _TabViewState._tabBuilder (package:fluent_ui/src/controls/navigation/tab_view.dart:298:28)
#2      _TabViewState.build.<anonymous closure>.<anonymous closure>
(package:fluent_ui/src/controls/navigation/tab_view.dart:482:32)
#3      _ReorderableListViewState._itemBuilder (package:flutter/src/material/reorderable_list.dart:296:43)
#4      SliverReorderableListState._itemBuilder (package:flutter/src/widgets/reorderable_list.dart:958:44)
#5      SliverChildBuilderDelegate.build (package:flutter/src/widgets/scroll_delegate.dart:490:22)
#6      SliverMultiBoxAdaptorElement._build (package:flutter/src/widgets/sliver.dart:831:28)
#7      SliverMultiBoxAdaptorElement.performRebuild.processElement (package:flutter/src/widgets/sliver.dart:761:67)
#8      Iterable.forEach (dart:core/iterable.dart:347:35)
#9      SliverMultiBoxAdaptorElement.performRebuild (package:flutter/src/widgets/sliver.dart:808:24)
#10     SliverMultiBoxAdaptorElement.update (package:flutter/src/widgets/sliver.dart:737:7)
#11     Element.updateChild (package:flutter/src/widgets/framework.dart:3827:15)
#12     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:5512:16)
#13     StatefulElement.performRebuild (package:flutter/src/widgets/framework.dart:5650:11)
#14     Element.rebuild (package:flutter/src/widgets/framework.dart:5203:7)
#15     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2905:19)
#16     WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:1021:21)
#17     RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:443:5)
#18     SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:1388:15)
#19     SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:1313:9)
#20     SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:1171:5)
#21     _invoke (dart:ui/hooks.dart:312:13)
#22     PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:419:5)
#23     _drawFrame (dart:ui/hooks.dart:283:31)
════════════════════════════════════════════════════════════════════════════════════════════════════

https://github.com/bdlukaa/fluent_ui/assets/40336192/7982e7c2-67b6-481b-ad59-9339439ee16e

Minimal reproducible code(from fluent_ui example):

import 'dart:math';

import 'package:fluent_ui/fluent_ui.dart';

void main() {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(const App());
}

class App extends StatelessWidget {
  const App({super.key});

  @override
  Widget build(BuildContext context) {
    return const FluentApp(
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int currentIndex = 0;
  List<Tab> tabs = [];

  Tab generateTab(int index) {
    late Tab tab;
    tab = Tab(
      text: Text('Document $index'),
      semanticLabel: 'Document #$index',
      icon: const FlutterLogo(),
      body: Container(
        color:
        Colors.accentColors[Random().nextInt(Colors.accentColors.length)],
      ),
      onClosed: () {
        setState(() {
          tabs.remove(tab);

          if (currentIndex > 0) currentIndex--;
        });
      },
    );
    return tab;
  }

  @override
  Widget build(BuildContext context) {
    return NavigationView(
      content: ScaffoldPage(
        content: TabView(
          tabs: tabs,
          currentIndex: currentIndex,
          onChanged: (index) => setState(() => currentIndex = index),
          tabWidthBehavior: TabWidthBehavior.sizeToContent,
          closeButtonVisibility: CloseButtonVisibilityMode.onHover,
          showScrollButtons: false,
          onNewPressed: () {
            setState(() {
              final index = tabs.length + 1;
              final tab = generateTab(index);
              tabs.add(tab);
            });
          },
          onReorder: (oldIndex, newIndex) {
            setState(() {
              if (oldIndex < newIndex) {
                newIndex -= 1;
              }
              final item = tabs.removeAt(oldIndex);
              tabs.insert(newIndex, item);

              if (currentIndex == newIndex) {
                currentIndex = oldIndex;
              } else if (currentIndex == oldIndex) {
                currentIndex = newIndex;
              }
            });
          },
        ),
      ),
    );
  }
}
TENX-S commented 3 months ago

Replace onClosed with the following works:

onClosed: () {
        tabs.remove(tab);
        setState(() {
          if (currentIndex > 0) currentIndex--;
        });
      },