本文介绍了Flutter:如果Firebase Storage中的JSON文件已更新,则如何使用Firebase Storage获取新数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在通过从Firebase Storage调用JSON文件来显示数据,但我希望这样做而不是每次都下载JSON文件来显示数据=>我将检查Firebase Store中的JSON文件是否已更改:

I am currently displaying the data by calling the JSON file from Firebase Storage, but I want that instead of download JSON file every single time to show data => I will check if the JSON file from the Firebase Store has changed:


  • 如果已更改=>将新的JSON文件下载到本地目录并显示。

  • 否则==>在本地目录中显示旧的JSON文件(此旧JSON文件将在首次打开应用程序时下载)

将JSON上传到Firebase存储后,这是JSON链接:

This is JSON link after I upload JSON to Firebase Storage:

据我所知,此链接由两部分组成:

As far as I know, this link is made up of 2 parts:

第一部分 https:// firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json

最后一部分?alt = media& token = + 2e3d416-62dc-4137-93a3-59ade95ac38f (值的字符串: 第一部分中的 downloadTokens)

Last part: ?alt=media&token= + 2e3d416-62dc-4137-93a3-59ade95ac38f (it is value of String: "downloadTokens" in First part)

在链接的第一部分中,有关于JSON文件的所有信息,尤其是我认为String updated 的值可以用作

In the First part of the link, there is all information about JSON file, and especially I think that value of String "updated" can be used as a condition for the purpose of downloading files or not.

Ex。 已更新: 2020-08-04T14:30:10.920Z,

的值每当我上传与旧JSON文件同名的新JSON文件时,更新的字符串都会改变,但链接下载不会更改。

The value of this String updated will change every time I upload a new JSON file with the same name as the old JSON file but the link download will not change.

所以我要执行以下操作:

