как заставить эту ленивую прокрутку работать с провайдером

требуется около 7 дней, чтобы попытаться создать рабочий пример для lazyload listview с провайдером в трепете с реальным примером, и он все еще не работает, потому что я думаю, что чего-то не хватает

В качестве примечания: первая загрузка работает хорошо, и когда я прокручиваю ее, печатаю (прокручиваю), но ничего не происходит, она все еще находится на той же странице, если я пытаюсь вернуть переменную _todolist в _onScrollUpdated он не меняет страницу правильно, и после трех раз я вижу эту ошибку

E / flutter (7713): [ОШИБКА: flutter / lib / ui / ui_dart_state.cc (166)] Необработанное исключение: тип String не является подтипом типа List E / flutter (7713): # 0 TodoService. fetchTodos (пакет: flutter_todo_provider / services / todo_service.dart: 32: 21)

пример json https://jsonformatter.org/52c83e

todos_screen.dart

import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_todo_provider/helpers/http_exception.dart';

import 'package:provider/provider.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/services/todo_service.dart';

class TodosScreen extends StatefulWidget {
  @override
  _TodosScreenState createState() => _TodosScreenState();
}

class _TodosScreenState extends State<TodosScreen> {
  ScrollController _controller;
  List<dynamic> _todoList;
  bool _isLoading ;
  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    _controller.addListener(_onScrollUpdated);
  }
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {

    return Scaffold(
      appBar: AppBar(
        title: Text(Configuration.AppName),
      ),
      body: FutureBuilder(
          future: _fetchListItems(),
          builder: (context, snapshot){
            if(snapshot.hasData){
              return _listItems(snapshot.data);
            }
            return _buildProgressIndicator();
          }
      ),
    );
  }
  _fetchListItems() async {
    try {
      await Provider.of<TodoService>(context, listen: false).loadNextPage();
      _todoList = Provider.of<TodoService>(context, listen: false).items;
    } on HttpException catch (e) {
      EasyLoading.showError(e.message);
    }
    return  _todoList ;

  }


  Widget _listItems(data){
    _isLoading =  Provider.of<TodoService>(context, listen: false).isLoading ;
    return ListView.builder(
      controller: _controller,
      itemCount: data.length  ,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(data[index].title),
          subtitle:Text(data[index].content),
          trailing: Icon(Icons.print),
        );
      },
    );
  }

  Future<void> _onScrollUpdated() async {
    print("Scroll11");
    var maxScroll = _controller.position.maxScrollExtent;
    var currentPosition = _controller.position.pixels;
    if (currentPosition == maxScroll ) {
      try {
        await Provider.of<TodoService>(context, listen: false).loadNextPage();
        _todoList = Provider.of<TodoService>(context, listen: false).items;

// return _todoList; если использовать эту строку, я вижу ошибку

      } on HttpException catch (e) {
        EasyLoading.showError(e.message);
      }
    }
  }

  Widget _buildProgressIndicator() {
    _isLoading =  Provider.of<TodoService>(context, listen: false).isLoading ;
    return new Padding(
      padding: const EdgeInsets.all(8.0),
      child: new Center(
        child: new Opacity(
          opacity: _isLoading ? 1.0 : 00,
          child: new CircularProgressIndicator(),
        ),
      ),
    );
  }
}

todo_service.dart

 import 'dart:io';
    import 'package:dio/dio.dart';
    import 'package:flutter/foundation.dart';
    import 'package:flutter_todo_provider/.env.dart';
    import 'package:flutter_todo_provider/models/todo.dart';
    
    class TodoService with ChangeNotifier {
      bool isLoading = false;
      bool isFetching = false;
      int currentPage = 1;
      int totalRows = 10;
      List<Todo> items = [];
    
    
      loadNextPage() async {
        await fetchTodos(currentPage);
        currentPage++;
        notifyListeners();
      }
    
      Future fetchTodos(int currentPage) async {
    
        try {
          //404
          var options = Options(headers: {
            HttpHeaders.authorizationHeader: 'Basic ${Configuration.authToken}'
          });
          Map<String, dynamic> qParams = {
            'current_page': currentPage,
          };
          Response  response = await Dio().get('${Configuration.ApiUrl}/todos/my_todos',   options: options, queryParameters: qParams);
          List<dynamic> responseBode =  response.data["data"];
          responseBode.forEach(( dynamic json) {
            items.add(Todo.fromJson(json));
          });
          notifyListeners();
    
        } on DioError catch (e) {
          print("Error Message" + e.response.statusMessage);
          return items=[];
        }
    
    
      }
    

}


