Pull-to-refresh в Flutter не работают при отсутствии подключения к Интернету

#flutter #listview #storage #pull-to-refresh

#трепетание #просмотр списка #Хранение #извлекать для обновления

Вопрос:

У меня есть класс, который отображает в виде списка некоторые данные (события) JSON, которые я получаю с запросом API, и сохраняет их в хранилище устройства, чтобы они не загружались каждый раз, ЕСЛИ только пользователь не выполняет операцию извлечения для обновления, чтобы загружать новостные события. В случае, если во время операции загрузки нет подключения к Интернету, приложение отображает «Невозможно загрузить список событий: проверьте ваше подключение к Интернету!». Поэтому я предполагаю, что если пользователь впервые открывает приложение, оно должно загрузить события или показать, если при подключении к Интернету отсутствует сообщение, упомянутое выше (или что событий нет, если длина загруженного массива событий == 0). Если это происходит не в первый раз, покажите список событий, ранее загруженных и сохраненных.

Моя проблема в том, что если, например, у меня отключен Интернет и после того, как я включился, запрос на обновление не работает, вместо этого, когда у меня будет загружен список, я могу выполнить операцию запроса на обновление.

Это мой код:

 class EventDetails {
  String data;
  int id;
  String name;
  String description;
  String applicationStatus;
  String applicationStarts;
  String applicationEnd;
  String starts;
  String ends;
  int fee;

  EventDetails({
    this.data,
    this.id,
    this.name,
    this.description,
    this.applicationStatus,
    this.applicationStarts,
    this.applicationEnd,
    this.starts,
    this.ends,
    this.fee,
  });

  EventDetails.fromJson(Map<String, dynamic> json) {
    data = json['data'];
    id = json['id'];
    name = json['name'];
    description = json['description'];
    applicationStatus = json['application_status'];
    applicationStarts = json['application_starts'];
    applicationEnd = json['application_ends'];
    starts = json['starts'];
    ends = json['ends'];
    fee = json['fee'];
  }
}

class EventsListView extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _EventListState();
  }
}


class _EventListState extends State<EventsListView> {

  List<EventDetails> list;
  Storage storage = Storage();

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: _getData,
      child: FutureBuilder<List<EventDetails>>(
        future: loadEvents(),
        builder: (context, snapshot) {
          if (snapshot.hasData) {
            List<EventDetails> data = snapshot.data;
            if (data.length == 0) {
              return Text("No events found",
                  style: TextStyle(
                    fontWeight: FontWeight.w500,
                    fontSize: 20,
                  ));
            } else {
              return _eventsListView(data);
            }
          } else if (snapshot.hasError) {
            if (snapshot.error.runtimeType.toString() == "SocketException") {
              return Text(
                  "Impossible to download the events list: check your internet connection!",
                  style: TextStyle(
                    fontWeight: FontWeight.w500,
                    fontSize: 20,
                  ));
            } else {
              return Text("${snapshot.error}");
            }
          }
          return CircularProgressIndicator();
        },
      ),
    );
  }

  Future<List<EventDetails>> loadEvents() async {

    String content = await storage.readList();

     if (content != 'no file available') {
      list = getListFromData(content, list);
    }

    if ((list != null) amp;amp; (list.length != 0)) {
      print('not empty');
      return list;
    } else {
    return await downloadEvents(list, storage);
    }
  }

  Future<List<EventDetails>> downloadEvents(
      List<EventDetails> list, Storage storage) async {
 
    String url = "https://myurl";
    final response = await http.get(url);
    if (response.statusCode == 200) {
      String responseResult = response.body;
      list = getListFromData(responseResult, list);
      storage.writeList(response.body);
      return list;
    } else {
      throw Exception('Failed to load events from API');
    }
  }

  List<EventDetails> getListFromData(String response, List<EventDetails> list) {
    Map<String, dynamic> map = json.decode(response);
    List<dynamic> jsonResponse = map["data"];
    list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
    return list;
  }

  ListView _eventsListView(data) {
    return ListView.separated(
        itemCount: data.length,
        separatorBuilder: (context, index) => Divider(
          color: const Color(0xFFCCCCCC),
        ),
        itemBuilder: (BuildContext context, int index) {
          return GestureDetector(
              child: _tile(data[index].name),
              onTap: () {
                Navigator.pushNamed(
                  context,
                  SingleEvent.routeName,
                  arguments: ScreenArguments(
                    data[index].name,
                    data[index].description,
                    data[index].starts,
                    data[index].ends,
                  ),
                );
              });
        });
  }

  Future<void> _getData() async {
    setState(() {
      downloadEvents(list,storage);
    });
  }

  @override
  void initState() {
    super.initState();
    loadEvents();
  }

  ListTile _tile(String title) => ListTile(
    title: Text(title,
        style: TextStyle(
          fontWeight: FontWeight.w500,
          fontSize: 20,
        )),
  );
}
 

