如何正确加载异步数据

问题描述 投票:0回答:1

我对 Flutter 很陌生,想知道如何在 Widget 中正确加载异步数据。这是我到目前为止的代码:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';

import '../../../api/api_result.dart';
import '../../../common/application.dart';
import '../../../models/survey.dart';
import '../../common/list_material_ink_well.dart';
import '../../common/loading_widget.dart';
import '../../common/main_layout.dart';
import '../../common/status_bar.dart';
import '../../common/status_bar_icon.dart';
import 'survey_poll.dart';

class SurveysPollsWidget extends StatefulWidget {
  const SurveysPollsWidget({super.key});

  @override
  State<StatefulWidget> createState() => SurveysPollsWidgetState();
}

class SurveysPollsWidgetState extends State<SurveysPollsWidget> {
  bool isEditMode = false;
  Map<String, bool> activity = <String, bool>{};

  Future<List<Survey>?> getSurveys(Application application) async {
    ApiResult<List<Survey>>? apiResult =
        await application.repositories?.survey.getSurveys();

    if (apiResult?.error != null) {
      await application.auth.logout();
    }

    if (apiResult?.value != null && application.caches != null) {
      application.caches?.survey.saveSurveys(apiResult!.value!);
    }

    return apiResult?.value;
  }

  Future<List<Survey>?> getSurveysFromCache(Application application) async {
    List<Survey>? result = await application.caches?.survey.getSurveys();

    return result;
  }

  final MaterialStateProperty<Icon?> thumbIcon =
      MaterialStateProperty.resolveWith<Icon?>(
    (Set<MaterialState> states) {
      if (states.contains(MaterialState.selected)) {
        return const Icon(Icons.check);
      }
      return const Icon(Icons.close);
    },
  );

  Widget _drawItem(Survey survey) => isEditMode
      ? ListMaterialInkWell(
          survey.name,
          navItem: Switch(
            thumbIcon: thumbIcon,
            value: activity[survey.id] ?? false,
            onChanged: (bool value) {
              setState(() {
                activity[survey.id] = value;
              });
            },
          ),
        )
      : ListMaterialInkWell(
          survey.name,
          onTap: () => Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => SurveyPollWebViewWidget(survey: survey),
            ),
          ),
        );

  void toggleEditMode() => setState(() {
        isEditMode = !isEditMode;
      });

  Widget _draw(BuildContext context, AsyncSnapshot<List<Survey>?> cachedData) =>
      Material(
        child: MainLayout(
            title: AppLocalizations.of(context)!.surveysAndPolls,
            iconLeft: isEditMode
                ? StatusBarIcon(
                    StatusBar.goBackIcon.icon, (context) => toggleEditMode())
                : StatusBar.goBackIcon,
            iconRight: !isEditMode
                ? StatusBarIcon(
                    Icons.edit_outlined, (context) => toggleEditMode())
                : null,
            showLoading: !cachedData.hasData,
            children: cachedData.hasData ? cachedData.data!.map(_drawItem).toList() : []),
      );

  @override
  Widget build(BuildContext context) => Consumer<Application>(
      builder: (_, application, __) => () {
            if (application.repositories == null) {
              return const LoadingWidget();
            }

            return FutureBuilder<List<Survey>?>(
                    future: getSurveysFromCache(application),
                    builder: (context, cachedData) => cachedData
                                .connectionState !=
                            ConnectionState.done
                        ? Container()
                        : cachedData.data != null
                            ? _draw(
                                context, cachedData)
                            : FutureBuilder<List<Survey>?>(
                                future: getSurveys(application),
                                builder: (context, snapshot) => _draw(
                                    context, snapshot),
                              ));
          }());
}

这里的问题是,每次状态改变时,通过切换 editMode 或开关,屏幕会闪烁黑色(默认背景),因为整个小部件都会重新渲染,并且在调用

getSurveysFromCache
时会在短时间内,
Container()
被渲染,那么黑。

一个肮脏的解决方案是为调查添加一个状态:

import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';

import '../../../api/api_result.dart';
import '../../../common/application.dart';
import '../../../models/survey.dart';
import '../../common/list_material_ink_well.dart';
import '../../common/loading_widget.dart';
import '../../common/main_layout.dart';
import '../../common/status_bar.dart';
import '../../common/status_bar_icon.dart';
import 'survey_poll.dart';

class SurveysPollsWidget extends StatefulWidget {
  const SurveysPollsWidget({super.key});

