Как сократить операции чтения в Firebase Cloud Firestore? - PullRequest
1 голос
/ 07 июня 2019

Как страстный учитель старших классов, я использую Firebase Cloud Firestore & Flutter для своего приложения только для чтения. Мои данные - около 2000 файлов image.png с некоторыми тегами в качестве категорий и подкатегорий. Категории, подкатегории и изображения имеют порядок между собой. Причиной использования Cloud Firestore является то, что я могу изменить (= добавить, удалить, обновить) подкатегории и изображения моих категорий в любое время, и мой пользователь сможет обновлять мой последний контент.

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

Это код того, что происходит при открытии приложения:

return FutureBuilder<QuerySnapshot>(
      future: _getCategories(),
      builder: (context, catSnapshot) {

            if((!catSnapshot.hasData) && !catSnapshot.hasError) return customProgressIndicator("Categories Synchronizing");

        if(_categories.isNotEmpty) _categories.clear();
        _categories = catSnapshot.data.documents.map<Category>((document) => Category.fromJson(document.data)).toList();

        return FutureBuilder(
          future: _getSubCategories(),
          builder: (context, subSnapshot) {
            if(!subSnapshot.hasData && !subSnapshot.hasError) return customProgressIndicator("Subcategories Synchronizing");

            if(_subcategories.isNotEmpty) _subcategories.clear();
            for(int i=0; i<_categories.length; i++) {
              String catName = _categories[i].name;
              List<Subcategory> subcategoriesI = subSnapshot.data.documents.where((document) => document.data["catName"]==catName).map<Subcategory>((document) => Subcategory.fromJson(document.data)).toList();
              _subcategories[catName] = subcategoriesI;
            }

            return RefreshIndicator(
              onRefresh: _onRefresh,
              child: _categories.length==0 ? whenNoData() : ListView.builder(
                itemBuilder: (context, i) => _createCategoryItem(i),
                itemCount: _categories.length,
                padding: EdgeInsets.all(8.0),
              ),         );       },      );    },   )

Это моя главная страница:

class _CategoryListPageState  extends State<CategoryListPage> {
  List<Category> _categories = [];
  Map<String, List<Subcategory>> _subcategories = {};

 Future<QuerySnapshot> _getCategories() async{

    var conn=await UserHasConnection();
    print("connection: " + conn.toString());
    Future<QuerySnapshot> snapshots;

      if (widget.isUserPro) {
        snapshots = Firestore.instance.collection("categories").where("isPro", isEqualTo: true).orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {print("Timeout Category");});
        return snapshots;
      }   else {
        snapshots = Firestore.instance.collection("categories").orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {print("Timeout Category");});
        return snapshots;
      }
  }

  _getSubCategories() async{
    Future<QuerySnapshot> snapshots;
      if(widget.isUserPro){
    snapshots=Firestore.instance.collection("subcategories").where("isPro", isEqualTo: true).orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {
          print("Timeout Subcategory");
        });
        return snapshots;
      }  else  {  snapshots=Firestore.instance.collection("subcategories").orderBy("showOrder").getDocuments();
        snapshots.timeout(Duration(seconds: globalTimeOut), onTimeout: () {
          print("Timeout Subcategory");
        });
        return snapshots;
      }
  }

Это мои коллекции документов Firestore:

