Home > database >  Can I trigger grandparent state changes without an external state management library?
Can I trigger grandparent state changes without an external state management library?

Time:01-14

I cannot find a satisfactory way for a grandchild widget to trigger a grandparent state change. My app saves and sources its data all from an on-device database. Ive tried to proceed this far without using a state management library as I thought this was overkill - the app is not complex.

Ive got a ListView (grandparent), which in turn has children that are my own version of ListTiles. There are two icon buttons on each ListTile, one to edit and one to delete - both of which trigger a different alertdialog (grandchild) popup. When I perform an update or delete on the data, it is written to the db and a Future is returned - and then I need the grandparent ListView state to refresh. StatefulBuilders will only give me a way to refresh state on the grandchild (separately from the child), not a way to trigger 'multi level' state change.

Is it time for a state management solution such as BLOC or Riverpod, or is there any other solution?

ListView Grandparent Widget

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        body: Builder(
          builder: (BuildContext context) {
            return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                // other children here
                Expanded(
                  flex: 11,
                  child: FutureBuilder<List<MyCustomObject>>(
                  future: _getQuotes(), // queries the db
                  builder: (context, AsyncSnapshot snapshot) {
                    if (snapshot.connectionState == ConnectionState.waiting
                        && !snapshot.hasData) {
                      return const Center(
                        child: SizedBox(
                          height: AppDims.smallSizedBoxLoadingProgress,
                          width: AppDims.smallSizedBoxLoadingProgress,
                          child: CircularProgressIndicator()
                        ),
                      );
                    } else if (snapshot.hasError) {
                      log(snapshot.error.toString());
                      log(snapshot.stackTrace.toString());
                      return Center(child: Text(snapshot.error.toString()));
                    } else {
                        // no point using StatefulBuilder here, as i need 
                        // to potentially trigger _getQuotes() again to rebuild the entire ListView
                      return ListView.builder(
                        padding: const EdgeInsets.symmetric(
                          horizontal: AppDims.textHorizontalPadding,
                          vertical: AppDims.textVerticalPadding
                        ),
                        itemCount: snapshot.data!.length,
                        itemBuilder: (context, int index) {
                          return MyCustomTile(
                            // tile data mapping from snapshot for MyCustomObject
                          );
                        },
                      );
                    }
                  },
                )
              )
            ]
          );
        }
      )
    );
  }

MyCustomTile Child Widget

@override
  Widget build(BuildContext context) {
    return Card(
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.circular(AppDims.tileBorderRadius),
        side: const BorderSide(
          color: Colors.green,
          width: 1.5,
        )
      ),
      child: ListTile(
        // other omitted ListTile params here
        trailing: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          crossAxisAlignment: CrossAxisAlignment.stretch,
          mainAxisSize: MainAxisSize.min,
          children: [
            IconButton(
              icon: const Icon(Icons.edit),
              onPressed: () => showDialog(
                context: context,
                barrierDismissible: true,
                builder: (BuildContext context) {
                  return EditDialog();
                }
              ).then((_) => setState(() {})), // will only setState on the dialog!
            ),
            IconButton(
              icon: const Icon(Icons.delete),
              onPressed: () => showDialog(
                context: context,
                barrierDismissible: true,
                builder: (BuildContext context) => DeleteWarningDialog(
                  widget.id,
                  AppStrings.price.toLowerCase(),
                  true
                ),
              ),
            ),
          ]
        ),
      ),
    );
  }

DeleteWarningDialog Grandchild Widget

@override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: Text(_buildFinalWarningString()),
      actions: [
        TextButton(
          child: const Text(AppStrings.cancel),
          onPressed: () => Navigator.pop(context),
        ),
        TextButton(
          child: const Text(AppStrings.delete),
          onPressed: () {
            _appDatabase.deleteFoo(widget.objectIdToDelete);
            Navigator.pop(context);
          },
        )
      ],
    );
  }

CodePudding user response:

you can create a function that updates the state of the grandparent widget and pass it from grandparent to parent then from parent to grandchild and use it wherever you want to update the grandparent widget

CodePudding user response:

you will have to declare a function in the grandParent which is the listView in your case and pass it to parent and children's. but it will be so complicated and not really efficient, using state management would make it a lot easer and clean

  • Related