问题
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:
- If it changed => download the new JSON file to Local directory and display it.
- Otherwise => display the old JSON file in Local directory (This old JSON file will be downloaded when first time App open)
About JSON File
This is JSON link after I upload JSON to Firebase Storage:
https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json?alt=media&token=92e3d416-62dc-4137-93a3-59ade95ac38f
As far as I know, this link is made up of 2 parts:
First part: https://firebasestorage.googleapis.com/v0/b/tft-test-48c87.appspot.com/o/loadData.json
Last part: ?alt=media&token=
+ 2e3d416-62dc-4137-93a3-59ade95ac38f
(it is value of String: "downloadTokens" in First part)
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. "updated": "2020-08-04T14:30:10.920Z",
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.
Steps
So I want to do the following:
- 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:
Case A: if value of String "updated" in First Part
!=
value of String "updated" in Local directory =>- 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
- Step 1: download JSON file (by link:
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));
}));
}
}
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:
After open Page => readFile
> 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
- the data is still loaded immediately from the old JSON, at the same time, run in background:
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?
回答1:
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';
/// 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 List<Data> data = await compute(dataFromJson, response.body);
final file = await _createFile();
Map<String, dynamic> args = {
'file': file,
'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));
}));
}
}
Also yu could check Dio package which have some functions over http that let you add parameters to the url
来源:https://stackoverflow.com/questions/63251355/flutter-how-to-use-firebase-storage-to-get-new-data-if-json-file-in-firebase-st