json - Flutter:如果 Firebase Storage 中的 JSON 文件已更新,如何使用 Firebase Storage 获取新数据
问题描述
我目前通过从 Firebase 存储调用 JSON 文件来显示数据,但我希望不是每次都下载 JSON 文件来显示数据 => 我将检查来自 Firebase 存储的 JSON 文件是否已更改:
- 如果它改变了 => 将新的 JSON 文件下载到本地目录并显示它。
- 否则 => 在本地目录中显示旧的 JSON 文件(这个旧的 JSON 文件将在第一次打开 App 时下载)
关于 JSON 文件
这是我将 JSON 上传到 Firebase 存储后的 JSON 链接:
据我所知,这个链接由两部分组成:
第一部分:https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json
最后一部分:?alt=media&token=
+ 2e3d416-62dc-4137-93a3-59ade95ac38f
(它是字符串的值:第一部分中的“downloadTokens” )
在链接的第一部分,有关于 JSON 文件的所有信息,特别是我认为 String "updated"的值可以用作下载文件或不下载文件的条件。
前任。"updated": "2020-08-04T14:30:10.920Z",
每次我上传与旧 JSON 文件同名的新 JSON 文件时,更新的这个String的值都会改变,但链接下载不会改变。
脚步
所以我想做以下事情:
- 创建文件以将字符串“updated”存储在本地目录中(例如“updated”:null)以及下载到本地目录后存储JSON 文件的位置
- 打开应用
- 检查链接第一部分中的字符串“更新”:
案例A:如果本地目录中字符串“更新”的第一部分
!=
中字符串“更新”的值=>- 第一步:下载JSON文件(链接:
First part
++?alt=media&token=
)downloadTokens
到本地目录(如果旧的json文件已经存在,将被替换) - 第 2 步:通过 Firebase 存储中字符串“更新”的值覆盖本地目录中字符串“更新”的值
- 第三步:访问Local目录下的JSON文件显示数据
- 第一步:下载JSON文件(链接:
案例B:如果本地目录中字符串“更新”的第一部分
==
中字符串“更新”的值=>什么都不做,只需访问本地目录中的JSON文件以显示数据
我知道对于一篇文章来说这是很多问题,我是一个有代码的新手,如果我把它分成几篇文章,那么对我来说很难将它们组合起来。所以我希望有完整代码的答案,那会很棒。谢谢。这是主文件:
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 的回答对我很有用,但是,我编辑帖子以添加一个我认为会尽快加载页面的案例,所以请再次帮助我:
打开页面后 => readFile
> compareLastUpdate
> _lastUpdateDB
&_createFile
- 案例 A : 应用程序第一次打开 =>
readFile
: false >_lastUpdateDB
&_createFile
>readFile
再次 - 案例 B:不是第一次打开应用程序:
- 数据仍会立即从旧 JSON 加载,同时在后台运行
compareLastUpdate
::- 如果更新时间相同 => 什么也不做
- 如果更新时间不同 =>
_lastUpdateDB
&_createFile
- 数据仍会立即从旧 JSON 加载,同时在后台运行
P/S:这样的流程,他们第二次打开页面就会显示新的数据,对吧?但是我想知道如果StatefulWidget
在新的JSON文件被覆盖到旧的JSON文件之后使用=> => 之后手机屏幕会显示新数据吗?
解决方案
我建议使用shared_preferences将最后更新的日期保存为字符串
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
}
}
然后在小部件中,您只需将其称为相同的
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));
}));
}
}
您还可以检查Dio 包,该包具有一些通过 http 的功能,可让您将参数添加到 url
推荐阅读
- javascript - 在 angular-archwizard 步骤上更改边框颜色
- xctest - 是否有任何 fastlane 操作或插件可让您在测试之间执行脚本?
- html - 播放后关闭/隐藏视频
- multithreading - 如何在后台线程中运行数据库操作时同步 Delphi 事件?
- html - HTML5 视频嵌入 - 在代码末尾显示海报
- tsql - 从 cfquery 中删除事务
- authentication - 强制使用 Google 帐户选择器屏幕
- java - 使用 RestTemplate 消耗两个 RESTful API 时出现 HTTP 409 错误 - 已解决
- keras - Keras:层可训练集错误不起作用
- javascript - Reactjs - 如何在视口中心显示网格