Flutter:Future 不断重建,因为它在流中被调用。如果可能的话,如何让它再次调用?
Flutter: Future keeps rebuilding because it is being called inside a stream. How to only make it call again, if possible?
所以我想显示歌曲列表,但是显示歌曲的 Uint8List 图稿的 future 是从 future 调用的。代码有效,但专辑封面看起来好像有问题,因为它不断被调用。我不知道如何解决这个问题,我尝试了很多解决方案。请帮忙。
这是我的代码:
StreamBuilder<List<SongInfo>>(
stream: widget.songs,
builder: (context, snapshot) {
if (snapshot.hasError)
return Utility.createDefaultInfoWidget(Text("${snapshot.error}"));
if (!snapshot.hasData)
return Utility.createDefaultInfoWidget(
CircularProgressIndicator());
return (snapshot.data.isEmpty)
? NoDataWidget(
title: "There is no Songs",
)
: Column(
children: [
Container(
padding:
EdgeInsets.symmetric(vertical: 10, horizontal: 15),
alignment: Alignment.centerRight,
child: Text("${snapshot.data.length} Songs"),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, songIndex) {
SongInfo song = snapshot.data[songIndex];
return ListItemWidget(
title: Text("${song.title}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: <Widget>[
Text("Artist: ${song.artist}"),
Text(
"Duration: ${Utility.parseToMinutesSeconds(int.parse(song.duration))}",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500),
),
],
),
trailing: (widget.addToPlaylistAction == true)
? IconButton(
icon: Icon(Icons.playlist_add),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(_dialogTitle),
content: FutureBuilder<
List<PlaylistInfo>>(
future: model.getPlayList(),
builder:
(context, snapshot) {
if (snapshot.hasError) {
print("has error");
return Utility
.createDefaultInfoWidget(
Text(
"${snapshot.error}"));
}
if (snapshot.hasData) {
if (snapshot
.data.isEmpty) {
print("is Empty");
return NoDataWidget(
title:
"There is no playlists",
);
}
return PlaylistDialogContent(
options: snapshot.data
.map((playlist) =>
playlist.name)
.toList(),
onSelected: (index) {
snapshot.data[index]
.addSong(
song: song);
Navigator.pop(
context);
},
);
}
print("has no data");
return Utility
.createDefaultInfoWidget(
CircularProgressIndicator());
}),
);
});
},
tooltip: "Add to playlist",
)
: Container(
width: .0,
height: .0,
),
leading: song.albumArtwork == null
? FutureBuilder<Uint8List>(
future: model.audioQuery.getArtwork(
type: ResourceType.SONG,
id: song.id,
size: Size(100, 100)),
builder: (context, snapshot) {
SchedulerBinding.instance
.addPostFrameCallback(
(_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
if (snapshot.data.isEmpty)
return CircleAvatar(
backgroundImage: AssetImage(
"assets/images/title.png"),
);
if (isDataFetched) {
return CircleAvatar(
backgroundColor: Colors.transparent,
backgroundImage: MemoryImage(
snapshot.data,
),
);
} else {
return CircleAvatar(
child: CircularProgressIndicator(),
);
}
})
: CircleAvatar(
backgroundImage: FileImage(
IO.File(song?.albumArtwork)),
),
);
},
),
),
],
);
},
我不希望使用 post-frame 回调在未来的构建器或流构建器中设置状态。原因是你基本上要求 flutter 在下一帧中再次构建小部件,同时构建当前的小部件,递归地将整个事情设置为一个循环。如果您需要那些 isServiceError 和 isDataFetched 标志,也许您可以创建一个新的有状态小部件并在 initState 中手动执行加载任务。
您当前代码中的问题似乎与以下方面有关:
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
在未来的构建器中调用。每次设置状态时,都会在重建小部件时再次调用相同的代码,从而形成一个循环,在这个循环中,整个事情会一次又一次地不必要地构建。
您可以通过在分配 post 帧回调之前检查标志来避免它,如下所示:
if(!isDataFetched)
{
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isDataFetched = true;
}));
}
因此在下一帧中,isDataFetched 将为真,因此不再有 post 帧回调。
然而,这个解决方案并不是一个真正合适的解决方案,因为正如我上面提到的,在未来的构建器中使用 post-frame 回调设置状态不是一个好主意。如果你不需要未来构建器之外的那些标志,你应该简单地避免它们并依赖构建器本身内部的 snapshot.hasData 和 snapshot.hasError。
所以我想显示歌曲列表,但是显示歌曲的 Uint8List 图稿的 future 是从 future 调用的。代码有效,但专辑封面看起来好像有问题,因为它不断被调用。我不知道如何解决这个问题,我尝试了很多解决方案。请帮忙。
这是我的代码:
StreamBuilder<List<SongInfo>>(
stream: widget.songs,
builder: (context, snapshot) {
if (snapshot.hasError)
return Utility.createDefaultInfoWidget(Text("${snapshot.error}"));
if (!snapshot.hasData)
return Utility.createDefaultInfoWidget(
CircularProgressIndicator());
return (snapshot.data.isEmpty)
? NoDataWidget(
title: "There is no Songs",
)
: Column(
children: [
Container(
padding:
EdgeInsets.symmetric(vertical: 10, horizontal: 15),
alignment: Alignment.centerRight,
child: Text("${snapshot.data.length} Songs"),
),
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, songIndex) {
SongInfo song = snapshot.data[songIndex];
return ListItemWidget(
title: Text("${song.title}"),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
MainAxisAlignment.spaceAround,
children: <Widget>[
Text("Artist: ${song.artist}"),
Text(
"Duration: ${Utility.parseToMinutesSeconds(int.parse(song.duration))}",
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500),
),
],
),
trailing: (widget.addToPlaylistAction == true)
? IconButton(
icon: Icon(Icons.playlist_add),
onPressed: () {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text(_dialogTitle),
content: FutureBuilder<
List<PlaylistInfo>>(
future: model.getPlayList(),
builder:
(context, snapshot) {
if (snapshot.hasError) {
print("has error");
return Utility
.createDefaultInfoWidget(
Text(
"${snapshot.error}"));
}
if (snapshot.hasData) {
if (snapshot
.data.isEmpty) {
print("is Empty");
return NoDataWidget(
title:
"There is no playlists",
);
}
return PlaylistDialogContent(
options: snapshot.data
.map((playlist) =>
playlist.name)
.toList(),
onSelected: (index) {
snapshot.data[index]
.addSong(
song: song);
Navigator.pop(
context);
},
);
}
print("has no data");
return Utility
.createDefaultInfoWidget(
CircularProgressIndicator());
}),
);
});
},
tooltip: "Add to playlist",
)
: Container(
width: .0,
height: .0,
),
leading: song.albumArtwork == null
? FutureBuilder<Uint8List>(
future: model.audioQuery.getArtwork(
type: ResourceType.SONG,
id: song.id,
size: Size(100, 100)),
builder: (context, snapshot) {
SchedulerBinding.instance
.addPostFrameCallback(
(_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
if (snapshot.data.isEmpty)
return CircleAvatar(
backgroundImage: AssetImage(
"assets/images/title.png"),
);
if (isDataFetched) {
return CircleAvatar(
backgroundColor: Colors.transparent,
backgroundImage: MemoryImage(
snapshot.data,
),
);
} else {
return CircleAvatar(
child: CircularProgressIndicator(),
);
}
})
: CircleAvatar(
backgroundImage: FileImage(
IO.File(song?.albumArtwork)),
),
);
},
),
),
],
);
},
我不希望使用 post-frame 回调在未来的构建器或流构建器中设置状态。原因是你基本上要求 flutter 在下一帧中再次构建小部件,同时构建当前的小部件,递归地将整个事情设置为一个循环。如果您需要那些 isServiceError 和 isDataFetched 标志,也许您可以创建一个新的有状态小部件并在 initState 中手动执行加载任务。
您当前代码中的问题似乎与以下方面有关:
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isServiceError = false;
isDataFetched = true;
}));
在未来的构建器中调用。每次设置状态时,都会在重建小部件时再次调用相同的代码,从而形成一个循环,在这个循环中,整个事情会一次又一次地不必要地构建。
您可以通过在分配 post 帧回调之前检查标志来避免它,如下所示:
if(!isDataFetched)
{
SchedulerBinding.instance
.addPostFrameCallback((_) => setState(() {
isDataFetched = true;
}));
}
因此在下一帧中,isDataFetched 将为真,因此不再有 post 帧回调。
然而,这个解决方案并不是一个真正合适的解决方案,因为正如我上面提到的,在未来的构建器中使用 post-frame 回调设置状态不是一个好主意。如果你不需要未来构建器之外的那些标志,你应该简单地避免它们并依赖构建器本身内部的 snapshot.hasData 和 snapshot.hasError。