0

I'm having quite lot of problems to render the items of a ListViewBuilder with a dynamic height, meaning that the height will change accordingly to the number of items that i want to render. Moreover, the listview is NeverScrollable since i wrapped it with a SingleChildScrollView to scroll the listView together with other widgets as a unique widget. Finally, the shrinkWrap is set to True for the listViewBuilder. The problem is that if i fix the height of the TabBarView, which contains the ListViewBuilder, to a value which is bigger than the all items summed up height, then i will be left with white space! On the other hand, if the height is smaller, then some items aren't rendered!!! Do you have any solutions for this? Thanks!

Below the code:

  1. This the HomePage: i have a Column with a Container for the search bar and a FoodPageView() (this last one is expanded) as children:
  const HomePage({
    Key? key,
  }) : super(key: key);

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

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: 1,
        selectedIconTheme: const IconThemeData(
          color: Colors.blue,
        ),
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: 'Home',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.shopping_cart),
            label: 'Cart',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: 'Me',
          ),
        ],
      ),
      body: SafeArea(
        child: Column(
          children: [
            Container(
              margin: const EdgeInsets.only(
                top: 20,
                left: 10,
                right: 10,
              ),
              child: Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Expanded(
                    child: TextField(
                      decoration: InputDecoration(
                        filled: true,
                        fillColor: Colors.blue[100],
                        border: OutlineInputBorder(
                          borderRadius: BorderRadius.circular(15),
                          borderSide: const BorderSide(
                            width: 0,
                            style: BorderStyle.none,
                          ),
                        ),
                        contentPadding: const EdgeInsets.only(
                          left: 20,
                        ),
                        hintText: "Search store",
                      ),
                    ),
                  ),
                  Container(
                    margin: const EdgeInsets.only(
                      left: 10,
                    ),
                    height: Dimensions.height50,
                    width: Dimensions.width50,
                    decoration: BoxDecoration(
                      borderRadius: BorderRadius.circular(15),
                      color: Colors.amberAccent,
                    ),
                    child: const Icon(
                      Icons.search_outlined,
                    ),
                  )
                ],
              ),
            ),
            SizedBox(
              height: Dimensions.height20,
            ),
            const Expanded(child: FoodPageView()),
          ],
        ),
      ),
    );
  }
}
  1. The FoodPageView() implementation: it contains a Column with children a PageViewBuilder, a DotsIndicator and finally a custom NavigationBarTab()
  const FoodPageView({Key? key}) : super(key: key);

  @override
  State<FoodPageView> createState() => _FoodPageViewState();
}

class _FoodPageViewState extends State<FoodPageView> {
  final PageController _pageController = PageController(
    viewportFraction: 0.85,
  );
  double _currPageValue = 0.0;
  final double _scaleFactor = 0.8;
  final int _height = 300;

