0

I want to add my ListView.seperated into Column, so that I can add CircularProgressIndicator below this ListvView when loading more items. I have used advices from How to add a ListView to a Column in Flutter? so that I made structure Column -> Expanded -> ListVie, but I got errors and list cannot be loaded:

======== Exception caught by rendering library =====================================================
RenderBox was not laid out: RenderRepaintBoundary#e8fad NEEDS-LAYOUT NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1929 pos 12: 'hasSize'
The relevant error-causing widget was: 

The relevant error-causing widget was: 
  Column file:///C://lib/ui/pages/home/page/HomePage.dart:125:16
When the exception was thrown, this was the stack: 
#0      RenderFlex.performLayout.<anonymous closure> (package:flutter/src/rendering/flex.dart:926:9)
#1      RenderFlex.performLayout (package:flutter/src/rendering/flex.dart:929:6)
#2      RenderObject.layout (package:flutter/src/rendering/object.dart:1781:7)
#3      ChildLayoutHelper.layoutChild (package:flutter/src/rendering/layout_helper.dart:54:11)
#4      RenderFlex._computeSizes (package:flutter/src/rendering/flex.dart:829:43)

  Column file:///C:/.../.../lib/ui/pages/home/page/HomePage.dart:125:16

Could you tell me what have I done wrong? This AdvertisementTile builds new widgets, but it is built onto another column. Maybe this is wrong?

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:.../ui/pages/home/page/AdvertisementCard.dart';
import 'package:.../ui/pages/home/page/model/AdvertisementList.dart';

import '../../SizedBox.dart';
import 'AdvertisementProdRepository.dart';
import 'BottomAppBar.dart';
import 'FAB.dart';
import 'model/AdvertisementList.dart';

class HomePage extends StatefulWidget {
  final String jwt;

  const HomePage(this.jwt);

  @override
  _HomePage createState() => _HomePage();

  factory HomePage.fromBase64(String jwt) => HomePage(jwt);
}

class _HomePage extends State<HomePage> {
  late final String jwt;
  late Future<AdvertisementList> _listOfItems;
  final searchTextController = TextEditingController();

  @override
  void initState() {
    super.initState();
    jwt = widget.jwt;
    _listOfItems = AdvertisementProdRepository.fetchAdvertisements(1);
  }

  @override
  Widget build(BuildContext context) => Scaffold(
        body: Scaffold(
          backgroundColor: const Color(0xFEF9F9FC),
          floatingActionButtonLocation:
              FloatingActionButtonLocation.centerDocked,
          floatingActionButton: buildFAB(),
          bottomNavigationBar: BuildBottomAppBar(),
          body: Container(
            padding: EdgeInsets.only(left: 25.0, right: 25, top: 25),
            width: MediaQuery.of(context).size.width,
            height: MediaQuery.of(context).size.height,
            child: Column(
              children: [
                TextFormField(
                  controller: searchTextController,
                  decoration: InputDecoration(
                      prefixIcon: Icon(Icons.search),
                      border: OutlineInputBorder(),
                      hintText: 'Szukaj',
                      fillColor: Color(0xffeeeeee),
                      filled: true),
                ),
                buildSizedBox(20.0),
                Padding(
                  padding: const EdgeInsets.only(left: 4),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    children: [
                      Text(
                        'Najnowsze ogłoszenia',
                        style: TextStyle(
                            fontSize: 20, fontWeight: FontWeight.bold),
                        textAlign: TextAlign.left,
                      ),
                    ],
                  ),
                ),
                buildSizedBox(10.0),
                FutureBuilder<AdvertisementList>(
                  future: _listOfItems,
                  builder: (context, snapshot) {
                    if (!snapshot.hasData) {
                      return Center(child: CircularProgressIndicator());
                    } else {
                      return AdvertisementTile(advertisements: snapshot.data!);
                    }
                  },
                ),
              ],
            ),
          ),
        ),
      );
}

class AdvertisementTile extends StatefulWidget {
  final AdvertisementList advertisements;

  AdvertisementTile({Key? key, required this.advertisements}) : super(key: key);

  @override
  State<StatefulWidget> createState() => AdvertisementTileState();
}

class AdvertisementTileState extends State<AdvertisementTile> {
  AdvertisementLoadMoreStatus loadMoreStatus =
      AdvertisementLoadMoreStatus.STABLE;
  final ScrollController scrollController = new ScrollController();
  late List<Advertisement> advertisements;
  late int currentPageNumber;
  bool _loading = false;

