如何更新导航抽屉中的有状态小部件,同时在 Android 中保持与片段相同的类?

Posted

技术标签:

【中文标题】如何更新导航抽屉中的有状态小部件,同时在 Android 中保持与片段相同的类?【英文标题】:How to update stateful widget in Navigation drawer while keeping same class like fragments in Android? 【发布时间】:2018-11-17 14:06:01 【问题描述】:

我想在从服务器、导航抽屉获取数据后返回同一个类的同时更新我的​​类的有状态小部件。我遇到的问题是,如果我导航到导航抽屉的另一个项目,类只加载一次数据并且保持不变。因为状态只创建一次。

这是我的代码:

class CategoryFilter extends StatefulWidget 
 int productIndex;
 String category_name;
 CategoryFilter(this.productIndex, this.category_name)
 
  print("CategoryFilter");
  print(productIndex);
  print(category_name);
  new _CategoryFilterState(productIndex, category_name);


 @override
 _CategoryFilterState createState() => new 
_CategoryFilterState(productIndex, category_name);


 class _CategoryFilterState extends State<CategoryFilter> 

 int productIndex;
 List<ResponseDataProducts> productList;
 List data;
 String category_name;

_CategoryFilterState(this.productIndex, this.category_name)
 
   print("CategoryFilter");
   print(productIndex);
  print(category_name);


@override
void initState()
super.initState();

    Future<String> status = getData(productIndex);

    status.then((onValue)

    if(onValue.toString() == "Success")
    

      Navigator.pop(context);

    

  );


  // this.getData();
 

 @override
 Widget build(BuildContext context) 
  return new Scaffold(

    body: new Container(
        color: Colors.white30,

        child: new ListView.builder(
           itemCount: productList == null ? 0 : productList.length,
           itemBuilder: (BuildContext context, int index) 

             return new Container(

               margin: const EdgeInsets.only( bottom: 10.0),
               constraints: new BoxConstraints.expand(
                  height: 200.0

               ),
               alignment: Alignment.bottomLeft,

               decoration: new BoxDecoration(
                   image: new DecorationImage(image:
                   new NetworkImage
                     ("http://myurl.com/"+productList[index].thumbnail),
                      fit: BoxFit.cover)
               ),
               child:new Container(

                 child: new Text(
                   productList[index].name,
                   style: new TextStyle(color: Colors.white, fontSize: 30.0),
                 ),
                color: Colors.black54,
                 alignment: new FractionalOffset(0.5, 0.0),
                 height: 35.0,
                 // margin: const EdgeInsets.symmetric(vertical: 30.0),

               ),
             );
            )
    ),
  ) ;




  void _onLoading()
  
    showDialog(context: context,
       barrierDismissible: false,
      child: progress);

    new Future.delayed(new Duration(seconds: 2), ()
    // Navigator.pop(context);
   );

  


   Future<String> getData(int productIndex) async 
   productList = new List<ResponseDataProducts>();


     _onLoading();

   http.Response response = await http.get(
       Uri.encodeFull(CommonMethods.base_url + 'product/$productIndex'),
    headers: "Accept": "application/json");

print(response.body);

  setState(() 
    var convertDataToJson = JSON.decode(response.body);
    data = convertDataToJson["responseData"];
    for(int i=0; i<data.length; i++)
  

    ResponseDataProducts responseData = new ResponseDataProducts(
        data[i]["id"],
        data[i]["name"], data[i]["description"],
        data[i]["title"], data[i]["thumbnail"]);
    productList.add(responseData);
  
  //Navigator.pop(context);

);

return "Success";

 

这是我从导航抽屉中调用这个 categoryFilter 类的方式:

_getDraserItemWidget(int pos)
 
  switch(pos)
  
  case 0:

    return new Home(bar_id);


  case 1:



    return  new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);



  case 2:



  return  new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);


  case 3:


    return  new CategoryFilter(categoryList[pos-1].id, categoryList[pos-1].name);


  case 4:

    return new OpeningTime();


  case 5:

    break;



 
 

【问题讨论】:

【参考方案1】:

