Flutter状态管理:如何防止小部件重建

huangapple go评论70阅读模式
英文:

Flutter State Management: How can I prevent the widget rebuilding

问题

我从一个API获取了一个列表,该列表已经定义了一些字段。我想在其中一些字段上写入数据并将其发送回去,所以我使用了一个Form,在表单下面我使用了一个Consumer和一个Listview.separated来表示这个列表。我在想要写入的字段上使用了textformfield。所有这些都能正常工作,除了一个问题。当我在textformfields之外点击时,UI会重新构建,并且字段会恢复到它们的初始值。是否可以使用Selector而不是Consumer来防止UI重建?

Form(
  key: _calendarFormKey,
  child: Consumer<CalendarViewProvider>(
    builder: (context, cal, child) {
      if (cal.length == 0) {
        return Center(
          child: Text('List is empty'),
        );
      } else {
        textControllers.clear();
        int? numOfControllers = cal.length;
        for (int i = 0; i < numOfControllers; i++) {
          textControllers.add(TextEditingController());
        }

        return ListView.separated(
          separatorBuilder: (context, index) {
            return SizedBox(height: 10);
          },
          itemCount: cal.length,
          itemBuilder: (context, index) {
            var _cale = cal[index];

            textControllers[index].text = '${_cale.description}';

            void clearController() {
              context.read<CalendarViewProvider>().removeCalendar(code: _cale.code);
              textControllers[index].clear();
            }

            addToList(String text) {
              caleList.clear();
              for (int i = 0; i < numOfControllers; i++) {
                var _cal = cal[i];
                Map<String, dynamic> caleData = {
                  'code': _cal.code,
                  'start': _cal.start,
                  'end': _cal.end,
                  'title': _cal.title,
                  'description': textControllers[i].text,
                };
                caleList.add(caleData);
              }
            }

            return Container(
              height: ScreenConfig.screenHeight * .08,
              child: Row(
                children: [
                  SizedBox(width: 3),
                  Text(
                    _cale.start?.substring(11, 16) ?? '',
                    style: TextStyle(
                      fontWeight: FontWeight.bold,
                      color: Colors.grey[600],
                    ),
                  ),
                  Stack(
                    children: [
                      Image.asset(
                        AppImages.dietDayImg,
                        height: ScreenConfig.screenHeight * .15,
                        width: ScreenConfig.screenWidth * .21,
                      ),
                      Positioned.fill(
                        child: Center(
                          child: Text(
                            _cale.title?.substring(0, 1) ?? '',
                            style: TextStyle(
                              color: Colors.white,
                              fontWeight: FontWeight.bold,
                              fontSize: 14,
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                  SizedBox(width: 1),
                  SingleChildScrollView(
                    child: Container(
                      height: ScreenConfig.screenHeight * .078,
                      width: ScreenConfig.screenWidth * .55,
                      decoration: BoxDecoration(
                        color: Color(0xFFd6e7ea),
                        borderRadius: BorderRadius.all(
                          Radius.circular(10),
                        ),
                      ),
                      child: Padding(
                        padding: const EdgeInsets.only(left: 5),
                        child: TextFormField(
                          controller: textControllers[index],
                          maxLines: 20,
                          decoration: InputDecoration(
                            border: InputBorder.none,
                          ),
                          onSaved: (newValue) {
                            addToList(textControllers[index].text);
                            textControllers[index].text = newValue!;
                          },
                          onChanged: (value) {
                            caleList[index]['description'] = value;
                            addToList(textControllers[index].text);
                            textControllers[index].text = value;
                          },
                        ),
                      ),
                    ),
                  ),
                  IconButton(
                    onPressed: () {
                      deleteDialog();
                    },
                    icon: Icon(Icons.delete),
                    iconSize: 12,
                    color: Colors.red,
                  ),
                ],
              ),
            );
          },
        );
      }
    },
  ),
)

希望这个翻译对您有所帮助。如果您有其他问题,请随时提问。

英文:

I fetch a list from an api, which list has some fields already defined. I want to write on some of the fields and send it back, so I use a Form and under the form I use a Consumer and a Listview.seperated to represent the list. On the fields I want to write I use a textformfield. All of these are working correctly, except one issue. When I tap outside the textformfields the ui rebuilds again and the fields take their initial values. Is it possible to prevent that ui rebuild using Selector instead of Consumer?

Form(
key: _calendarFormKey,
child: Consumer&lt;CalendarViewProvider&gt;(
builder: (context, cal, child) {
if (cal.length == 0) {
return Center(
child: Text(&#39;List is empty&#39;),
);
} else {
textControllers.clear();
int? numOfControllers = cal.length;
for (int i = 0; i &lt; numOfControllers; i++) {
textControllers.add(TextEditingController());
}
return ListView.separated(
separatorBuilder: (context, index) {
return SizedBox(height: 10);
},
itemCount: cal.length,
itemBuilder: (context, index) {
var _cale = cal[index];
textControllers[index].text =
&#39;${_cale.description}&#39;;
void clearController() {
context
.read&lt;CalendarViewProvider&gt;()
.removeCalendar(code: _cale.code);
textControllers[index].clear();
}
addToList(String text) {
caleList.clear();
for (int i = 0; i &lt; numOfControllers; i++) {
var _cal = cal[i];
Map&lt;String, dynamic&gt; caleData = {
&#39;code&#39;: _cal.code,
&#39;start&#39;: _cal.start,
&#39;end&#39;: _cal.end,
&#39;title&#39;: _cal.title,
&#39;description&#39;: textControllers[i].text,
};
caleList.add(caleData);
}
}
return Container(
height: ScreenConfig.screenHeight * .08,
child: Row(
children: [
SizedBox(width: 3),
Text(
_cale.start?.substring(11, 16) ?? &#39;&#39;,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey[600]),
),
Stack(
children: [
Image.asset(
AppImages.dietDayImg,
height:
ScreenConfig.screenHeight * .15,
width: ScreenConfig.screenWidth * .21,
),
Positioned.fill(
child: Center(
child: Text(
_cale.title?.substring(0, 1) ??
&#39;&#39;,
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
),
),
],
),
SizedBox(width: 1),
SingleChildScrollView(
child: Container(
height:
ScreenConfig.screenHeight * .078,
width: ScreenConfig.screenWidth * .55,
decoration: BoxDecoration(
color: Color(0xFFd6e7ea),
borderRadius: BorderRadius.all(
Radius.circular(10),
),
),
child: Padding(
padding:
const EdgeInsets.only(left: 5),
child: TextFormField(
controller: textControllers[index],
maxLines: 20,
decoration: InputDecoration(
border: InputBorder.none,
),
onSaved: (newValue) {
addToList(
textControllers[index].text);
textControllers[index].text =
newValue!;
},
onChanged: (value) {
caleList[index][&#39;description&#39;] =
value;
addToList(
textControllers[index].text);
textControllers[index].text =
value;
},
),
),
),
),
IconButton(
onPressed: () {
deleteDialog();
},
icon: Icon(Icons.delete),
iconSize: 12,
color: Colors.red,
),
],
),
);
});
}
},
),
),

答案1

得分: 0

Here is the translated content with the original order randomized:

since I can only see your &quot;Form-Code&quot;) or it can also work if you use &quot;AutomaticKeepAliveClientMixin&quot;. Here for you have to make the Widget what you want to keep alife to stateful then add like that 

class _YourWidgetState extends State<YourWidget> with AutomaticKeepAliveClientMixin {

@override
Widget build(BuildContext context) {
/// Normaly you dont need to call super in Flutter for State<T>. But since we using [AutomaticKeepAliveClientMixin],
/// we have to call it. In Flutter is super used to call the constructor of the base Class.
super.build(context);
return.....;
}
/// this is also important
@override
bool get wantKeepAlive => true;
}
It will always update. You can fill cal in initstate.
But maybe you have also to do (here I am guessing, provider. If you are filling "cal" in the build method with something like:<br>

final cal = Provider.of&lt;T&gt;(context);

Since you taged (but not mentioned) provider, I assume that you are using


Please note that the content has been translated as per your request, and the original order has been randomized.
<details>
<summary>英文:</summary>
Since you taged (but not mentioned) provider, I assume that you are using provider. If you are filling &quot;cal&quot; in the build method with something like:&lt;br&gt;

final cal = Provider.of<T>(context);


It will always update. You can fill cal in initstate. 
But maybe you have also to do (here I am guessing, since I can only see your &quot;Form-Code&quot;) or it can also work if you use &quot;AutomaticKeepAliveClientMixin&quot;. Here for you have to make the Widget what you want to keep alife to stateful then add like that 

class _YourWidgetState extends State<YourWidget> with AutomaticKeepAliveClientMixin {

@override
Widget build(BuildContext context) {
/// Normaly you dont need to call super in Flutter for State<T>. But since we using [AutomaticKeepAliveClientMixin],
/// we have to call it. In Flutter is super used to call the constructor of the base Class.
super.build(context);
return.....;
}
/// this is also important
@override
bool get wantKeepAlive => true;
}

答案2

得分: 0

所以在我的现有代码中,我删除了我创建的列表下面的textfield控制器的声明。我将api的结果放入列表中,并根据该列表声明控制器,这样这些文本字段就不会再次重建。我将我的代码放在Widget构建下面,并放在返回之上:

var list = context.watch<CalendarViewProvider>().calendarView?.results;
list?.forEach((element) {
element.description;
var controller = TextEditingController(text: element.description);
textControllers.add(controller);
});
英文:

So in my existing code I deleted the declaration of the textfield controllers under the list I created. I put the results of the api inside a list, and I declare the controllers based on that list so, these textfields don't rebuild again. I put my code under the Widget build and over the return:

var list = context.watch&lt;CalendarViewProvider().calendarView?.results;
list?.forEach((element) {
element.description;
var controller = TextEditingController(text: element.description);
textControllers.add(controller);
});

huangapple
  • 本文由 发表于 2023年5月25日 17:19:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/76330691.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定