Запуск нескольких асинхронных запросов одновременно в Dart. Правильный путь

#flutter #dart #flutter-futurebuilder

Вопрос:

Я пытаюсь работать с параллелизмом в Flutter, поэтому у меня есть три запроса на получение от сервера, и мне нужно получать от них значения одновременно. Каждый запрос был проанализирован в виде модели. Теперь я пытаюсь понять, как я могу объединить все модели в один список с тремя get-запросами и запустить этот окончательный список в ListView.builder. Также моя задача довольно сложна для такого ничтожества, как я, потому что, помимо одновременного анализа всех трех списков, мне нужно их отфильтровать, потому что, как сказано в API, все запросы вложены и зависят от идентификатора. Как я могу это решить?

Это мои модели:

 ScheduleVariants{
final int mrId;
final int mvId;

ScheduleVariants({this.mrId, this.mvId});
}

FlightCard{
final int mvId;
final int stId;

FlightCard({this.mrId, this.stId});
}

Stop{
final int stId;

Stop({this.stId})
}
 

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

Теперь я пытаюсь сделать параллельный вызов, как это:

 class Dire extends StatefulWidget {
  final int mrId;
  final int mvId;
  final int stId;

  const Dire({Key key, this.mrId, this.mvId, this.stId}) : super(key: key);
  @override
  _DireState createState() => _DireState();
}

class _DireState extends State<Dire> {
  @override
  void initState() {
    fetchData();
    super.initState();
    stops.where((element) => element.stId == widget.stId).toList();
    card.where((element) => element.mvId == widget.mvId).toList();
    sheduler.where((element) => element.mrId == widget.mrId).toList();
  }

  List<ScheduleVariants> sheduler;
  List<FlightCard> card;
  List<Stop> stops;

  Future fetchData() async {
    String username = '';
    String password = '';
    String basicAuth =
        'Basic '   base64Encode(utf8.encode('$username:$password'));
    print(basicAuth);
    final result = await Future.wait([
      http.get(
          Uri.parse(
              "http://mysecurelink/getMarshVariants.php?fmt=json"),
          headers: <String, String>{'authorization': basicAuth}),
      http.get(
          Uri.parse(
              "http://mysecurelink/getFlightCard.php?fmt=jsonamp;mv_id"),
          headers: <String, String>{'authorization': basicAuth}),
      http.get(
          Uri.parse(
              "http://mysecurelink/getStops.php?fmt=json"),
          headers: <String, String>{'authorization': basicAuth}),
    ]);
    setState(() {
      sheduler = json.decode(result[0].body) as List;
      card = json.decode(result[1].body) as List;
      stops = json.decode(result[2].body) as List;
    });
  }

  @override
  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(),
        body: FutureBuilder(
            future: fetchData(),
            builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
              if (snapshot.hasData) {
                return ListView.builder(
                    itemCount: stops.length,
                    itemBuilder: (context, index) {
                      return ListTile(
                        title: Text(stops[index].stTitle),
                      );
                    });
              } else {
      

      return CircularProgressIndicator();
          }
        }));
 

}
}

В конце основной задачей является выполнение трех параллельных запросов, отфильтрованных по идентификатору, и получение данных из модели остановок. Как вы можете сделать это правильно?

Я нуб и не понимаю, как правильно это делать, и я буду очень рад, если кто-нибудь поможет мне разобраться в этой задаче.

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

1. Что вы подразумеваете под «отфильтровать их»? Что вы подразумеваете под «получением данных из модели остановок»? Что такое fetchList ? Вам нужно будет улучшить свой вопрос, добавив несколько примеров, потому что трудно понять, чего вы хотите. Я также рекомендовал бы вам также исправить форматирование.

2. Привет @wxker Я внес некоторые изменения для лучшего понимания того, что я пытаюсь сделать. И я постараюсь лучше объяснить идею в комментариях: моя главная задача-отфильтровать остановки по идентификатору. Но разработчики API объяснили мне, что для выполнения этой задачи мне сначала нужно использовать модель варианта расписания, затем через модель ScheduleVariant я должен использовать модель FlightCard, и уже из FlightCard по идентификатору я получаю все отфильтрованные остановки. Я попытался объединить эти модели вместе, но все равно получаю пустой экран, и мне посоветовали использовать три get-запроса к серверу одновременно.

