问题描述
我目前正在通过从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:
- 创建文件以存储字符串本地目录中的已更新 (例如,已更新:空),以及下载到本地目录后存储 JSON文件的位置 strong>
- 打开应用程序
- 在链接的第一部分中检查字符串 updated :
- Create file to store String "updated" in Local directory (Ex. "updated": null) and where to store the JSON file after download to Local directory
- Open App
- 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获取新数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!
- the data is still loaded immediately from the old JSON, at the same time, run in background:
- 数据仍立即从旧JSON加载,同时在后台运行:
- 步骤1:下载JSON文件(通过链接: