title: How to display a dynamic map using Tabs and SliverGrid
body: <p>I have a Model called service provider that contains a the following data:</p>
  <pre><code>class ServiceProvider {
      final String logoUrl;
      final int lowerPrice;
      final String name;
      final int numberOfOrders;
      final int onTime;
      final int ordersPercentage;
      final int upperPrice;
      final double rating;
      final Map&lt;dynamic,dynamic&gt; albums;
    };
  }
  </code></pre>
  <p>I am able to parse all the data correctly without any problems however I hit a brick wall when I tried to display the albums map
  basically its a map of strings with lists as values looking like this :</p>
  <pre><code> albums: {
              &quot;Nature&quot; :[
                &quot;imageURLHere&quot;,
                &quot;imageURLHere&quot;,
                &quot;imageURLHere&quot;
              ],
              &quot;Wedding&quot; : [
                 &quot;imageURLHere&quot;
               ],
              &quot;Portraits&quot; : [
                &quot;imageURLHere&quot;,
                &quot;imageURLHere&quot;
              ],
            }
  </code></pre>
  <p>My idea was to use a Tabs to represent each Key (Nature,wedding,portrait)
  and use a SliverGrid to display the images.</p>
  <p>I used a Custom Tab Builder to be able to build the TabBar and TabViews Dynamically, however I cannot think of a way to make the SliverGrid Inside to display each image list.</p>
  <p>basically I can only show one of the lists 3 times.</p>
  <p>keeping in mind that the number of albums and number of images is variable.</p>
  <p>This is the custom tab Builder that I am using</p>
  <pre><code>  final int itemCount;
    final IndexedWidgetBuilder tabBuilder;
    final IndexedWidgetBuilder pageBuilder;
    final Widget? stub;
    final ValueChanged&lt;int&gt;? onPositionChange;
    final ValueChanged&lt;double&gt;? onScroll;
    final int? initPosition;
  
    CustomTabView({
      required this.itemCount,
      required this.tabBuilder,
      required this.pageBuilder,
      this.stub,
      this.onPositionChange,
      this.onScroll,
      this.initPosition,
    });
  
    @override
    _CustomTabsState createState() =&gt; _CustomTabsState();
  }
  
  class _CustomTabsState extends State&lt;CustomTabView&gt; with TickerProviderStateMixin {
    TabController? controller;
    int? _currentCount;
    int? _currentPosition;
  
    @override
    void initState() {
      _currentPosition = widget.initPosition ?? 0;
      controller = TabController(
        length: widget.itemCount,
        vsync: this,
        initialIndex: _currentPosition!,
      );
      controller!.addListener(onPositionChange);
      controller!.animation!.addListener(onScroll);
      _currentCount = widget.itemCount;
      super.initState();
    }
  
    @override
    void didUpdateWidget(CustomTabView oldWidget) {
      if (_currentCount != widget.itemCount) {
        controller!.animation!.removeListener(onScroll);
        controller!.removeListener(onPositionChange);
        controller!.dispose();
  
        if (widget.initPosition != null) {
          _currentPosition = widget.initPosition;
        }
  
        if (_currentPosition! &gt; widget.itemCount - 1) {
          _currentPosition = widget.itemCount - 1;
          _currentPosition = _currentPosition! &lt; 0 ? 0 :
          _currentPosition;
          if (widget.onPositionChange is ValueChanged&lt;int&gt;) {
            WidgetsBinding.instance!.addPostFrameCallback((_){
              if(mounted) {
                widget.onPositionChange!(_currentPosition!);
              }
            });
          }
        }
  
        _currentCount = widget.itemCount;
        setState(() {
          controller = TabController(
            length: widget.itemCount,
            vsync: this,
            initialIndex: _currentPosition!,
          );
          controller!.addListener(onPositionChange);
          controller!.animation!.addListener(onScroll);
        });
      } else if (widget.initPosition != null) {
        controller!.animateTo(widget.initPosition!);
      }
  
      super.didUpdateWidget(oldWidget);
    }
  
    @override
    void dispose() {
      controller!.animation!.removeListener(onScroll);
      controller!.removeListener(onPositionChange);
      controller!.dispose();
      super.dispose();
    }
  
    @override
    Widget build(BuildContext context) {
      if (widget.itemCount &lt; 1) return widget.stub ?? Container();
  
      return Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: &lt;Widget&gt;[
          Container(
            alignment: Alignment.center,
            child: TabBar(
              isScrollable: true,
              controller: controller,
              labelColor: Theme.of(context).primaryColor,
              unselectedLabelColor: Theme.of(context).hintColor,
              indicator: BoxDecoration(
                border: Border(
                  bottom: BorderSide(
                    color: Theme.of(context).primaryColor,
                    width: 2,
                  ),
                ),
              ),
              tabs: List.generate(
                widget.itemCount,
                    (index) =&gt; widget.tabBuilder(context, index),
              ),
            ),
          ),
          Expanded(
            child: TabBarView(
              controller: controller,
              children: List.generate(
                widget.itemCount,
                    (index) =&gt; widget.pageBuilder(context, index),
              ),
            ),
          ),
        ],
      );
    }
  
    onPositionChange() {
      if (!controller!.indexIsChanging) {
        _currentPosition = controller!.index;
        if (widget.onPositionChange is ValueChanged&lt;int&gt;) {
          widget.onPositionChange!(_currentPosition!);
        }
      }
    }
  
    onScroll() {
      if (widget.onScroll is ValueChanged&lt;double&gt;) {
        widget.onScroll!(controller!.animation!.value);
      }
    }
  }
  </code></pre>
  <p>and this is where I use it and build my SliverGrid inside it</p>
  <pre><code>class IndividualServiceProviderScreen extends StatelessWidget {
    final ServiceProvider serviceProvider;
  
    const IndividualServiceProviderScreen({
      required this.serviceProvider,
    });
  
    @override
    Widget build(BuildContext context) {
      List data = serviceProvider.albums.keys.toList();
      int currentPosition = 1;
  
      return DefaultTabController(
          length: serviceProvider.albums.keys.length,
          child: Scaffold(
              appBar: AppBar(
                title: Text(serviceProvider.name),
              ),
              body: NestedScrollView(
                physics: BouncingScrollPhysics(
                    parent: AlwaysScrollableScrollPhysics()),
                headerSliverBuilder:
                    (BuildContext context, bool innerBoxIsScrolled) {
                  return &lt;Widget&gt;[
                    SliverToBoxAdapter(
                      child: ServiceProviderDetailsCard(
                        serviceProvider: serviceProvider,
                      ),
                    ),
                  ];
                },
                body: CustomTabView(
                  initPosition: currentPosition,
                  itemCount: data.length,
                  tabBuilder: (context, index) =&gt; Tab(text: data[index]),
                  pageBuilder: (context, index) =&gt;
                      CustomScrollView(
                        physics: BouncingScrollPhysics(
                        parent: AlwaysScrollableScrollPhysics()),
                        slivers: [
                        SliverGrid(
                          gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
                          maxCrossAxisExtent: 200,
                          mainAxisSpacing: 10.0,
                          crossAxisSpacing: 10.0,
                          childAspectRatio: 1.0,
                        ),
                        delegate: SliverChildBuilderDelegate(
                          (BuildContext context, int index) {
  // I want to be able to use the CustomTabBuilder index inside 
  //here but i can't pass the index inside because 
  //it gets overridden by the index of the sliver builder
                            if (index &lt; serviceProvider.albums[data[0]].length) { 
                              return Image(
                                  image: NetworkImage(
                                      serviceProvider.albums[data[0]][index]));
                            }
                          },
                          childCount: serviceProvider.albums[data[0]].length,
                        ),
                      )
                    ],
                  ),
                  onPositionChange: (index) {
                    print('current position: $index');
                    currentPosition = index;
                  },
                ),
              ),
              bottomNavigationBar: Container(
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                      topRight: Radius.circular(10),
                      topLeft: Radius.circular(10)),
                  boxShadow: [
                    BoxShadow(
                        color: Colors.black38, spreadRadius: 0, blurRadius: 10),
                  ],
                ),
                child: ClipRRect(
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(10.0),
                    topRight: Radius.circular(10.0),
                  ),
                  child: Padding(
                    padding: const EdgeInsets.fromLTRB(50, 8, 50, 8),
                    child: ElevatedButton(
                        style:
                            ElevatedButton.styleFrom(minimumSize: Size(20, 50)),
                        onPressed: () =&gt; {print(&quot;Booking&quot;)},
                        child: Text(
                          &quot;Book now&quot;,
                          style: TextStyle(fontSize: 24),
                        )),
                  ),
                ),
              )
          )
      );
    }
  }
  </code></pre>
  <p>I tried using the onPositionChange function to pass the current index of the TabView inside the SliverGrid's delegate, however that still doesn't work because the images are loaded after the change happens and that is not the correct behavior</p>
  <p><a href="https://i.stack.imgur.com/d0PaV.png" rel="nofollow noreferrer">This is what I managed to build so far </a></p>
  <p>But as mentioned above whenever I change tabs same images are displayed, and when I use currentIndex the images do change but to the previous index not the actually pressed tab.</p>