3. @wxker Я ищу лучшее решение, потому что я никогда не пытался решить такую задачу, как сейчас. Я думал, что мне нужно объединить все модели и отфильтровать их с помощью какого-то алгоритма, но это также не сработало, или я просто сделал что-то не так

Ответ №1:

Я собираюсь сделать некоторые предположения, потому что информации недостаточно:

  1. Dire представляет собой комбинацию трех классов ScheduleVariants , FlightCard и Stop где ScheduleVariants.mvId == FlightCard.mvId и FlightCard.stId == Stop.stId .
  2. Все три API вернут список в качестве ответа.
  3. Все ScheduleVariants они уникальны mvId , все FlightCards они уникальны mvId и stId , и все Stops они уникальны stId .

Нет ничего плохого в том, как вы выполняете несколько асинхронных запросов. Future.wait в этом случае принимает список фьючерсов и возвращает список ответов. Проблема, с которой вы столкнулись, заключается в том, что вы просто не знаете, как объединить ответы на три запроса API.

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

Dire должен быть просто класс модели.

 class Dire {
  final ScheduleVariants scheduleVariant;
  final FlightCard flightCard;
  final Stop stop;

  Dire(this.scheduleVariant, this.flightCard, this.stop);
}
 

В вашем виджете, где вы хотите получить Dires доступ к API, вы можете использовать эту функцию в FutureBuilder :

   Future<List<Dire>> fetchData() async {
    String username = '';
    String password = '';
    String basicAuth =
        'Basic '   base64Encode(utf8.encode('$username:$password'));
    print(basicAuth);
    final result = await Future.wait([
      http.get(
          Uri.parse(
              "http://mysecurelink/getMarshVariants.php?fmt=json"),
          headers: <String, String>{'authorization': basicAuth}),
      http.get(
          Uri.parse(
              "http://mysecurelink/getFlightCard.php?fmt=jsonamp;mv_id"),
          headers: <String, String>{'authorization': basicAuth}),
      http.get(
          Uri.parse(
              "http://mysecurelink/getStops.php?fmt=json"),
          headers: <String, String>{'authorization': basicAuth}),
    ]);
    flightCardMap = HashMap.fromIterable(json.decode(result[1].body), (fc) => fc["mvId"], (fc) => FlightCard(fc));
    stopMap = HashMap.fromIterable(json.decode(result[2].body), (s) => s["stId"], (s) => Stop(s));
    return json.decode(result[0].body).map((sv) => {
        flightCard = flightCardMap[sv["mvId"]];
        return Dire(ScheduleVariants(sv), flightCard, stopMap[flightCard["stId"]]);
    }).toList();
  }
 

Отказ от ответственности: Я не проверял этот фрагмент кода на наличие синтаксических ошибок, так что некоторые из них могут быть, но общая идея есть.

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

1. большое вам спасибо. Я хочу подтвердить, что я все правильно понял из вашего кода (я только учусь и стараюсь ничего не пропустить). И поэтому, чтобы объединить все 3 запроса API, мне пришлось создать отдельную модель, в которой расположены параметры расписания, FlightCard, Stop. Правильно ли я это понял? После этого используйте Future builder, где я использую все 3 URL-адреса и делаю get-запросы, а затем использую хэш-карту для каждого запроса? Также я хотел спросить, следует ли мне использовать @JsonSerializable() для хорошей практики или это не обязательно?

2. Используйте Future.wait для получения ответов на все 3 запроса API. Преобразуйте ответы на полетные карточки и остановки в хэш-карты с соответствующим идентификатором в качестве ключа. Выполните итерацию по вариантам расписания, объедините его с соответствующей летной карточкой и прекратите использовать класс Dire. Верните список функций из функции. Функция может быть передана в FutureBuilder, где snapshot.data будет список функций. Вам решать, использовать JsonSerializable или нет.