categories
  isPro: boolean (there aid free and paid-proUser's)
  name: string, name of the category
  scCount: int, number of subcategories it has
  showOrder: its order among other categories

subcategories
  catName: name of the category it belongs to
  fcCount: number of image.png it has
  imageBytes: subcategories have an image
  isPro: boolean, subcategories can also be free or paid
  name: subcategories have a name
  showOrder: each subcategory has an order among other subcategories in that category
  title: title of the subcat, similar to name

flashcards
  backBytes: back image base64encoded
  backStamp
  catName: category of this image belongs to
  frontBytes: front image base64encoded
  frontStamp
  isPro: cards are also free or paid (for search function below)
  name
  showOrder: order of this card in the subcategory
  subName: name of the subcategory that this card belongs to

*** У меня есть отдельные коллекции flascards, flashcards2x, flashcards3x для разных размеров экрана устройства

Также есть функция поиска, которая выполняется из свойства frontContent коллекции flashcardContents:

flashcardContents
  frontContent
  globalOrder
  isPro
  name
  showOrder
  updateTime
  updateType: (1-adden, 2-updated, 3-deleted)

Наконец, мои функции FirebaseService:

class FlashcardFirebaseService extends IServiceBase {
  Future<List<Flashcard>> QueryRatedFlashCards(List<Flashcard> _flashcards,
      List<String> flashcardNames, String ratioPostfix, bool isUserPro) async {
    var snapshot;
    flashcardNames.forEach((flashcardName) {
      print("flashcardName: " + flashcardName);

      if (isUserPro) {
        //For pro user
        snapshot = Firestore.instance
            .collection("flashcards${ratioPostfix}")
            .where("name", isEqualTo: flashcardName)
            .snapshots();
      } else {
        snapshot = Firestore.instance
            .collection("flashcards${ratioPostfix}")
            .where("name", isEqualTo: flashcardName)
            .where("isPro", isEqualTo: false)
            .snapshots();
      }

      snapshot.listen((onData) {
        _flashcards.addAll(onData.documents
            .map<Flashcard>((document) => Flashcard.fromJson(document.data))
            .toList());
      });
    });
    return _flashcards;
  }

  static Future<List<Category>> SyncCategories(bool isPro, double lastStamp){
    Firestore.instance
        .collection("categories")
        .getDocuments().then((data){
          return data;
    });
  }

  static Future<List<Category>> SyncSubcategories(bool isPro){
    Firestore.instance
        .collection("subcategories")
        .getDocuments().then((data){
      return data;
    });      }  }


class FlashcardSearchService extends IServiceBase {
  static Future SyncLocalSearchDb() async {
    Query baseQuery;
    AppConfig appConfig;
    appConfig = await AppConfigService.GetAppConfiguration().then((appConfig){
      if (appConfig != null) {
        var lastSyncDate;
        try {
          lastSyncDate = appConfig.fcLastSync;
        } catch (ex) {}

        print("lastSyncDate ---------------------------:"+ (lastSyncDate/1000).toString());

        baseQuery = Firestore.instance
            .collection("flashcardContents")
            .where("updateTime", isGreaterThan: lastSyncDate/1000);
      } else {
        appConfig = null;

        print("appConfig null");

        //Create all card contents
        baseQuery = Firestore.instance.collection("flashcardContents");
        appConfig = new AppConfig();
      }

      baseQuery.getDocuments().then((query) {
        query.documents.forEach((doc) => _syncFlashcardContent(doc));
        if(query.documents.length>0)
          {
            appConfig.fcLastSync = new DateTime.now().millisecondsSinceEpoch.toDouble();
            print("new lastSyncDate ---------------------------:"+ (appConfig.fcLastSync /1000).toString());
            print("SYNC!!! " + appConfig.fcLastSync.toString());
         AppConfigService.UpdateApplicationConfigurationSyncTime(appConfig);

          } else {
              print("CANT SYNC!!! " + appConfig.fcLastSync.toString());
            }
        return appConfig;
      }).timeout(Duration(seconds: globalTimeOut),onTimeout: (){
        print("TimeOutSync");
        return appConfig;
      });
    });

Подводя итог, у меня есть коллекция flashcards image.png, которую мои студенты могут использовать для изучения. Мой контент меняется в некоторой степени еженедельно, и я хочу, чтобы мои пользователи догоняли меня. Проблема в том, что с моим кодом, если устройство подключено к сети, все документы читаются постоянно, даже если нет изменений, и даже если я закрываю приложение через несколько секунд, я открываю его. Это становится недоступным для меня.

Извините за длинный пост, надеюсь, по крайней мере, вам понравился код, любая идея ценна и ценится.

Я обнаружил, что основной причиной того, что в моем приложении такое большое количество операций чтения, является: «Если прослушиватель отключен более чем на 30 минут (например, если пользователь отключается), с вас будет взиматься плата за чтение, как если бы вы выпустил новый запрос ". Итак, новый вопрос: возможно ли изменить поведение этого слушателя? Или есть способ обойти это? Я чувствую, что мое приложение не очень подходит для Firestore, но пока это кажется единственной проблемой!

Подводя итог: мне нужен слушатель, который работает, например, один раз в день, потому что моя коллекция будет меняться, вероятно, один раз в день. Или мне нужен способ обойти это. Например, когда мое приложение отключается, слушатель не работает. Какие-либо варианты отключить сетевое подключение моих приложений без отключения автономного сохранения Firestore? Спасибо

1 Ответ

0 голосов
/ 07 июня 2019

Для нас в Stack Overflow не возможно диагностировать источник всех операций чтения в вашей базе данных, поскольку мы не понимаем общую работу вашего приложения (у нас нет всего вашего исходного кода, и мы не понять поведение ваших пользователей). Тем не менее, очень распространенный источник непредвиденных чтений исходит из самой консоли Firebase. Когда вы используете консоль для просмотра базы данных, она будет стоить вам чтения, чтобы заполнить консоль. Если вы оставите окно консоли открытым для коллекции, которая активно изменяется, она будет накапливать чтения со временем, пока консоль открыта.

...