#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. Теперь это работает, спасибо!! Единственное, чего я не понимаю, это почему теперь, если у меня есть список событий, и я отключаю Интернет, и вместо этого я хочу снова загружать события, чтобы показать страницу «нет данных», она по-прежнему показывает список.