颤振:即使使用了`provider`,小部件仍在重建
Posted
技术标签:
【中文标题】颤振:即使使用了`provider`,小部件仍在重建【英文标题】:flutter: Widgets are rebuilding even though `provider` is used 【发布时间】:2020-06-14 15:48:09 【问题描述】:我正在使用provider
进行颤振状态管理。下面是我的代码
首页
class HomePage extends StatelessWidget
@override
Widget build(BuildContext context)
return DefaultTabController(
length: 2,
child: Scaffold(
appBar: AppBar(
//backgroundColor: Color.fromRGBO(0, 72, 100, 10),
backgroundColor: Color.fromRGBO(25, 72, 114, 10),
title: Text("something"),
bottom: TabBar(
indicatorColor: Colors.white70,
labelStyle: TextStyle(
fontFamily: 'Roboto-Regular',
fontSize: 16.0,
letterSpacing: 0.15),
labelColor: Colors.white,
labelPadding: EdgeInsets.all(5),
tabs: <Widget>[
Tab(
text: "Fresh Products",
),
Tab(
text: "Frozen Products",
),
],
),
),
body: Center(child: Home()),
),
);
class Home extends StatelessWidget
@override
Widget build(BuildContext context)
print("Home build methof");
return TabBarView(
children: <Widget>[
Container(
height: double.infinity,
child: FutureBuilder(
future: Provider.of<ProductSpeciesImpl>(context, listen: false)
.getAllSpecies(),
builder: (BuildContext context, AsyncSnapshot snapshot)
if (snapshot.connectionState == ConnectionState.waiting)
return Center(
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
CircularProgressIndicator(),
Container(
height: 10,
),
Text(
"Loading Data... Please Wait",
style: Theme.of(context).textTheme.body1,
)
],
),
),
);
else
if (snapshot.hasError)
return Center(
child: Text("An error Occured"),
);
else
return Consumer<ProductSpeciesImpl>(
builder: (context, data, child) => GridView.builder(
physics:
ScrollPhysics(), // to disable GridView's scrolling
shrinkWrap: true,
itemCount: Provider.of<ProductSpeciesImpl>(context)
.productList
.length,
gridDelegate:
new SliverGridDelegateWithFixedCrossAxisCount(
childAspectRatio:
(MediaQuery.of(context).size.width *
.5 /
190),
crossAxisCount: 2),
itemBuilder: (BuildContext context, int index)
return GestureDetector(
onTap: ()
//print("sdsdsdsd");
// print(snapshot.data[index].name + " " + snapshot.data[index].idProductSpecies.toString() + " "+ snapshot.data[index].photo);
Navigator.pushNamed(context, "/products",
arguments:
"name": Provider.of<ProductSpeciesImpl>(
context,
listen: false)
.productList[index]
.name,
"id": Provider.of<ProductSpeciesImpl>(
context,
listen: false)
.productList[index]
.idProductSpecies,
"photo": Provider.of<ProductSpeciesImpl>(
context,
listen: false)
.productList[index]
.photo
);
,
child: Card(
elevation: 4.0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0)),
clipBehavior: Clip.antiAlias,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Container(
child: Text(
Provider.of<ProductSpeciesImpl>(context,
listen: false)
.productList[index]
.name,
),
)
],
),
),
);
));
,
),
),
ProductSpeciesImpl
class ProductSpeciesImpl
with ChangeNotifier
implements ProductSpeciesInterface
NavLinks navLinks = NavLinks();
List<ProductSpecies> productList = [];
@override
Future<void> getAllSpecies() async
var data = await http.get(navLinks.getAllProductSpecies());
var jsonData = convert.json.decode(data.body).cast<Map<String, dynamic>>();
try
productList = jsonData
.map<ProductSpecies>((json) => new ProductSpecies.fromJson(json))
.toList();
print("Product sIZE: " + productList.length.toString());
notifyListeners();
catch (error)
throw error;
代码运行良好。问题是每次我访问另一个页面并返回此页面时,UI 都会重新加载。我用过consumer
,据我了解,consumer
只会在调用时加载相关部分。这意味着我也不必在init
中运行我的产品加载代码。所以,我不明白为什么会这样。
感谢您对解决此问题的支持。
【问题讨论】:
【参考方案1】:您正在通过调用getAllSpecies()
在您的build()
函数中发起HTTP 请求。你不应该那样做。
class Home extends StatelessWidget
@override
Widget build(BuildContext context)
//...
child: FutureBuilder(
future: Provider.of<ProductSpeciesImpl>(context, listen: false)
.getAllSpecies(),
build()
函数应该没有副作用。请将小部件转换为StatefulWidget
并在initState()
中加载。或者,让父 StatefulWidget
在其 initState()
中进行加载,并将数据传递给其构造函数中的此小部件。
【讨论】:
谢谢。我使用这种方式的原因是,在一个著名的教程中,他正在使用futurebuilder
加载构建器,但令人惊讶的是没有重新加载!请您帮我理解这一点,consumer
和 provider
在页面重新加载时无法停止重建过程?
如果在调用getAllSpecies()
时只返回Provider
中的现有数据会更好。一般来说,你应该可以处理很多 build()
调用,你的代码不应该依赖于 build()
是否被调用。
好的。所以我将在initState
期间从服务器获取数据,并使用consumer
将这些“现有”数据加载到ListView
。希望那很好?
errrr.. 我转换为有状态的,并注意到我的initstate
也会在您离开屏幕并返回时运行。实际上,我在标签上,在标签之间导航,
我猜你可以在你的提供者类中做if (productList != null) loadProductList()
,但这不是一个好方法。您的ProductSpeciesImpl
始终在加载,而从未使用过productList
。如果我是你,我会放弃这个并使用pub.dev/documentation/provider/latest/provider/… 不要试图将 HTTP 加载与小部件事件联系起来。您的选项卡可以构建多次,您应该明确说明何时执行 HTTP 请求。这可能在包含选项卡的小部件的 initState 中。很抱歉之前的误导性投票。以上是关于颤振:即使使用了`provider`,小部件仍在重建的主要内容,如果未能解决你的问题,请参考以下文章
通过 addPostFrameCallback 访问 Flutter Provider 时说小部件在小部件树之外,但颤振检查器显示其他情况
使 API 在所有小部件中都可访问 - 使用已实例化变量的 Provider?