Флаттер использовать поток или будущее? - PullRequest
1 голос
/ 29 октября 2019

Я использую базу данных Firestore для хранения списка объектов. Чтобы получить их, я использую Stream, предоставляемый пакетом Firestore, например:

 class FirestoreApi implements Api {

  FirestoreApi._();
  static final instance = FirestoreApi._();

  @override
  Stream<List<Job>> getJobList() {
    final path = "users/myUserId/jobs";
    final reference = Firestore.instance.collection(path);
    final snapshots = reference.snapshots();

    return snapshots.map((snapshot) => snapshot.documents.map(
      (snapshot) => Job(
        id: snapshot.data['uid'],
      name: snapshot.data['name']
     ),
    ).toList());
  }
}

Он реализует класс abstract:

abstract class Api {
  Stream<List<Job>> getJobList();
}

В своем классе репозитория я вызываюэто так:

class Repository {
  final FirestoreApi _firestoreApi = FirestoreApi.instance;

  Stream<List<job>> getJobList() => _firestoreApi.getJobList();
}

Затем в моем BloC я вызываю репозиторий:

class JobBloc {

  final _repository = new Repository();

  Stream<List<Job>> getJobList() {
    try {
      return _repository.getJobList();
    } catch (e) {
      rethrow;
    } finally {}
  }
}

И, наконец, вот как я использую его в своем Widget:

Widget _buildBody(BuildContext context) {
  final JobBloc _jobBloc = Provider.of<JobBloc>(context);

  return StreamBuilder<List<Job>>(
  stream: _jobBloc.getJobList(),
  builder: (BuildContext context, AsyncSnapshot<List<Job>> snapshot) {
    if (snapshot.hasData) {
      return RefreshIndicator(
        child: JobList(snapshot.data),
        onRefresh: () => _jobBloc.refreshJobList(),
      );
    } else {
      if(snapshot.connectionState == ConnectionState.waiting) {
        return Center(child: CircularProgressIndicator());
      } else {
        return Center(child: Text("No data"));
      }

    }
  },
);
 }

Пока здесь все отлично работает, и мой Widget обновляется в режиме реального времени, когда что-то меняется в базе данных Firestore.

Но теперь я хочу пойти еще дальше. Допустим, в будущем мне нужно изменить реализацию API и использовать API REST вместо Firestore. Я хочу, чтобы мой код был подготовлен для этого.

В этом случае все методы getJobList() должны возвращать Future<List<Job>>, поскольку API не вернет Stream (я не знаю, возможно ли это).

Iбудет иметь другой класс API, как этот, который теперь возвращает Future<List<Job>>:

class RestApi implements Api {

 RestApi._();
 static final instance = RestApi._();

 @override
 Future<List<Job>> getJobList() {
   //TODO: my rest api implementation
 }
}

Таким образом, класс API abstract будет изменен следующим образом:

abstract class Api {
  Future<List<Job>> getJobList();
}

Вот обновленный репозиторий:

class Repository {
  final RestApi _restApi = RestApi.instance;

  Future<List<job>> getJobList() => _restApi.getJobList();
}

И, наконец, в моем BloC я бы sink вернул список API в StreamController примерно так:

class JobBloc {
  final StreamController _jobController = StreamController<List<Job>>.broadcast();

  // retrieve data from stream
  Stream<List<Job>> get jobList => _jobController.stream;

  Future<List<Job>> getJobList() async {
    try {
      _jobController.sink.add(await _repository.getJobList());
    } catch (e) {
      rethrow;
    } finally {}
 }
}

Теперь вопрос: мне это действительно нравитсяFirestore возвращает Stream, это заставляет мое приложение обновляться в режиме реального времени. Но с другой стороны, мне бы хотелось, чтобы моя архитектура была последовательной.

Так как я не могу заставить мой REST API возвращать Stream, я думаю, что единственный возможный способ - это преобразование Firebase Stream в Future, но тогда я потеряю функцию обновления в реальном времени. .

Примерно так:

class FirestoreApi implements Api {

   FirestoreApi._();
   static final instance = FirestoreApi._();