  @override
  void initState() {
    super.initState();
    _pageController.addListener(() {
      setState(() {
        _currPageValue = _pageController.page!;
      });
    });
  }

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return SingleChildScrollView(
      physics: const ScrollPhysics(),
      child: Column(
        children: [
          SizedBox(
            height: Dimensions.height290,
            child: BlocBuilder<ProductsBloc, ProductsState>(
              builder: (context, state) {
                final List<Product> productsPromos = state.products
                    .where((product) => product.hasPromo == true)
                    .toList();

                return PageView.builder(
                  controller: _pageController,
                  physics: const ScrollPhysics(),
                  itemCount: productsPromos.length,
                  itemBuilder: ((context, index) {
                    Matrix4 matrix = Matrix4.identity();
                    if (index == _currPageValue.floor()) {
                      final double currScale =
                          1 - (_currPageValue - index) * (1 - _scaleFactor);
                      final double currTrans = _height * (1 - currScale) / 2;
                      matrix = Matrix4.diagonal3Values(1, currScale, 1)
                        ..setTranslationRaw(0, currTrans, 0);
                    } else if (index == _currPageValue.floor() + 1) {
                      final double currScale = _scaleFactor +
                          (_currPageValue - index + 1) * (1 - _scaleFactor);
                      final double currTrans = _height * (1 - currScale) / 2;
                      matrix = Matrix4.diagonal3Values(1, currScale, 1)
                        ..setTranslationRaw(0, currTrans, 0);
                    } else if (index == _currPageValue.floor() - 1) {
                      final double currScale =
                          1 - (_currPageValue - index) * (1 - _scaleFactor);
                      final double currTrans = _height * (1 - currScale) / 2;
                      matrix = Matrix4.diagonal3Values(1, currScale, 1)
                        ..setTranslationRaw(0, currTrans, 0);
                    } else {
                      const double currScale = 0.8;
                      final double currTrans = _height * (1 - _scaleFactor) / 2;
                      matrix = Matrix4.diagonal3Values(1, currScale, 1)
                        ..setTranslationRaw(0, currTrans, 0);
                    }

                    return Transform(
                      transform: matrix,
                      child: Stack(
                        children: [
                          Container(
                            height: Dimensions.height200,
                            margin: const EdgeInsets.only(
                              right: 10,
                            ),
                            decoration: BoxDecoration(
                              image: DecorationImage(
                                fit: BoxFit.fill,
                                image: AssetImage(productsPromos[index].image),
                              ),
                              borderRadius: BorderRadius.circular(20),
                            ),
                          ),
                          Align(
                            alignment: Alignment.bottomCenter,
                            child: Container(
                              height: Dimensions.height100,
                              margin: const EdgeInsets.only(
                                left: 30,
                                right: 30,
                                bottom: 15,
                              ),
                              decoration: BoxDecoration(
                                boxShadow: const [
                                  BoxShadow(
                                    color: Colors.grey,
                                    blurRadius: 5,
                                    offset: Offset(0, 5),
                                  ),
                                  BoxShadow(
                                    color: Colors.white,
                                    blurRadius: 0,
                                    offset: Offset(-5, 0),
                                  ),
                                  BoxShadow(
                                    color: Colors.white,
                                    blurRadius: 0,
                                    offset: Offset(5, 0),
                                  ),
                                ],
                                color: Colors.white,
                                borderRadius: BorderRadius.circular(20),
                              ),
                              child: Container(
                                padding: const EdgeInsets.only(
                                  left: 10,
                                  top: 10,
                                  bottom: 10,
                                ),
                                child: Row(
                                  mainAxisAlignment:
                                      MainAxisAlignment.spaceBetween,
                                  children: [
                                    Column(
                                        crossAxisAlignment:
                                            CrossAxisAlignment.start,
                                        children: [
                                          Text(
                                            productsPromos[index].name,
                                            style: const TextStyle(
                                              fontSize: 20,
                                            ),
                                          ),
                                          const SizedBox(height: 10),
                                          const Text(
                                            'Offer',
                                            style: TextStyle(
                                              fontSize: 15,
                                              fontWeight: FontWeight.bold,
                                            ),
                                          ),
                                          const SizedBox(height: 5),
                                          Text(
                                            '${productsPromos[index].promo!.percentagePromo}% Off',
                                            style: const TextStyle(
                                              fontSize: 17,
                                              fontWeight: FontWeight.bold,
                                            ),
                                          ),
                                        ]),
                                    const Icon(
                                      Icons.keyboard_arrow_right_outlined,
                                      size: 40,
                                    ),
                                  ],
                                ),
                              ),
                            ),
                          ),
                        ],
                      ),
                    );
                  }),
                );
              },
            ),
          ),
          BlocBuilder<ProductsBloc, ProductsState>(
            builder: (context, state) {
              final List<Product> productsPromos = state.products
                  .where((product) => product.hasPromo == true)
                  .toList();
              return DotsIndicator(
                dotsCount: productsPromos.length,
                position: _currPageValue,
                decorator: DotsDecorator(
                  activeSize: Size(Dimensions.width20, Dimensions.height10),
                  activeShape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.circular(
                      5,
                    ),
                  ),
                ),
              );
            },
          ),
          SizedBox(height: Dimensions.height5),
          const NavigationBarTab(),
        ],
      ),
    );
  }
}
  1. The NavigationBarTab() page below: it is a Column with a Tab Item Menu followed by the corresponding tabView page. Each tabView page is a ListViewBuilder FoodListView()
  const NavigationBarTab({Key? key}) : super(key: key);

  @override
  State<NavigationBarTab> createState() => _NavigationBarTabState();
}

