Search code examples
flutterrichtext

FutureBuilder The method 'findRenderObject' was called on null


I want to render the position of a RichText built by a FutureBuilder as the code below, I used the WidgetsBinding.instance.addPostFrameCallback in the initState() but I got an error: The method 'findRenderObject' was called on null., I tried this approach without FutureBuilder works fine, I do not know how to solve this with FutureBuilder

class BookScreen extends StatefulWidget {
  int bookId;
  BookScreen(this.bookId);

  @override
  _BookScreenState createState() => _BookScreenState();
}

class _BookScreenState extends State<BookScreen> {

  final GlobalKey _itemKey = GlobalKey();

void initState() {
    super.initState();

WidgetsBinding.instance.addPostFrameCallback((_) {findRichText();});

  }
@override
  Widget build(BuildContext context) {

return FutureBuilder(
      future: Provider.of<Book>(context, listen: false)
          .getBookDetail(widget.bookId),
      builder: (ctx, snapshot) => snapshot.connectionState == ConnectionState.waiting
              ? Center(
                  child: CircularProgressIndicator(),
                )
              : ListView(
                  children: <Widget>[
                    Padding(
                      padding: const EdgeInsets.all(10.0),
                      child: RichText(
                        key: _itemKey, // Here is the global key
                        text: TextSpan(
                          children: _getTextSpan(snapshot.data),
                        ),
                      ),
                   ),
                ],
             ),
          );

void findRichText() {
    var richText = _itemKey.currentContext.findRenderObject() as RenderParagraph;
    print(richText.localToGlobal(Offset.zero));
}

Solution

  • It is possible to query the text position after it renders.

    For example, you can move ListView to a separate widget. When postframe callback is called, the text will already exist so you'll get its position

    class _BookScreenState extends State<BookScreen> {
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
          future: ...,
          builder: (ctx, snapshot) =>
          snapshot.connectionState == ConnectionState.waiting
              ? Center(child: CircularProgressIndicator())
              : BooksList(data: snapshot.data),
        );
      }
    }
    
    class BooksList extends StatefulWidget {
      final BooksListData data;
    
      BooksList({@required this.data});
    
      @override
      _BooksListState createState() => _BooksListState();
    }
    
    class _BooksListState extends State<BooksList> {
      final GlobalKey _itemKey = GlobalKey();
    
      @override
      Widget build(BuildContext context) {
        return ListView(
          children: [
            RichText(
              key: _itemKey,
              text: TextSpan(
                children: _getTextSpan(widget.data),
              ),
            ),
          ],
        );
      }
    
      void initState() {
        super.initState();
        WidgetsBinding.instance.addPostFrameCallback((_) {
          findRichText();
        });
      }
    
      void findRichText() {
        var richText = _itemKey.currentContext.findRenderObject() as RenderParagraph;
        print(richText.localToGlobal(Offset.zero));
      }
    }
    

    However this approach complicates the code and doesn't seem reliable.

    Alternatively, if you want scrolling to listview item, you can use scrollable_positioned_list package. It provides more declarative api:

    final ItemScrollController itemScrollController = ItemScrollController();
    
    ScrollablePositionedList.builder(
      itemCount: ...,
      itemBuilder: (context, index) => ...,
      itemScrollController: itemScrollController,
    );
    
    itemScrollController.jumpTo(
      index: 100,
      alignment: 0.5,
    );