skip to Main Content

I’m using the gsheets package to asynchronously read and write data to a Google spreadsheet. I click a button on an AppBar() on the home page to add a row to the database. The row is successfully added to the spreadsheet. Here is the code:

// import statements

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

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

class _HomePageState extends State<HomePage> {
  bool viewNotebooks = false;
  bool viewList = true;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leadingWidth: 650,
        leading: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            // code irrelevant to this question, then
            TextButton.icon(
                icon: const Icon(Icons.note_add_outlined),
                label: const Text("New Note"),
                onPressed: () async {
                  await Data.addNote(
                      title: 'New Note', content: '[content goes here]');
                  setState(() {});
                }),
            // more code 
          ],
        ),
      ),
      body: Row(
        children: [
          // more code
          viewList
              ? const Expanded(
                  flex: 1,
                  child: NotesListView(),
                )
              : Container(),
          // more code
        ],
      ),
    );
  }
}

A ListView.builder() should then rebuild to include the new row’s data in its items. Here is the relevant code, in a separate .dart file:

import 'package:flutter/material.dart';
import '../providers/sheets_api.dart';

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

  @override
  State<NotesListView> createState() => _NotesListViewState();
}

class _NotesListViewState extends State<NotesListView> {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: Data.getRows(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.waiting) {
          return const Center(
           child: CircularProgressIndicator(),
         );
        }
        if (snapshot.hasData) {
          return ListView.builder(
            shrinkWrap: true,
            itemCount: snapshot.data!.length,
            itemBuilder: (context, index) {
              return ListTile(
                  title: Text(snapshot.data![index][1]),
                  subtitle: Text(snapshot.data![index][2]),
                  onTap: () async {
                    var id = snapshot.data![index][0];
                    // print(await Data.getNoteById(id));
                  });
            },
          );
       } else {
          return const Center(
            // render the loading indicator
           child: CircularProgressIndicator(),
          );
        }
      },
    );
  }
}

The widget, however, does not rebuild when the spreadsheet content changes. But when I put the code in the app’s main.dart file, even in a widget outside _NotesListViewState, the UI does rebuild as expected.

Widget notesListView() {
  return FutureBuilder (
  // rest of code

I’m struggling to understand what’s going on. I’ve read about BuildContexts and widget trees, and about keys, and I assume it’s something about the BuildContext for NotesListView being outside the main app’s widget tree? But I don’t really understand and, if that’s the case, I don’t know how to have NotesListView be separate (with its own BuildContext) and yet still be seen within the main app’s context. I know I could use something like Provider to control when widgets rebuild, or just keep the custom notesListView widget in HomePage, but I’d like to understand what’s going on here.

2

Answers


  1. I assume that the reason is const word here:

    viewList
                  ? const Expanded(
                      flex: 1,
                      child: NotesListView(),
                    ) ...
    

    As an option, you could add key:

    viewList
                  ? const Expanded(
                      flex: 1,
                      child: NotesListView(key: UniqueKey()),//or other type of key
                    ) ...
    

    It will rebuild widget every time on parent rebuild. Or you could override method didUpdateWidget() in the NotesListView and call future :

    Data.getRows()
    

    when parent widget rebuilds.

    Login or Signup to reply.
  2. Flutter will rebuild the affected widgets when it decides that a change has been made. For widget state changes you use, e.g.

    setState(() => myState = true);
    

    within a stateful widget to notify Flutter that you are changing state and that it should rebuild the widget tree for affected widgets. It is worth mentioning that Flutter will compare the current value of myState in the widget tree and the new value and will only rebuild if they differ.

    Crucially, it is Flutter which decides whether a rebuild is required. You may have called setState() but you didn’t change any local state within that method. Flutter would have analysed the widget tree and decided that there were no changes. It is not enough to have made changes to your data source in the way that you have. In general terms, your data source or provider needs to tell Flutter that it has changed by way of a notification which mutates inherited (think global) state. This can be done manually with InheritedWidget, or you can use packages like provider or riverpod or get. These packages handle this reactive communication for you so you don’t have to do it manually. In riverpod for example you make a watch() call at the top of the build() method of a widget which says ‘when this data source changes I want you to rebuild’. You then mutate your data source via dedicated mutation methods, which then kick off the notification to Flutter to rebuild. I think you need to plump for one of these packages for an easy life. I went for riverpod which has a bit of a learning curve, but nothing insurmountable. Once you understand this reactive component, which is really crucial, then everything should fall into place.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search