代码之家  ›  专栏  ›  技术社区  ›  Alexandre B.

这种使用ValueNotifier和FutureBuilder的方法正确吗?

  •  0
  • Alexandre B.  · 技术社区  · 7 月前

    我正在读取一个JSON文件,以根据数据生成布局。基本上,它返回一列或一行。我正在使用FutureBuilder来处理数据加载。

    我也在使用 值通知器 检测何时 准备安装 更改并触发setState()。

    你觉得这种方法怎么样?你认为这是处理这种情况的适当方式吗?

    我注意到一件事:当我点击“更新”按钮时,打印(“确定”);被打了两次电话。这是预期的行为吗?

    代码运行良好,但我的目标是学习实现它的正确方法。

     class InitInterface extends StatefulWidget {
      const InitInterface({super.key});
    
      @override
      State<InitInterface> createState() => _InitInterfaceState();
    }
    
    class _InitInterfaceState extends State<InitInterface>  {
      //used in FutureBuilder to know when we are ready to start install the layout.
      late Future<Widget> readyToInstall; 
      late Stream<Widget> readyToInstall0;
    
      @override
      void initState() {
        //will read json file and get data to install the layout
        readyToInstall = loadLayout();
        readyToInstall.then((value){
          print('teste');
          panelData.addListener(() { //panelData is objhect from a class that extend ValueNotifier
            readyToInstall = updateLayout();
            setState(() {});        
          });
        });   
        super.initState();
      }
    
    
    
    
      @override
      Widget build(BuildContext context) {   
        return Column(
          children: [
            FutureBuilder<Widget>(
              future: readyToInstall, 
              builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
                if (snapshot.hasData) {
                  print('ok'); //<--called two times after update
                  return snapshot.data!;             
                } else if (snapshot.hasError) {
                  return Text('Error: ${snapshot.error}');
                } else {
                  return const Loading();
                }
              },
            ),
    
            ElevatedButton(
                onPressed:(){
                  readyToInstall = updateLayout();
                  setState(() {});              
                  },
                child: Text('Update')),
          ],
        );
      }
    }
    
    1 回复  |  直到 7 月前
        1
  •  2
  •   TripleNine    7 月前

    请勿使用 Widget 在你的业务逻辑中

    你应该避免使用 小装置 在ui代码之外。 InitInterfaceState 应直接接收数据,并根据数据决定布局:

    class _InitInterfaceState extends State<InitInterface> {
      late Future<Data> data;
      late Stream<Data> streamOfData;
      ...
    

    然后定义一个方法,该方法实际决定 Column Row 应该在widget树中使用:

    Widget getLayout(Data? data) {
      if (data == null) {
      // Throw exception if the data is null
        throw Exception(...);
      }
      if (data.needsColumn) {
        return Column(...);
      } else {
        return Row(...);
      }
    }
    

    然后,您可以在FutureBuilder中调用此函数:

    @override
      Widget build(BuildContext context) {
        return Column(
          children: [
            FutureBuilder<Widget>(
              // Use data instead of widget here
              future: data,
              builder: (BuildContext context, AsyncSnapshot<Widget> snapshot) {
                if (snapshot.hasData) {
                  return getLayout(snapshot.data);
                } else if (snapshot.hasError) {
                  return Text('Error: ${snapshot.error}');
                } else {
                  return const Loading();
                }
              },
            ),
            ElevatedButton(
                onPressed: () {
                  readyToInstall = updateLayout();
                  setState(() {});
                },
                child: Text('Update')
            ),
          ],
        );
      }
    

    避免使用 setState 里面 initState

    这里没有问题的原因是 setState 在回叫中被调用,这很好。但这仍然不是一个好的做法,所以你应该考虑将其移动到例如。 getLayout 函数或定义一个新的函数并调用它。

    我不知道你到底需要ValueNotifier做什么,所以我帮不了你。

    不要在内部编写ui逻辑 build 有副作用的方法

    别担心,你没有那样做。它之所以不好,是因为 建造 方法可以按顺序调用多次。这就是为什么你 print("ok") 被多次呼叫。