rrousselGit / flutter_hooks

React hooks for Flutter. Hooks are a new kind of object that manages a Widget life-cycles. They are used to increase code sharing between widgets and as a complete replacement for StatefulWidget.
MIT License
3.13k stars 179 forks source link

Why list not updating? #401

Closed tony123S closed 9 months ago

tony123S commented 9 months ago

In page A, I'm calling an API to get list data from server , then add it into list.

Page A code

Widget build(BuildContext context) { 

ValueNotifier<List> list = useState([]);

useEffect(() {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    context
        .read<AgentVehiclesListCubit>()
        .getAgentVehiclesList(
            GetAgentVehiclesListParams(tenancyId: tenancy.id!))
        .whenComplete(() {
      final state = BlocProvider.of<AgentVehiclesListCubit>(context).state;
      if (state is AgentVehiclesListDone) {
        list.value = state.tenancy;  // add the output from server to list
      }
    });
  });

  return () {};
}, const []); 

When Button is pressed, pass the list to EditScreen.

InkWell(
   onTap: () {
     Navigator.pushNamed(
        context, TenancyRoutes.editScreen,
        arguments: list);
},

In EditScreen, there is a floatingActionButton for user to add vehicleItems. Once added, the item should be added into the list, and Listview should be refreshed.

class EditScreen extends HookWidget {
      final ValueNotifier<List<VehicleItems>> list;

      const EditScreen(this.list, {super.key});

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          floatingActionButton: FloatingActionButton(
              backgroundColor: AppTheme.light.colorScheme.primary,
              onPressed: () {
                Navigator.pushNamed(context, TenancyRoutes.createVehicleItemsScreen)
                    .then(((onValue) {
                  if (onValue is VehicleItems) {
                    debugPrint(onValue.toString());
                    list.value = [...list.value..add(onValue)]; // add new item into here
                  }
                }));
              },
              child: const Icon(
                Icons.add,
                color: Colors.white,
              )),
          appBar: AppBar(
              iconTheme: const IconThemeData(color: Colors.white),
              backgroundColor: AppTheme.light.colorScheme.primary,
              title: Text(
                'vehicles'.tr(),
                style: const TextStyle(color: Colors.white),
              ),
              actions: const []),
          body: _showBody(list),
        );
      }

      Widget _showBody(ValueNotifier<List<VehicleItems>> list) {
        return ListView.separated(
            padding: EdgeInsets.zero,
            shrinkWrap: true,
            separatorBuilder: (context, index) => Padding(
                padding: const EdgeInsets.symmetric(horizontal: 20),
                child: Container(
                  color: Colors.grey.shade200,
                  height: 1,
                )),
            itemCount: list.value.length,
            itemBuilder: (context, index) {
              final items = list.value[index];
              return InkWell(
                  onTap: () {},
                  child: ListItemPadding(
                    child: VehicleItem(items),
                    // isDeleteVisible: false,
                  ));
            });
      }
    }

However, the listView remains unchanged unless I hot reload it. How to fix it?

rrousselGit commented 9 months ago

You're not listening to the ValueNotifier

Use useValueListenable(list);

tony123S commented 9 months ago

You're not listening to the ValueNotifier

Use useValueListenable(list);

Sorry, I have edited my post to make it clearer. I'm using useState in Page A.
I have added useValueListenable after list.value = [...list.value..add(onValue)];, , but still same.

rrousselGit commented 9 months ago

My answer doesn't change. Use useValueListenable in EditScreen :)

tony123S commented 9 months ago

Error after useValueListenable is added,


E/flutter (23009): [ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: 'package:flutter_hooks/src/framework.dart': Failed assertion: line 133 pos 12: 'HookElement._currentHookElement != null': Hooks can only be called from the build method of a widget that mix-in `Hooks`.
E/flutter (23009): 
E/flutter (23009): Hooks should only be called within the build method of a widget.
E/flutter (23009): Calling them outside of build method leads to an unstable state and is therefore prohibited.
E/flutter (23009): 
E/flutter (23009): #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
E/flutter (23009): #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
E/flutter (23009): #2      Hook.use (package:flutter_hooks/src/framework.dart:133:12)
E/flutter (23009): #3      use (package:flutter_hooks/src/framework.dart:18:32)
E/flutter (23009): #4      useValueListenable (package:flutter_hooks/src/listenable.dart:9:3)
E/flutter (23009): #5      EditScreen.build.<anonymous closure>.<anonymous closure> (package:xxx/edit_screen.dart:23:17)
E/flutter (23009): <asynchronous suspension>

Am I added it in the right place?

tony123S commented 9 months ago

fixed.

Widget _showBody(ValueNotifier<List<VehicleItems>> list) {
  final vehicleList = useValueListenable(list); // useValueListenable put here instead
  return ListView.separated(
      padding: EdgeInsets.zero,
      shrinkWrap: true,
      separatorBuilder: (context, index) => Padding(
          padding: const EdgeInsets.symmetric(horizontal: 20),
          child: Container(
            color: Colors.grey.shade200,
            height: 1,
          )),
      itemCount: vehicleList.length,
      itemBuilder: (context, index) {
        final items = vehicleList[index];
        return InkWell(
            onTap: () {},
            child: ListItemPadding(
              child: VehicleItem(items),
              // isDeleteVisible: false,
            ));
      });
}
soxft commented 5 months ago

I have the same questions, useValueListenable is still not working for me when using xx.value.add(1) but xx.value = [...xx.value, 1] works

here is the code

class CampusMerchantHome extends HookWidget {
  const CampusMerchantHome({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final devices = useState<List<int>>([]);

    Future<void> initPrinter() async {
      devices.value.add(1);
    }

    Widget deviceItem(ValueNotifier<List<int>> device) {
      final deviceListener = useValueListenable(device);

      return ListView.builder(
        itemCount: deviceListener.length,
        itemBuilder: (context, index) {
          final deviceChoose = deviceListener[index];

          return ListTile(
            title: Text(deviceChoose.toString()),
          );
        },
      );
    }

    return Scaffold(
      appBar: AppBar(
        title: const Text("welcome to use 校悦送"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: () => initPrinter(),
              child: const Text('initPrinter'),
            ),
            Expanded(
              child: deviceItem(devices),
            ),
          ],
        ),
      ),
    );
  }
}
shahriarsihamstat commented 5 months ago

I have the same questions, useValueListenable is still not working for me when using xx.value.add(1) but xx.value = [...xx.value, 1] works

@soxft I had the same problem, and your solution worked for me as well. Kudos!