class _NavigationBarTabState extends State<NavigationBarTab>
    with TickerProviderStateMixin {
  @override
  Widget build(BuildContext context) {
    final TabController tabController = TabController(
      length: 4,
      vsync: this,
    );
    return Column(
      children: [
        SizedBox(
          height: 30,
          child: TabBar(
            isScrollable: false,
            controller: tabController,
            labelColor: Colors.black,
            unselectedLabelColor: Colors.grey,
            tabs: const [
              Tab(
                text: 'Pizza',
              ),
              Tab(
                text: 'Specials',
              ),
              Tab(
                text: 'Desserts',
              ),
              Tab(
                text: 'Drinks',
              ),
            ],
          ),
        ),
        SizedBox(
          height: 700,
          child: TabBarView(
            physics: const NeverScrollableScrollPhysics(),
            controller: tabController,
            children: const [
              Expanded(child: FoodListView()),
              Expanded(child: FoodListView()),
              Expanded(child: FoodListView()),
              Expanded(child: FoodListView()),
            ],
          ),
        )
      ],
    );
  }
}
  1. Finally the FoodListView() page: a ListViewBuilder with NeverScrollable physics and shrinkWrap to true.
class FoodListView extends StatefulWidget {
  const FoodListView({Key? key}) : super(key: key);

  @override
  State<FoodListView> createState() => _FoodListViewState();
}

class _FoodListViewState extends State<FoodListView> {
  final PageController _pageController = PageController();

  @override
  void dispose() {
    _pageController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ProductsBloc, ProductsState>(
      builder: (context, state) {
        final List<Product> products = state.products
            .where(
              (element) => element.hasPromo == false,
            )
            .toList();

        return ListView.builder(
          physics: const NeverScrollableScrollPhysics(),
          shrinkWrap: true,
          itemCount: products.length,
          itemBuilder: ((context, index) {
            return FoodCard(
              index: index,
              products: products,
            );
          }),
        );
      },
    );
  }
}
  1. The FoodCard() is a Container with fixed height.
  final int index;
  final List<Product> products;
  const FoodCard({
    Key? key,
    required this.index,
    required this.products,
  }) : super(key: key);

  @override
  State<FoodCard> createState() => _FoodCardState();
}