我建议您使用FutureBuilder 小部件,而不是调用该方法在您的类的initState 方法中加载数据。如果您从导航抽屉返回一个新的FutureBuilder,则每次创建一个新的时都会调用您的服务,并且通常是执行异步请求的更好方法。

这是一个非常简单的例子。它不能很好地完成抽屉(或其他一些事情 - 只有这么多时间可以花在这样的事情上),但它应该说明这个概念。

请注意,它不是“更新小部件”,而是简单地创建一个新小部件。由于 Flutter 做事的方式,这应该是相对高效的,尤其是因为您不是一直都在这样做,而是仅在用户从导航菜单中选择某些内容时才这样做。

import 'dart:async';

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

class MyApp extends StatefulWidget 
  @override
  State<StatefulWidget> createState() => new MyAppState();


class MyAppState extends State<MyApp> 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
      home: new TextPage(text: "Home!"),
    );
  


Map<int, int> _nums = Map();

class TextPage extends StatelessWidget 
  final String text;

  const TextPage(Key key, @required this.text) : super(key: key);

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
      appBar: new PreferredSize(
        child: new Container(),
        preferredSize: Size.fromHeight(10.0),
      ),
      body: new Center(
        child: new Text(text),
      ),
      drawer: new Builder(
        builder: (context) => Material(
              child: new SafeArea(
                child: Column(
                  children: <Widget>[
                    new FlatButton(
                      onPressed: () 
                        Navigator.pushReplacement(
                            context, new MaterialPageRoute(builder: (context) => _getDrawerItemWidget(1)));
                      ,
                      child: Text("First item"),
                    ),
                    new FlatButton(
                      onPressed: () 
                        Navigator.pushReplacement(
                            context, new MaterialPageRoute(builder: (context) => _getDrawerItemWidget(2)));
                      ,
                      child: Text("Second item"),
                    ),
                  ],
                ),
              ),
            ),
      ),
    );
  

  _getDrawerItemWidget(int i) 
    return new FutureBuilder<String>(
      builder: (context, AsyncSnapshot<String> snapshot) 
        if (snapshot.data != null) 
          return new TextPage(text: snapshot.data);
         else 
          return new TextPage(text: "Loading.");
        
      ,
      future: () async 
        var num = _nums.putIfAbsent(i, () => 0);
        _nums[i] = num + 1;
        String toReturn = "You retrieved number $i for the $num time";
        return await Future.delayed<String>(Duration(seconds: 1), () => toReturn);
      (),
    );
  

理论上,您可以做一些不同的事情,保留 GlobalKey 引用并使用这些引用来调用子小部件上的方法(如果它与当前选择匹配以使其更新),但这在颤振中通常是一个坏主意 - 最佳实践鼓励您通过数据在小部件树中向下而不是向下调用函数。如果你必须使用 GlobalKeys,你通常可以重构来做一些更好的事情。

【讨论】:

谢谢@rmtmckenzie。但这又不能解决我在从导航类第二次调用同一类时更新小部件的问题。 当然可以。由于每次在导航抽屉中执行任何操作时都会创建一个新的未来,因此它会再次进行检索并使用结果构建一个新的小部件。新的小部件将包含更新的数据。另外,您还可以获得能够在加载时定义您想要发生的事情的优势(理论上,如果您愿意,您可以在此之前显示旧版本。我将通过一个简单的示例更新我的答案。此外,您的代码对于试图帮助你的人来说,包含实际上并不是很有帮助 - 你最好包含导航抽屉代码。

以上是关于如何更新导航抽屉中的有状态小部件,同时在 Android 中保持与片段相同的类?的主要内容,如果未能解决你的问题,请参考以下文章

调用 setState 时,我的有状态小部件不会更新其状态

导航抽屉未在颤动中从 APPBar 小部件打开

需要从其他有状态小部件更新布尔标志

我的有状态小部件不会更新 ui,即使我正在调用 setState 并将正确的值传递给小部件类

后按后更新导航抽屉的选定状态

如何在更新父有状态小部件时更新子有状态小部件