Я действительно новичок в Flutter, что я делаю не так?

Ответ №1:

FutureBuilder не будет обновляться после оценки будущего. Если вы хотите, чтобы запрос на обновление работал, вы можете просто сохранить данные списка как состояние виджета и отобразить другой пользовательский интерфейс на основе состояния.

В дополнение к этому, RefreshIndicator не будет работать, если дочерний элемент не прокручивается. Вместо того, чтобы возвращать обычный Text виджет, когда нет данных, возвращайте SingleChildScrollView с текстом внутри, чтобы у вас был прокручиваемый внутри вашего RefreshIndicator .

Вот пример:

 class EventsListView extends StatefulWidget {
  @override
  _EventsListViewState createState() => _EventsListViewState();
}

class _EventsListViewState extends State<EventsListView> {
  List list;
  Storage storage = Storage();
  String errorMessage;

  @override
  Widget build(BuildContext context) {
    return RefreshIndicator(
      onRefresh: downloadEvents,
      child: listWidget(),
    );
  }

  Widget listWidget() {
    if (list != null) {
      return ListView(); // here you would return the list view with contents in it
    } else {
      return SingleChildScrollView(child: Text('noData'));  // You need to return a scrollable widget for the refresh to work. 
    }
  }

  Future<void> loadEvents() async {
    String content = await storage.readList();

    if (content != 'no file available') {
      list = getListFromData(content, list);
    }

    if ((list != null) amp;amp; (list.length != 0)) {
      print('not empty');
      errorMessage = null;
      setState(() {});
    } else {
      await downloadEvents();
    }
  }

  Future<void> downloadEvents() async {
    String url = "https://myurl";
    final response = await http.get(url);
    if (response.statusCode == 200) {
      String responseResult = response.body;
      list = getListFromData(responseResult, list);
      storage.writeList(response.body);
      errorMessage = null;
      setState(() {});
    } else {
      setState(() {
        errorMessage =
            'Error occured'; // here, you would actually add more if, else statements to show better error message
      });
      throw Exception('Failed to load events from API');
    }
  }

  List<EventDetails> getListFromData(String response, List<EventDetails> list) {
    Map<String, dynamic> map = json.decode(response);
    List<dynamic> jsonResponse = map["data"];
    list = jsonResponse.map((job) => new EventDetails.fromJson(job)).toList();
    return list;
  }

  @override
  void initState() {
    super.initState();
    loadEvents();
  }
}
 

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

1. Я пытался, но я все еще не могу перезагрузить страницу в случае, если при первом запуске приложения у меня нет подключения к Интернету, и я не могу загрузить события. Он показывает «нет данных» в аспекте, но когда я включаю Интернет и хочу нажать, чтобы загрузить событие, я не могу этого сделать.

2. @TheOldBlackbeard Когда вы отключаете Интернет и снова обновляете, ничего не происходит?

3. Точно. Проблема в том, что если я прокручиваю сверху, чтобы отображался значок обновления для обновления, круглый значок вообще не отображается. Если я нахожусь в списке, значок обновления для обновления отображается без каких-либо проблем. Это похоже на то, что в списке работает функция обновления, но если я нахожусь в состоянии «Нет данных», я не могу выполнить обновление, чтобы загрузить события. Кроме того, если я нахожусь в listview и отключаю Интернет, и я хочу использовать pull для обновления, чтобы загружать новые события, должно отображаться «нет подключения / данных», но вместо этого список предыдущих событий продолжает отображаться :/

4. @TheOldBlackbeard Я отредактировал свой ответ. По сути, вам нужен виджет с возможностью прокрутки, который будет дочерним элементом вашего RefreshIndicator . Сложно центрировать Text виджет внутри SingleChildScrollView , но, возможно, вы можете добавить Text виджету немного отступов вверху.

5. Большое вам спасибо @dshukertjr это была проблема! Индикатор обновления не работает без scrollview. Теперь это работает, спасибо!! Единственное, чего я не понимаю, это почему теперь, если у меня есть список событий, и я отключаю Интернет, и вместо этого я хочу снова загружать события, чтобы показать страницу «нет данных», она по-прежнему показывает список.