  @override
  void initState() {
    advertisements = widget.advertisements.items;
    currentPageNumber = widget.advertisements.pageNumber;
    super.initState();
  }

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

  @override
  Widget build(BuildContext context) {
    return NotificationListener(
      onNotification: onNotification,
        child: Column(
          children: <Widget>[
            Expanded(
              child: ListView.separated(
                padding: EdgeInsets.zero,
                scrollDirection: Axis.vertical,
                controller: scrollController,
                itemCount: advertisements.length,
                physics: const AlwaysScrollableScrollPhysics(),
                itemBuilder: (_, index) {
                  return AdvertisementCard(data: advertisements[index]);
                },
                separatorBuilder: (BuildContext context, int index) {
                  return SizedBox(
                    height: 10,
                  );
                },
              ),
            ),
          ],
        ),
    );
  }
  /*child: Padding(
  padding: const EdgeInsets.only(bottom: 28.0),*/
  bool onNotification(ScrollNotification notification) {
    if (notification is ScrollUpdateNotification) {
      if (scrollController.position.maxScrollExtent > scrollController.offset &&
          scrollController.position.maxScrollExtent - scrollController.offset <=
              50) {
        if (loadMoreStatus == AdvertisementLoadMoreStatus.STABLE) {
          loadMoreStatus = AdvertisementLoadMoreStatus.LOADING;
          _loading = true;
          AdvertisementProdRepository.fetchAdvertisements(currentPageNumber + 1)
              .then((advertisementObject) {
            currentPageNumber = advertisementObject.pageNumber;
            loadMoreStatus = AdvertisementLoadMoreStatus.STABLE;
            setState(() => advertisements.addAll(advertisementObject.items));
            _loading = false;
          });
        }
      }
    }
    return true;
  }
}

enum AdvertisementLoadMoreStatus { LOADING, STABLE }

enter image description here

kpoads
  • 51
  • 6

2 Answers2

1

So the way a Column behaves is that when you give it 2 children, the flutter engine estimates the space that the single widget would cover, and then it would render those two widgets accordingly. An Expanded widget tells flutter to take a widget and then cover as much space as that widget can take. In some cases, it makes sense to use an Expanded widget if the other widget is wrapped in a Container, this way flutter knows that you are referring to the remaining space. ListView is a widget that takes all available spaces available.

So to fix your render issue you can put the Listview in a Container and give the Container a fixed height then flutter knows how to manage your screen and avoid the rendering issue.

If you eventually get any, you'll still have most of your UI, then you'll just have to tweak the height of your Container.

E.g

                      Container(
                        height: 400,
                        child: ListView.separated(
                            itemBuilder: (context, index) {
                              return AdvertisementCard(
                                  data: advertisements[index]);
                            },
                            separatorBuilder: (context, index) {
                              return SizedBox(
                                height: 10,
                              );
                            },
                            itemCount: reports.length),
                      )
Denzel
  • 942
  • 5
  • 14
  • yea, but how can I know the height of the container if list can return 10 or 20 elements? Im not sure how much items will be returned. Also I dont know resoltuion of user phone so it should be dynamically. – kpoads Aug 05 '21 at 21:15
0

You should constrain the height of the Listview as the Column wants to get all the available height of its parent (likely Scaffold in your case). So, an unbounded-height-widget as a Listview will cause this kind of error. Solution: wrap the Listview with a Container of some height.

  • yea, but how can I know the height of the container if list can return 10 or 20 elements? Im not sure how much items will be returned. Also I dont know resoltuion of user phone so it should be dynamically. – kpoads Aug 05 '21 at 21:16
  • to calculate the height dynamically you can set the `Container` height as follows `height: MediaQuery.of(context).size.height * 0.6`. and you can set `shrinkWrap` proberty of the `Listview` to `true` so that the `Listview` will wrap its content and be as high as its children allows it to be. – Mohamed Yahia Aug 05 '21 at 21:54
  • now the issue is you don't know how many items will be retrieved so you can have a variable `bool smallNumofItems` and a have a condition on the `Container` height to be null if `smallNumofItems` is true otherwise use `MediaQuery` with `shrinkWrap` set to true as it is. – Mohamed Yahia Aug 05 '21 at 22:04
  • but why with Expanded it does not work? It is one of solutions in link I provided in question. – kpoads Aug 06 '21 at 10:16
  • Wrapping the `Listview` with `Expanded` is like expanding what is already expanded. that's why the CircularProgressIndicator() won't appear. by the way there is a package that achieve this, it's called [lazy loading](https://pub.dev/packages/lazy_load_scrollview) – Mohamed Yahia Aug 06 '21 at 19:52