    @override
    Future<List<Job>> getJobList() async {
      final path = "users/myUserId/jobs";
      final reference = Firestore.instance.collection(path);
      final snapshots = reference.snapshots();

    Stream<List<Job>> jobs = snapshots.map((snapshot) => snapshot.documents.map(
        (snapshot) => Job(
          id: snapshot.data['uid'],
          name: snapshot.data['name'],
        ),
       ).toList());

      List<Job> future = await jobs.first;
      return future;

     }
   }

До сих пор я исследовал, что использование Future вернет только один ответ, поэтому я потеряю функциональность в реальном времени.

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

Заранее спасибо, любые идеи или предложения будутбыть оцененным.

РЕДАКТИРОВАТЬ: Большое спасибо за ваши комментарии, я действительно ценю их. На самом деле я не знаю, какой из них должен быть помечен как принятый ответ, так как все они мне очень помогли, поэтому я решил дать всем вам положительный голос. Если кто-то не согласен с этим или это неправильное поведение в Stackoverflow, пожалуйста, дайте мне знать

Ответы [ 5 ]

1 голос
/ 30 октября 2019

Прежде всего, на мой взгляд, база Firebase не предназначена для поддержки зрелого проекта. В итоге вы получите API-интерфейс REST для резервного копирования вашего приложения. Это правда, что вы можете также использовать оба, но для разных целей. Так что я думаю, что вы должны думать о Firebase как о инструменте для MVP / доказательства концепции. Я знаю, что Firebase - это круто, хорошо работает и т. Д., Но затраты на конечный продукт неосуществимы.

Теперь никто не говорит, что у вас не может быть реализации клиента REST, которая будет возвращать Stream. Проверьте это Stream.fromFuture(theFuture). Вы можете думать о REST API как о потоке, который генерирует только одно событие (эквивалент Rx: Single)

Я бы также посоветовал быть осторожным с функцией обновления в реальном времени, предоставляемой Firebase, если вы переходите к полной версии. REST api, вы не сможете выполнить обновление в реальном времени, потому что REST не работает так. Firebase использует Sockets для связи (если я правильно помню).

1 голос
/ 30 октября 2019

Вы также можете включить оба метода в api / repository и либо получить Future, либо прослушать Stream в блоке, в зависимости от того, что вы хотите сделать. Я не думаю, что вам нужно беспокоиться о нарушении целостности REST, также имея метод, который возвращает поток. Нет лучшего способа использовать функциональность Firestore в реальном времени, чем использовать поток, как вы описали.

Но чтобы просто вернуть будущее, вам не нужно проходить через поток, вы можетепросто ждите getDocuments () CollectionReference, что-то вроде этого:

class FirestoreApi implements Api {

FirestoreApi._();
static final instance = FirestoreApi._();

CollectionReference jobsReference = Firestore.instance.collection("users/myUserId/jobs");

@override
Future<List<Job>> getJobList() async {
  QuerySnapshot query = await jobsReference.getDocuments();

  List<Job> jobs = query.documents.map((document) => Job(
      id: document.data['uid'],
      name: document.data['name'],
   )).toList();

  return jobs;

 }
}
1 голос
/ 29 октября 2019

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

1 голос
/ 30 октября 2019

Хороший вопрос.

Firestore vs REST API приведет к появлению различных API (Stream против Future).

Создание общего кода здесь не будет работать. Как вы сказали:

  • API на основе потоков будут в реальном времени
  • API на основе будущего не будут

Даже UX будет другим.

  • В версии Stream вам не нужен индикатор обновления.
  • В версии Future вы можете перезагрузить данные с помощью функции pull-to-refresh.

В этом случае я бы не рекомендовал проверять ваш код на будущее.

Если Firestore работает для вас хорошо, используйте Streams во всех ваших API.

Только если / когда вы решитеперейдите к REST API, после чего вы сможете конвертировать все свои API (и UX) в Futures.

Отказ от предварительных возможностей в реальном времени, кажется, не стоит.

1 голос
/ 29 октября 2019

Я думаю, все зависит от вашего приложения. Если обновление в реальном времени является важной функцией, которая сильно влияет на работу пользователя, придерживайтесь потоков данных Firebase. Если обновления в реальном времени не являются необходимостью, вы можете получить данные один раз, используя Futures. Альтернативой Firebase для обновления данных в реальном времени могут быть подписки GraphQL. Я бы порекомендовал вам проверить Hasura для быстрой реализации API GraphQL.

...