Flutter FutureProvider дважды выполняет сборку

#flutter

#flutter

Вопрос:

Я перешел от Statefulwidget использования initState для извлечения данных и Futurebuilder их загрузки Futureprovider . Но похоже Futureprovider build , что метод выполняется дважды, в то время как мой предыдущий подход выполнял его один раз. Это нормальное поведение?

 class ReportsPage extends StatelessWidget {
  const ReportsPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureProvider<List<ReportModel>>(
      create: (_) async => ReportsProvider().loadReportData(1),
      initialData: null,
      catchError: (_, __) => null,
      child: const ReportWidg()
    );
  }
}

class ReportWidg extends StatelessWidget {
  const ReportWidg();

  @override
  Widget build(BuildContext context) {
    print("Execute Build");
    final reportList = Provider.of<List<ReportModel>>(context);
    if (reportList == null) {
      return Center(child: CircularProgressIndicator());
    } else if (reportList.isEmpty) {
      return Center(child: Text("Det finns inga rapporter."));
    }

    print(reportList.length);
    return Container();
  }
}
 

Ответ №1:

Я относительно новичок в flutter, но я думаю, что это потому StatelessWidget @immutable , что это означает, что всякий раз, когда что-то меняется, ему нужно перестраиваться.

При первой сборке выполняется async вызов и ReportWidg() выполняется рендеринг.

Затем эта строка final reportList = Provider.of<List<ReportModel>>(context); получает новые извлеченные данные в результате async функции, поэтому immutable виджету необходимо перестроить себя, потому что его нельзя «изменить».

В объектно-ориентированном и функциональном программировании неизменяемый объект (неизменяемый объект) — это объект, состояние которого нельзя изменить после его создания. … Это в отличие от изменяемого объекта (изменяемый объект), который может быть изменен после его создания

или я ошибаюсь?

Ответ №2:

Я подозреваю, что ваш FutureProvider следует перенести в один экземпляр, например, поместить в глобальную переменную вне любых методов build () . Это, конечно, приведет к кэшированию результата, поэтому вы можете настроить его для перестроения, установив значение в зависимости от того, какие другие поставщики используют watch() или через FutureProvider.family .

Ответ №3:

Вы можете скопировать вставить запустить полный код ниже
Да. это нормально
, что в первый раз Execute Build reportList null и показывает CircularProgressIndicator()
Во второй раз Execute Build reportList есть данные и показать данные

Если вы установили listen: false , final reportList = Provider.of<List<ReportModel>>(context, listen: false);
Вы получаете только один Execute Build , и экран всегда будет отображаться CircularProgressIndicator()

В рабочей демонстрации имитируйте 5-секундную задержку в сети, чтобы вы могли видеть CircularProgressIndicator() , а затем показывать ListView
Вы можете ссылаться https://codetober.com/flutter-provider-examples /
фрагмент кода

 Widget build(BuildContext context) {
    print("Execute Build");
    final reportList = Provider.of<List<ReportModel>>(context);
    print("reportList ${reportList.toString()}");
    if (reportList == null) {
      print("reportList is null");
      return Center(child: CircularProgressIndicator());
    } else if (reportList.isEmpty) {
      return Center(child: Text("Empty"));
    }

    return Scaffold(
      body: ListView.builder(
          itemCount: reportList.length,
 

рабочая демонстрация

введите описание изображения здесь

полный код

 import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

class ReportModel {
  String title;

  ReportModel({this.title});
}

class ReportsProvider with ChangeNotifier {
  Future<List<ReportModel>> loadReportData(int no) async {
    await Future.delayed(Duration(seconds: 5), () {});
    return Future.value([
      ReportModel(title: "1"),
      ReportModel(title: "2"),
      ReportModel(title: "3")
    ]);
  }
}

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: ReportsPage(),
    );
  }
}

class ReportsPage extends StatelessWidget {
  const ReportsPage({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return FutureProvider<List<ReportModel>>(
        create: (_) async => ReportsProvider().loadReportData(1),
        initialData: null,
        catchError: (_, __) => null,
        child: const ReportWidg());
  }
}

class ReportWidg extends StatelessWidget {
  const ReportWidg();

  @override
  Widget build(BuildContext context) {
    print("Execute Build");
    final reportList = Provider.of<List<ReportModel>>(context);
    print("reportList ${reportList.toString()}");
    if (reportList == null) {
      print("reportList is null");
      return Center(child: CircularProgressIndicator());
    } else if (reportList.isEmpty) {
      return Center(child: Text("Empty"));
    }

    return Scaffold(
      body: ListView.builder(
          itemCount: reportList.length,
          itemBuilder: (context, index) {
            return Card(
                elevation: 6.0,
                child: Padding(
                  padding: const EdgeInsets.only(
                      top: 6.0, bottom: 6.0, left: 8.0, right: 8.0),
                  child: Text(reportList[index].title.toString()),
                ));
          }),
    );
  }
}
 

Ответ №4:

В вашем случае вы должны использовать Consumer , т.е.

 FutureProvider<List<ReportModel>(
  create: (_) => ...,
  child: Consumer<List<ReportModel>(
    builder: (_, reportList, __) {
      return reportList == null ?
        CircularProgressIndicator() :
        ReportWidg(reportList);
    }
  ),
),
 

Но в этом случае вам необходимо провести рефакторинг вашего ReportWidg .

Комментарии:

1. Во втором подходе не правда ли, что он не получит новые асинхронные данные? или его трудно определить, потому что он асинхронный…

2. После некоторого размышления я пришел к выводу, что автор второго ответа прав, поэтому подход, который listen: false не является хорошей идеей. So .. only Consumer — это обычный способ. При аренде я всегда использую это. 😉