So I want to do the following:


  1. 创建文件以存储字符串本地目录中的已更新 (例如,已更新:空),以及下载到本地目录后存储 JSON文件的位置 strong>

  2. 打开应用程序

  3. 在链接的第一部分中检查字符串 updated

  1. Create file to store String "updated" in Local directory (Ex. "updated": null) and where to store the JSON file after download to Local directory
  2. Open App
  3. Check String "updated" in link First Part:



  • 情况A :如果字符串的值 updated为在第一部分 != 字符串 updated的值中;在本地目录 =>

    • Case A: if value of String "updated" in First Part != value of String "updated" in Local directory =>


      • 步骤1:下载JSON文件(通过链接:第一部分 + ?alt = media& token = + downloadTokens )到本地目录(如果旧的json文件已经存在,它将被替换)

      • 第2步:覆盖字符串 updated的值在本地目录中按字符串 updated的值在Firebase存储中

      • 第3步:访问本地目录中的JSON文件以显示数据

      • Step 1: download JSON file (by link: First part + ?alt=media&token= + downloadTokens) to Local directory (If the old json file already exists, it will be replaced)
      • Step 2: overwrite value of String "updated" in Local directory by value of String "updated" in Firebase Storage
      • Step 3: access JSON file in Local directory to display data

      情况B :如果字符串的值已更新;在第一部分 == 字符串 updated的值在本地目录 =>中什么都不做,只需访问本地目录中的JSON文件以显示数据

      Case B: if value of String "updated" in First Part == value of String "updated" in Local directory => do nothing, just access JSON file in Local directory to display data

      我知道一个帖子有很多问题,我是代码新手,如果我将其分成几篇文章,那么对我来说很难将它们组合在一起。因此,我希望完整代码的答案会很好。谢谢。这是主文件:

      I know this is a lot of questions for one post, I'm a newbie with code and if I split it up into a few posts then it is very difficult to combine them for me. So I hope the answer with full code, that would be great. Thanks. This is the main file:

      import 'package:ask/model/load_data_model.dart';
      import 'package:flutter/material.dart';
      import 'package:http/http.dart' as http;
      
      class LoadDataPage extends StatefulWidget {
        @override
        _LoadDataPageState createState() => _LoadDataPageState();
      }
      
      class DataServices {
        static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
      
        static Future<List<Data>> getData() async {
          try {
            final response = await http.get(url);
            if (200 == response.statusCode) {
              final List<Data> data = dataFromJson(response.body);
              return data;
            } else {
              return List<Data>();
            }
          } catch (e) {
            return List<Data>();
          }
        }
      }
      
      class _LoadDataPageState extends State<LoadDataPage> {
        @override
        Widget build(BuildContext context) {
          return Scaffold(
              appBar: AppBar(title: Text('Load Data')),
              body: FutureBuilder(
                  future: DataServices.getData(),
                  builder: (BuildContext context, AsyncSnapshot snapshot) {
                    List<Widget> children;
                    List<Data> _data = snapshot.data;
                    if (snapshot.hasData) {
                      return ListView.builder(
                        itemCount: _data.length,
                        itemBuilder: (context, index) {
                          return Column(
                            children: [Text(_data[index].data)],
                          );
                        },
                      );
                    } else {
                      children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
                    }
                    return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
                  }));
        }
      }
      
      




      其他步骤


      EdwynZN的答案对我来说非常有效,但是,我编辑帖子以添加一个更多的案例,我认为这将使加载页面尽快,所以请再次帮助我:


      Another Steps

      EdwynZN's answer worked great for me, however, I edit the post to add one more case which I think will make load page ASAP, So please help me again:

      打开页面后=> readFile > compareLastUpdate > _lastUpdateDB & _createFile

      After open Page => readFile > compareLastUpdate > _lastUpdateDB & _createFile


      • 案例A :应用程序首次打开==> ; readFile :否> _lastUpdateDB & _createFile >再次 readFile

      • 案例B :不是第一次打开应用程序:

        • 数据仍立即从旧JSON加载,同时在后台运行: compareLastUpdate

          • 如果更新时间相同=>不执行任何操作

          • 如果更新时间不同=> _lastUpdateDB & _createFile

          • Case A: The first time the app opens => readFile: false > _lastUpdateDB & _createFile > readFile again
          • Case B: Not the first time the app opens:
            • the data is still loaded immediately from the old JSON, at the same time, run in background: compareLastUpdate:
              • If update times are the same => do nothing
              • If update times are diffirent => _lastUpdateDB & _createFile

              P / S:使用此流程,他们第二次打开页面时,将显示新数据,对吗?但是我想知道是否使用 StatefulWidget =>在新的JSON文件被覆盖到旧的JSON文件之后==>之后电话屏幕会显示新数据吗?

              P/S: With this flow, the second time they open the page then new data will be displayed, right? But I wonder that if using StatefulWidget => after the new JSON file is overwritten to the old JSON file => will the phone screen display new data after that?

              推荐答案

              我建议使用将最后更新的日期另存为字符串

              I would recommend using shared_preferences to save the last updated date as a String

              import 'package:shared_preferences/shared_preferences.dart';
              import 'package:path_provider/path_provider.dart';
              import 'dart:convert';
              
              
              /// Move them outside of the class as Top Level functions
              List<Data> readFile(File file) {
                try{
                  String data = file.readAsStringSync();
                  return dataFromJson(data);
                } catch(e){
                  print(e.toString());
                  return List<Data>(); // or return an empty list, up to you
                }
              }
              
              // No need of encoder now because response body is already a String
              void writeFile(Map<String, dynamic> arg) =>
                arg['file']?.writeAsStringSync(arg['data'], flush: true);
              
              class DataServices {
              
                DateTime dateApi;
              
                static const String url = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f';
                static const String urlUpdate = 'https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json';
              
                Future<List<Data>> getData() async {
                  bool update = await compareLastUpdate;
                  if(update) { // that means the update times are the same, so retrieving form json file is better than doing http request
                     final file  = await _createFile();
                     if(await file.exists()) return await compute(readFile, file);
                     else return null; //or an empty List
                     // If it doesn't exists (probably first time running the app)
                     // then retrieve an empty list, null or check how to fill the list from somewhere else
                  }
                  try {
                    final response = await http.get(url);
                    final SharedPreferences preferences = await SharedPreferences.getInstance();
                    if (200 == response.statusCode) {
                      final String utfData = utf8.decode(response.bodyBytes); //just decode it yourself instead of using response.body which uses [latin1] by default
                      final List<Data> data = await compute(dataFromJson, utfData);
                      final file  = await _createFile();
                      Map<String, dynamic> args = {
                        'file': file,
                        'data': utfData
                        //'data': response.body // pass the return body instead of the data
                      };
                      await compute(writeFile, args);
                      await preferences.setString('updateDate', dateApi.toString()); //Save the new date
                      return data;
                    } else {
                      return List<Data>();
                    }
                  } catch (e) {
                    return List<Data>();
                  }
                }
              
               File _createFile() async{
                 Directory tempDir = await getTemporaryDirectory(); // or check for a cache dir also
                 return File('${tempDir.path}/Data.json');
               }
              
              
              Future<bool> get compareLastUpdate async{
                final dateCache = await _lastUpdateDB;
                dateApi = await _lastUpdateApi;
              
                if(dateCache == null) return false;
                return dateApi?.isAtSameMomentAs(dateCache) ?? false; // or just isAfter()
                // If dateApi is null (an error conection or some throw) just return false or throw an error and
                // catch it somewhere else (and give info to the user why it couldn't update)
              }
              
              Future<DateTime> get _lastUpdateApi async{
                try {
                   final response = await http.get(urlUpdate);
                   DateTime dateTime;
                   if (200 == response.statusCode) {
                     final data = jsonDecode(response.body));
                     dateTime = DateTime.tryParse(data['updated'] ?? '');
                   }
                   return dateTime;
                 } catch (e) {
                   return null;
                 }
              }
              
                Future<DateTime> get _lastUpdateDB async{
                  final SharedPreferences preferences = await SharedPreferences.getInstance();
                  return DateTime.tryParse(preferences.getString('updateDate') ?? ''); // Or if it's null use an old date
                  // The first time the app opens there is no updateDate value, so it returns null, if that
                  // happens replace it by an old date, one you know your api will be always newer,
                  // Ex: 1999-08-06 02:07:53.973 Your Api/App didn't even exist back then
                  // Or just use an empty String so the tryParser returns null
                }
              }
              

              然后在小部件中将其称为相同

              Then in the widget you just call it the same

              class _LoadDataPageState extends State<LoadDataPage> {
                final DataServices services = DataServices();
              
                @override
                Widget build(BuildContext context) {
                  return Scaffold(
                      appBar: AppBar(title: Text('Load Data')),
                      body: FutureBuilder(
                          future: services.getData(),
                          builder: (BuildContext context, AsyncSnapshot snapshot) {
                            List<Widget> children;
                            List<Data> _data = snapshot.data;
                            if (snapshot.hasData) {
                              return ListView.builder(
                                itemCount: _data.length,
                                itemBuilder: (context, index) {
                                  return Column(
                                    children: [Text(_data[index].data)],
                                  );
                                },
                              );
                            } else {
                              children = <Widget>[SizedBox(child: CircularProgressIndicator(), width: 60, height: 60), const Padding(padding: EdgeInsets.only(top: 16), child: Text('Loading...'))];
                            }
                            return Center(child: Column(mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: children));
                          }));
                }
              }
              

              您也可以检查在http上具有一些功能,可让您向url添加参数

              Also yu could check Dio package which have some functions over http that let you add parameters to the url

              这篇关于Flutter:如果Firebase Storage中的JSON文件已更新,则如何使用Firebase Storage获取新数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-06 08:07