  @override
  State<StatefulWidget> createState() => SurveysPollsWidgetState();
}

class SurveysPollsWidgetState extends State<SurveysPollsWidget> {
  bool isEditMode = false;
  Map<String, bool> activity = <String, bool>{};
  List<Survey>? surveys;

  Future<List<Survey>?> getSurveys(Application application) async {
    ApiResult<List<Survey>>? apiResult =
        await application.repositories?.survey.getSurveys();

    if (apiResult?.error != null) {
      await application.auth.logout();
    }

    if (apiResult?.value != null && application.caches != null) {
      application.caches?.survey.saveSurveys(apiResult!.value!);
    }

    if (apiResult?.value != null) {
      for (Survey element in apiResult!.value!) {
        activity[element.id] = element.isActive;
      }
    }

    setState(() {
      surveys = apiResult?.value;
    });

    return apiResult?.value;
  }

  Future<List<Survey>?> getSurveysFromCache(Application application) async {
    List<Survey>? result = await application.caches?.survey.getSurveys();

    setState(() {
      surveys = result;
    });

    return result;
  }

  final MaterialStateProperty<Icon?> thumbIcon =
      MaterialStateProperty.resolveWith<Icon?>(
    (Set<MaterialState> states) {
      if (states.contains(MaterialState.selected)) {
        return const Icon(Icons.check);
      }
      return const Icon(Icons.close);
    },
  );

  Widget _drawItem(Survey survey) => isEditMode
      ? ListMaterialInkWell(
          survey.name,
          navItem: Switch(
            thumbIcon: thumbIcon,
            value: activity[survey.id] ?? false,
            onChanged: (bool value) {
              setState(() {
                activity[survey.id] = value;
              });
            },
          ),
        )
      : ListMaterialInkWell(
          survey.name,
          onTap: () => Navigator.push(
            context,
            MaterialPageRoute(
              builder: (context) => SurveyPollWebViewWidget(survey: survey),
            ),
          ),
        );

  void toggleEditMode() => setState(() {
        isEditMode = !isEditMode;
      });

  Widget _draw(BuildContext context, List<Survey>? surveys, bool hasData) =>
      Material(
        child: MainLayout(
            title: AppLocalizations.of(context)!.surveysAndPolls,
            iconLeft: isEditMode
                ? StatusBarIcon(
                    StatusBar.goBackIcon.icon, (context) => toggleEditMode())
                : StatusBar.goBackIcon,
            iconRight: !isEditMode
                ? StatusBarIcon(
                    Icons.edit_outlined, (context) => toggleEditMode())
                : null,
            showLoading: !hasData,
            children: hasData ? surveys!.map(_drawItem).toList() : []),
      );

  @override
  Widget build(BuildContext context) => Consumer<Application>(
      builder: (_, application, __) => () {
            if (application.repositories == null) {
              return const LoadingWidget();
            }

            return surveys != null
                ? _draw(context, surveys, true)
                : FutureBuilder<List<Survey>?>(
                    future: getSurveysFromCache(application),
                    builder: (context, cachedData) => cachedData
                                .connectionState !=
                            ConnectionState.done
                        ? Container()
                        : cachedData.data != null
                            ? _draw(
                                context, cachedData.data, cachedData.hasData)
                            : FutureBuilder<List<Survey>?>(
                                future: getSurveys(application),
                                builder: (context, snapshot) => _draw(
                                    context, snapshot.data, cachedData.hasData),
                              ));
          }());
}

一个干净的解决方案是在

getSurveysFromCache
中调用
getSurveys
initState
,但我需要由
Application
返回的
Consumer<Application>
实例对象。

一些提示如何正确操作和清洁?

flutter asynchronous loaddata
1个回答
0
投票

首先我建议使用 Riverpod 而不是 Provider。有几篇关于优点和缺点的文章:


使用 Provider,你基本上受到 BuildContext 的限制,但正如我一样,你只使用一些函数调用来加载数据,所以:

  1. 我会从提供商处提取这些内容
  2. 不要将 setState 与提供程序混合使用,而是仅使用提供程序来存储状态
  3. 相反,消费者使用选择器
  4. 拆分代码并仅监听特定的状态更改(使用 Selector
  5. 用 null 初始化数据,在未加载时渲染 Container(或加载指示器)
  6. initState
  7. 中加载数据
© www.soinside.com 2019 - 2024. All rights reserved.