如何避免每次导航到页面时都重新加载数据

时间:2019-08-02 16:17:43

标签: flutter dart dart-async

我试图避免在颤抖中重建FutureBuilder。我已经尝试了以下Q提出的解决方案。

How to parse JSON only once in Flutter

Flutter Switching to Tab Reloads Widgets and runs FutureBuilder

每次我导航至该页面时,我的应用仍会触发API。请指出我要去哪里了。

实用程序飞镖

//function which call API endpoint and returns Future in list
class EmpNetworkUtils {
    ...
    Future<List<Employees>> getEmployees(data) async {
        List<Employees> emps = [];
        final response = await http.get(host + '/emp', headers: { ... });
        final responseJson = json.decode(response.body);
        for (var empdata in responseJson) {
            Employees emp = Employees( ... );
            emps.add(emp);
        }
        return emps;
    }
}

EmpDetails.dart

class _EmpPageState extends State<EmpPage>{
    ...
    Future<List<Employees>>_getAllEmp;
    @override
    initState() {
        _getAllEmp = _getAll();
        super.initState();
    }
    Future <List<Employees>>_getAll() async {
        _sharedPreferences = await _prefs;
        String authToken = AuthSessionUtils.getToken(_sharedPreferences);
        return await EmpNetworkUtils().getEmployees(authToken);
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: new AppBar( ... ),
            body: Container(
                child: FutureBuilder(
                    future: _getAllEmp,
                    builder: (BuildContext context, AsyncSnapshot snapshot) { ... }
        )))
    }
}

更新: 我正在我的应用中使用bottomNavigationBar,从中加载了此页面。

2 个答案:

答案 0 :(得分:1)

您正在getEmployees中调用initState函数,这意味着每次将小部件插入树中时都会被调用。如果要在第一次调用函数后保存数据,则必须有一个持久化的小部件。

一个简单的实现方法是使用InheritedWidget和一个数据类:

class InheritedEmployees extends InheritedWidget {
  final EmployeeData employeeData;

  InheritedEmployees({
    Key key,
    @required Widget child,
  })  : assert(child != null),
        employeeData = EmployeeData(),
        super(key: key, child: child);

  static EmployeeData of(BuildContext context) => (context.inheritFromWidgetOfExactType(InheritedEmployees) as InheritedEmployees).employeeData;

  @override
  bool updateShouldNotify(InheritedEmployees old) => false;
}

class EmployeeData {
  List<Employees> _employees;

  Future<List<Employees>> get employees async {
    if (_employees != null) return _employees;
    _sharedPreferences = await _prefs;
    String authToken = AuthSessionUtils.getToken(_sharedPreferences);
    return _employees = await EmpNetworkUtils().getEmployees(authToken);
  }
}

现在,您只需要将InheritedEmployees放置在不会被处置的地方,例如关于您的首页,或者您想知道的MaterialApprunApp(InheritedEmployees(child: MaterialApp(..));)。这样,数据仅被提取一次,然后被缓存。如果您更适合您,也可以考虑使用AsyncMemoizer,但是我提供的示例应该可以正常工作。

现在,您将要在employees中调用此didChangeDependencies吸气剂,因为您的_EmpPageState依赖于InheritedEmployees,并且需要查找,这在initState

class _EmpPageState extends State<EmpPage>{
    Future<List<Employees>>_getAllEmp;

    @override
    void didChangeDependencies() {
      _getAllEmp = InheritedEmployees.of(context).employees;
      super.didChangeDependencies();
    }

    @override
    Widget build(BuildContext context) {
        return Scaffold(
            appBar: new AppBar( ... ),
            body: Container(
                child: FutureBuilder(
                    future: _getAllEmp,
                    builder: (BuildContext context, AsyncSnapshot snapshot) { ... }
        )))
    }
}

我提到您的State现在依赖于您的InheritedWidget,但这并不重要,因为updateShouldNotify总是返回false(不会有其他版本)。

答案 1 :(得分:0)

我有另一种解决此问题的方法,也可以应用到我的应用程序中

  • 应用GetX控制器调用API并呈现响应数据
  • 删除FutureBuilder以调用API数据

应用GetX控制器调用API数据,就像

class NavMenuController extends GetxController {

  Api api = new Api();
  var cart = List<NavMenu>().obs;

  @override
  void onInit() {
    // TODO: implement onInit
    getNavMenuData();
    super.onInit();
  }

  Future<List<NavMenu>> getNavMenuData() async {
    var nav_data = await api.getNavMenus();
    if(nav_data!=null) {
      cart.value = nav_data;
    }
    return cart;
  }

}

使用initState()上的控制器将API调用到所需的类中

class NavMenuDrawer extends StatefulWidget {
  @override
  _NavMenuDrawerState createState() => _NavMenuDrawerState();
}

class _NavMenuDrawerState extends State<NavMenuDrawer> {

  final NavMenuController navMenuController = Get.put(NavMenuController());

  @override
  void initState() {
    // TODO: implement initState
    super.initState();
    navMenuController.getNavMenuData();
  }

删除以下用于调用API的FutureBuilder代码,[如果您使用FutureBuilder / StreamBuilder则使用此功能]

return FutureBuilder<List<NavMenu>>(
      future: api.getNavMenus(),
      builder: (context, snapshot) {
      
      return ListView.builder(
              scrollDirection: Axis.vertical,
              shrinkWrap: true,
              physics: ScrollPhysics(),
              itemCount: snapshot.data?.length ?? 0,
              itemBuilder: (context, index) {
                return Column(
                  children: [
                    ListTile(
                      title: Text("${snapshot.data[index].title}"),

只需使用GetX控制器即可获取数据,例如

return ListView.builder(
      scrollDirection: Axis.vertical,
      shrinkWrap: true,
      physics: ScrollPhysics(),
      itemCount: navMenuController.cart?.length ?? 0,
      itemBuilder: (context, index) {
        return Column(
          children: [
            ListTile(
              title: Obx(() {
                return Text(
                    "${navMenuController.cart.value[index].title}");
              }),

注意:有关更多信息,您可以搜索如何应用GetX