class _FoodCardState extends State<FoodCard> {
  @override
  Widget build(BuildContext context) {
    return BlocBuilder<ProductsBloc, ProductsState>(
      builder: (context, state) {
        return Container(
          height: 125,
          decoration: BoxDecoration(
            borderRadius: BorderRadius.circular(10),
            border: Border.all(
              color: Colors.grey,
            ),
          ),
          margin: const EdgeInsets.only(
            left: 5,
            right: 5,
            top: 5,
          ),
          child: Row(
            children: [
              Container(
                margin: const EdgeInsets.only(
                  top: 5,
                  left: 5,
                  bottom: 5,
                ),
                height: Dimensions.height120,
                width: Dimensions.width120,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    fit: BoxFit.fill,
                    image: AssetImage(
                      widget.products[widget.index].image,
                    ),
                  ),
                  borderRadius: const BorderRadius.only(
                    topLeft: Radius.circular(10),
                    bottomLeft: Radius.circular(10),
                  ),
                ),
              ),
              Expanded(
                child: Container(
                  padding: const EdgeInsets.only(
                    left: 5,
                  ),
                  margin: const EdgeInsets.only(
                    top: 5,
                    bottom: 5,
                    right: 5,
                  ),
                  height: Dimensions.height120,
                  decoration: const BoxDecoration(
                    color: Colors.white,
                    borderRadius: BorderRadius.only(
                      topRight: Radius.circular(10),
                      bottomRight: Radius.circular(10),
                    ),
                  ),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.spaceBetween,
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          Text(
                            widget.products[widget.index].name,
                            style: const TextStyle(
                              fontSize: 17,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          SizedBox(
                            height: Dimensions.height10,
                          ),
                          Expanded(
                            child: Stack(children: [
                              SizedBox(
                                height: Dimensions.height150,
                                width: Dimensions.width210,
                                child: Text(
                                  widget.products[widget.index].description,
                                  maxLines: 4,
                                  style: const TextStyle(
                                    overflow: TextOverflow.ellipsis,
                                  ),
                                ),
                              ),
                            ]),
                          ),
                          SizedBox(
                            height: Dimensions.height5,
                          ),
                          Text(
                            '\$ ${widget.products[widget.index].price}',
                            style: const TextStyle(
                              fontSize: 20,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ],
                      ),
                      Container(
                        height: Dimensions.height30,
                        width: Dimensions.width30,
                        decoration: BoxDecoration(
                          color: Colors.amber,
                          borderRadius: BorderRadius.circular(10),
                        ),
                        child: const Icon(
                          Icons.shopping_cart,
                          color: Colors.white,
                        ),
                      )
                    ],
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

enter image description here enter image description here

Andi
  • 43
  • 9

3 Answers3

0

When you try two use Expanded widget indirectly inside column or row widget, you you should wrap the parent with Expanded too, so remove the SizedBox and do this:

Expanded(
  child: TabBarView(
            physics: const NeverScrollableScrollPhysics(),
            controller: tabController,
            children: const [
              FoodListView(),
              FoodListView(),
              FoodListView(),
              FoodListView(),
            ],
          ),
),
eamirho3ein
  • 16,619
  • 2
  • 12
  • 23
  • i get another exception: The following assertion was thrown during performLayout(): RenderFlex children have non-zero flex but incoming height constraints are unbounded. – Andi Sep 28 '22 at 13:53
  • @Andi I updated my answer again, check it out. – eamirho3ein Sep 28 '22 at 14:12
0

You can either move the whole widget inside a expanded. Which is considered the most used way of doing it. Like this ->

Expanded(
   child: TabBarView(
        physics: const NeverScrollableScrollPhysics(),
        controller: _yourController,
        children: const [
          FoodListView(),
          FoodListView(),
          FoodListView(),
          FoodListView(),
        ],
      ),
   ),

For this you'll have to remove the ScrollView you're using. Or In your wayyy, What you can try doing is, Enabling -> "shrinkWrap: true," inside your ListView.Builder(). And in both ways, you should be able to get rid of the fixed height you're providing now.

Hippo Fish
  • 416
  • 2
  • 9
  • the first option gives me "RenderFlex children have non-zero flex but incoming height constraints are unbounded." It seems that i need to give him the height. The shrinkWrap is already set to true. – Andi Sep 28 '22 at 13:59
  • Extremely sorry, I forgot to mention this, But in the first scenerio you should remove the SignleChildScrollView that you're using rn. Im updating my answer. – Hippo Fish Sep 28 '22 at 14:50
  • Ok, but if i remove the SingleChildScroll then just the listviewbuilder will be scrollable, whereas i want all the widget to be scrollable. – Andi Sep 28 '22 at 18:03
  • Aaaah I see, What you're looking for is Sliver widgets. But you can achieve it this way, but that will be supper like a sneaky way to manage it. – Hippo Fish Sep 29 '22 at 04:49
0

I solved the problem by replacing the TabBarView with a IndexedStack widget. It Works pretty good!! Here's the code of the indexedStack:

IndexedStack(
          index: _selectedIndex,
          children: [
            Visibility(
              visible: _selectedIndex == 0,
              maintainState: true,
              child: const FoodListView(
                category: "Pizza",
              ),
            ),
            Visibility(
              visible: _selectedIndex == 1,
              maintainState: true,
              child: const FoodListView(
                category: "Specialities",
              ),
            ),
            Visibility(
              visible: _selectedIndex == 2,
              maintainState: true,
              child: const FoodListView(
                category: "Desserts",
              ),
            ),
            Visibility(
              visible: _selectedIndex == 3,
              maintainState: true,
              child: const Text("index 3"),
            ),
          ],
        )```
Andi
  • 43
  • 9