在构建方法中声明变量是一种不好的做法吗

Is it a bad practice to declare variables in build method

我正在开发音乐播放器应用程序。我必须检索每首歌曲的缩略图、名称、作者等数据。所以我在构建方法

的 return 上方使用了以下代码
...@override
Widget build(BuildContext context) {
//For song index and details
final themeNotifier = Provider.of<ThemeNotifier>(context, listen: true);
audioFunctions = themeNotifier.getAudioFunctions();
themeNotifier.getSongIndex().then((value) {
  setState(() {
    index = value;
  });
});
thumbnailPath =
    audioFunctions.songs[index].albumArtwork ?? 'assets/thumbnail.jpg';
title = audioFunctions.optimiseSongTitles(index) ?? title;
author = audioFunctions.songs[index].artist ?? author;

//For the bg-gradient and device height
double deviceHeight = MediaQuery.of(context).size.height;
final light = Theme.of(context).primaryColorLight;
final dark = Theme.of(context).primaryColorDark;

return Scaffold(...

这是一种不好的做法吗?如果是这样,我怎样才能正确地做到这一点?

对于任何类型的小部件,您很可能需要一些准备工作(例如设置变量的初始值或向 FocusNode 添加侦听器)。

也就是说,对于 StatelessWidget 我还没有找到任何方法来做到这一点,然后在 build 函数的开头这样做。

对于 StatefulWidget,您可以通过覆盖 initState 方法来完成所有这些操作。您通常会在此处设置侦听器或设置 TextEditingController.

的初始值

对于任何需要在渲染之前等待一些 Future 的小部件,我会推荐 FutureBuilder 因为这很容易让你处理所有不同的 snapshot 条件和/或 ConnectionState.


对于你的情况,我没有发现

之类的问题
//For the bg-gradient and device height
double deviceHeight = MediaQuery.of(context).size.height;
final light = Theme.of(context).primaryColorLight;
final dark = Theme.of(context).primaryColorDark;

因为您可以将这些视为只是为原本很长的表达式制作的一种缩写。例如,与 Theme.of(context).primaryColorLight.

相比,重复写 light 会容易得多

然而,对于这样的事情

themeNotifier.getSongIndex().then((value) {
  setState(() {
    index = value;
  });
});

根据 getSongIndex() 的具体作用、可能导致的错误或延迟类型,您可能需要考虑其他选项,例如 FutureBuilder.

是的。 根据 Flutter Docs:

Although it’s convenient, it’s not recommended to put an API call in a build() method.

Flutter calls the build() method every time it needs to change anything in the view, and this happens surprisingly often. Leaving the fetch call in your build() method floods the API with unnecessary calls and slows down your app.

解决方法: 要么像这样使用 FutureBuilder 小部件:


class _MyAppState extends State<MyApp> {
  Future<int> songIndex;
  
  @override
  void didChangeDependencies(){
  songIndex = Provider.of<ThemeNotifier>(context, listen: false).getSongIndex();
  super.didChangeDependencies();
  }
  
  @override
  Widget build(BuildContext context) {

    //For the bg-gradient and device height
    double deviceHeight = MediaQuery.of(context).size.height;
    final light = Theme.of(context).primaryColorLight;
    final dark = Theme.of(context).primaryColorDark;
    return FutureBuilder<int>(
      future: songIndex,
      builder: (context, snapshot) {
        if (snapshot.hasData) {
          final index = snapshot.data;
          thumbnailPath = audioFunctions.songs[index].albumArtwork ??
              'assets/thumbnail.jpg';
          title = audioFunctions.optimiseSongTitles(index) ?? title;
          author = audioFunctions.songs[index].artist ?? author;
          return Scaffold(body: Container());
        } else if (snapshot.hasError) {
          return Text("${snapshot.error}");
        }

        return CircularProgressIndicator();
      },
    );
  }
}

注意:

final themeNotifier = Provider.of<ThemeNotifier>(context, listen: false);

listen 是 false 因为我认为我们不需要在每次提供程序更改其状态时重建小部件(因为我们仅将其用于执行方法)。

或者 [更好的方法] 将逻辑放在 ThemeNotifier class 中。 像这样:

在主题通知器中class:


class ThemeNotifier with ChangeNotifier {
  //whatever state and logic you have

  //state for you current song index
  int _index;

  int get index => _index;

  Future<void> getSongIndex() {
    // some logic to retreive the new index
    _index = newIndex;
    notifyListeners();
  }
}

在您提供的地方 ThemeNotifier

Widget build() => ChangeNotifierProvider(
      create: (_) => ThemeNotifier()..getSongIndex(),
      child: //whatever,
    );

在你使用(消费)的地方:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //For the bg-gradient and device height
    double deviceHeight = MediaQuery.of(context).size.height;
    final light = Theme.of(context).primaryColorLight;
    final dark = Theme.of(context).primaryColorDark;
    return Consumer<ThemeNotifier>(
      builder: (context, themeNotifier, _) {
        final index = themeNotifier.index;
        if (index == null) return CircularProgressIndicator();

        thumbnailPath =
            audioFunctions.songs[index].albumArtwork ?? 'assets/thumbnail.jpg';
        title = audioFunctions.optimiseSongTitles(index) ?? title;
        author = audioFunctions.songs[index].artist ?? author;
        return Scaffold(
          body: // continue here,
        );
      },
    );
  }
}

这是使用 Consumer 语法,如果您不想在这种情况下每次提供商调用 notifyListeners() 时重建整个 MyApp 小部件,这种语法会更好。但如果重建无关紧要(如本例),更简单的方法是:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    //For the bg-gradient and device height
    double deviceHeight = MediaQuery.of(context).size.height;
    final light = Theme.of(context).primaryColorLight;
    final dark = Theme.of(context).primaryColorDark;
    final themeNotifier = Provider.of<ThemeNotifier>(context);
    final index = themeNotifier.index;
    if (index == null) return CircularProgressIndicator();

    thumbnailPath =
        audioFunctions.songs[index].albumArtwork ?? 'assets/thumbnail.jpg';
    title = audioFunctions.optimiseSongTitles(index) ?? title;
    author = audioFunctions.songs[index].artist ?? author;
    return Scaffold(body: null // continue here,
        );
  }
}

最后的想法: 我建议你提取这个逻辑:

   thumbnailPath = audioFunctions.songs[index].albumArtwork ??
              'assets/thumbnail.jpg';
   title = audioFunctions.optimiseSongTitles(index) ?? title;
   author = audioFunctions.songs[index].artist ?? author;

进入提供商本身。