person user1080247    schedule 24.08.2020    source источник
comment
Получите данные из initState, удалите FutureBuilder, вместо этого используйте Provider / Consumer внутри вашего дерева виджетов.   -  person Rod    schedule 24.08.2020
comment
почему я не могу использовать FutureBuilder?   -  person user1080247    schedule 24.08.2020
comment
Потому что Future builder вызовет асинхронный вызов только один раз, и вам нужно снова обновить дерево виджетов после первого вызова. Взгляните: flutter.dev/docs/development/data- and-backend / state-mgmt / simple   -  person Rod    schedule 24.08.2020
comment
Проблема в том, что вам нужен Consumer внутри вашего дерева виджетов в ordem, чтобы получать notifyListeners и обновлять дерево.   -  person Rod    schedule 24.08.2020
comment
но, как документация, конструктор будущего имеет собственное состояние и перестраивается каждый раз при изменении данных api .flutter.dev / flutter / widgets / FutureBuilder-class.html.   -  person user1080247    schedule 24.08.2020
comment
Фьючерс завершается только один раз. Бывают случаи, когда вы меняете будущее, которого ожидает FutureBuilder, что приводит к другому обновлению. Завтра отвечу кодом. Сегодня я использую только мобильный телефон, и его сложно разместить здесь.   -  person Rod    schedule 24.08.2020
comment
спасибо, я попробую без FutureBuilder и обновлю свой вопрос новым кодом   -  person user1080247    schedule 24.08.2020


Ответы (1)


Вот код:

class TodoScreen extends StatefulWidget {
  // Your service goes here
  // (the class extending ChangeNotifier)
  @override
  _TodoScreenState createState() => _TodoScreenState();
}

class _TodoScreenState extends State<TodoScreen> {
  final TodoService todoService = TodoService();

  ScrollController _controller;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
    _controller.addListener(_onScrollUpdated);
    loadNextPage();
  }

  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Configuration.AppName'),
      ),
      body: ChangeNotifierProvider.value(
        value: todoService,
        child: Consumer<TodoService>(builder: (_, ctl, __) {
          if (todoService.isLoading) {
            return _buildProgressIndicator();
          } else {
            return _listItems(todoService.items);
          }
        }),
      ),
    );
  }

  Widget _listItems(data) {
    return ListView.builder(
      controller: _controller,
      itemCount: data.length,
      itemBuilder: (context, index) {
        return ListTile(
          title: Text(data[index].title),
          subtitle: Text(data[index].content),
          trailing: Icon(Icons.print),
        );
      },
    );
  }

  Widget _buildProgressIndicator() {
    return new Padding(
      padding: const EdgeInsets.all(8.0),
      child: new Center(
        child: CircularProgressIndicator(),
      ),
    );
  }

  Future<void> _onScrollUpdated() async {
    var maxScroll = _controller.position.maxScrollExtent;
    var currentPosition = _controller.position.pixels;
    if (currentPosition == maxScroll) {
      todoService.loadNextPage();
    }
  }
}

Обратите внимание, что я не вносил изменений в вашу службу. NotifyListeners сделает всю работу за нас.

Когда вы используете Provider, идея состоит в том, чтобы хранить все ваши данные внутри контроллера или службы (класса, расширяющего ChangeNitifier) ​​и просто использовать переменные с notifyListeners для изменения поведения вашего экрана.

Экран должен отслеживать изменения, для этого мы используем пару ChangeNotifierProvider.value с Consumer (builder: (_, ctl, __) {}). Используйте ChangeNotifierProvider на каком-то верхнем уровне дерева виджетов и используйте Consumer only там, где вам нужно обновить виджет. Вы даже можете использовать более одного Consumer, все они просто должны находиться в ChangeNotifierProvider.

person Rod    schedule 24.08.2020
comment
спасибо racr0x, но ваш пример не работает, он не загружает никаких данных при первой загрузке, он думает, что проблема с первым вызовом этой службы todoService.loadNextPage (); я думаю, вы забыли поместить его в initState - person user1080247; 24.08.2020
comment
Вам нужно сделать первый вызов внутри initState. Я обновлю свой ответ. Я не тестировал, так что, возможно, есть какие-то ошибки. Для вас важно понять идею, лежащую в основе. - person Rod; 24.08.2020
comment
хорошо, спасибо, после того, как я добавлю его, он работает, но все равно та же ошибка при прокрутке 3 раза, E / flutter (5783): [ОШИБКА: flutter / lib / ui / ui_dart_state.cc (166)] Необработанное исключение: тип 'String' не подтип типа «Список ‹dynamic›» я думаю, причина в том, что я достиг конца результатов, пожалуйста, как с этим справиться - person user1080247; 24.08.2020
comment
Используйте команду try / catch или обработайте ответ Response response = await Dio().get... - person Rod; 24.08.2020
comment
нет, дело не в этом, я уже использую try and catch, я думаю, что это касается получения нулевых данных после прокрутки до конца результатов, как я могу справиться с этим - person user1080247; 24.08.2020
comment
Обработайте ответ. Ответ может быть пустым, не вызывая исключений. Ищите содержание ответа. Проверьте, есть ли response.data! = Null и response.data.hasKey ('data'). Затем проверьте сами данные. - person Rod; 24.08.2020
comment
спасибо, не могли бы вы дать мне подсказку, как обработать достижение конца результатов json, чтобы предотвратить еще одну прокрутку? - person user1080247; 24.08.2020
comment
Установите какой-нибудь bool для отслеживания. - person Rod; 24.08.2020
comment
у меня уже есть, но проблема, как предотвратить прокрутку, отключить прокрутку после завершения результатов - person user1080247; 24.08.2020