使用云功能时,来自 Firestore 的时间戳会转换为地图
Posted
技术标签:
【中文标题】使用云功能时,来自 Firestore 的时间戳会转换为地图【英文标题】:Timestamp from firestore gets converted to a Map when using cloud function 【发布时间】:2019-10-08 06:05:03 【问题描述】:所以我在 Cloud Firestore 中有一个 Timestamp
。我正在使用云功能从 firestore 检索数据以进行颤动。但是JSON
将时间戳格式化为映射,因此我无法将其用作时间戳。如何再次将其转换为时间戳?
这就是我将时间戳添加到 Firestore 的方式。
var reference = Firestore.instance.collection('posts');
reference.add(
'postTitle': this.title,
'timestamp': DateTime.now(),
'likes': ,
'ownerId': userId,
)
要检索数据,代码如下:
factory Post.fromJSON(Map data)
return Post(
timestamp: data['timestamp'],
);
List<Post> _generateFeed(List<Map<String, dynamic>> feedData)
List<Post> listOfPosts = [];
for (var postData in feedData)
listOfPosts.add(Post.fromJSON(postData));
return listOfPosts;
但这会返回错误。
I/flutter (17271): The following assertion was thrown building FutureBuilder<DocumentSnapshot>(dirty, state:
I/flutter (17271): _FutureBuilderState<DocumentSnapshot>#1536b):
I/flutter (17271): type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'Timestamp'
这是我的云功能。getFeed.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
export const getFeedModule = function(req, res)
const uid = String(req.query.uid);
async function compileFeedPost()
const following = await getFollowing(uid, res)as any;
let listOfPosts = await getAllPosts(following, res);
listOfPosts = [].concat.apply([], listOfPosts);
res.send(listOfPosts);
compileFeedPost().then().catch();
async function getAllPosts(following, res)
let listOfPosts = [];
for (let user in following)
listOfPosts.push( await getUserPosts(following[user], res));
return listOfPosts;
function getUserPosts(userId, res)
const posts = admin.firestore().collection("posts").where("ownerId", "==", userId).orderBy("timestamp")
return posts.get()
.then(function(querySnapshot)
let listOfPosts = [];
querySnapshot.forEach(function(doc)
listOfPosts.push(doc.data());
);
return listOfPosts;
)
function getFollowing(uid, res)
const doc = admin.firestore().doc(`user/$uid`)
return doc.get().then(snapshot =>
const followings = snapshot.data().followings;
let following_list = [];
for (const following in followings)
if (followings[following] === true)
following_list.push(following);
return following_list;
).catch(error =>
res.status(500).send(error)
)
云功能index.ts
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import getFeedModule from "./getFeed"
admin.initializeApp();
export const getFeed = functions.https.onRequest((req, res) =>
getFeedModule(req, res);
)
由此调用
_getFeed() async
print("Starting getFeed");
FirebaseUser user = await FirebaseAuth.instance.currentUser();
SharedPreferences prefs = await SharedPreferences.getInstance();
String userId = user.uid;
var url =
'https://us-central1-jaluk-quiz.cloudfunctions.net/getFeed?uid=' + userId;
var httpClient = HttpClient();
List<QuizViewer>listOfPosts;
String result;
try
var request = await httpClient.getUrl(Uri.parse(url));
var response = await request.close();
if (response.statusCode == HttpStatus.ok)
String json = await response.transform(utf8.decoder).join();
prefs.setString("feed", json);
List<Map<String, dynamic>> data =
jsonDecode(json).cast<Map<String, dynamic>>();
listOfPosts = _generateFeed(data);
result = "Success in http request for feed";
else
result =
'Error getting a feed: Http status $response.statusCode | userId $userId';
catch (exception)
result = 'Failed invoking the getFeed function. Exception: $exception';
print(result);
setState(()
feedData = listOfPosts;
);
【问题讨论】:
你可能做错了什么。请编辑问题以显示所有未按您期望的方式工作的相关代码,包括填充数据库和将其读回。请验证您是否实际存储了时间戳类型的对象,而不是其他内容。 @DougStevenson 我已经用一些代码更新了我的问题。我是 Flutter 和编码的新手,所以我不知道该放哪一行代码。我的整个代码很长。 您提到了云功能。这是怎么回事?如果不涉及,请通过删除它来简化您的问题。 @DougStevenson 好的,我刚刚添加了云功能代码。 你的 Flutter 代码中涉及的函数是怎样的?它看起来与应用程序的执行方式无关,因为我们看不到它是如何被调用的,或者它究竟生成了什么。 【参考方案1】:确实,在使用云函数时,时间戳会以普通 Map 的形式返回。但如果您使用 Firebase SDK,它会返回 Timestamp
对象。
我使用以下函数来处理这两种情况:
/// https://***.com/a/57865272/1321917
DateTime dateTimeFromTimestamp(dynamic val)
Timestamp timestamp;
if (val is Timestamp)
timestamp = val;
else if (val is Map)
timestamp = Timestamp(val['_seconds'], val['_nanoseconds']);
if (timestamp != null)
return timestamp.toDate();
else
print('Unable to parse Timestamp from $val');
return null;
与json_annotation
lib 完美配合:
@JsonKey(
fromJson: dateTimeFromTimestamp,
toJson: dateTimeToTimestamp,
nullable: true)
final DateTime subscriptionExpiryDate;
【讨论】:
【参考方案2】:如果您正在处理已序列化为具有秒和纳秒组件的对象的时间戳,您可以使用这些组件创建一个新的具有new Timestamp(seconds, nanoseconds)
的Timestamp 对象。
【讨论】:
哇!那是天才。感谢大师的帮助!但是 JSON 以这种格式给我 _seconds: 1552543554634, _nanoseconds: 88000000。如何将其放入时间戳(秒,纳秒)? 我实际上不做飞镖编程。您必须弄清楚如何从 Cloud Functions 响应中获取这些值。如果您已经在访问响应的其他部分,这应该很简单。 哦,好的,没问题!你给我指路了。我会尝试把它放在时间戳中。再次感谢! @DougStevenson 无论如何要避免来自 cloudfunction 的这种序列化?每次我从 cloudfunction 返回带有时间戳的对象时,解析每个 JSON TIMESTAMP 并不方便【参考方案3】:您可以像这样使用 convert 来接收 DateTime:
class TimestampConverter implements JsonConverter<DateTime, dynamic>
const TimestampConverter();
@override
DateTime fromJson(dynamic data)
Timestamp timestamp;
if (data is Timestamp)
timestamp = data;
else if (data is Map)
timestamp = Timestamp(data['_seconds'], data['_nanoseconds']);
return timestamp?.toDate();
@override
Map<String, dynamic> toJson(DateTime dateTime)
final timestamp = Timestamp.fromDate(dateTime);
return
'_seconds': timestamp.seconds,
'_nanoseconds': timestamp.nanoseconds,
;
然后像这样标记你的模型字段:
@TimestampConverter() DateTime createdAt
【讨论】:
【参考方案4】:Andrey 的回答非常好。这是一个 JS/Typescript 改编,封装在一个类中:
import app from 'firebase/app'
import 'firebase/firestore'
import TimeAgo from 'javascript-time-ago'
// Load locale-specific relative date/time formatting rules.
import en from 'javascript-time-ago/locale/en'
// Add locale-specific relative date/time formatting rules.
TimeAgo.addLocale(en)
// Adapted from Andrey Gordeev's answer at:
// https://***.com/questions/56245156/timestamp-from-firestore-gets-converted-to-a-map-when-using-cloud-function
class MyClass
timeAgo: TimeAgo
constructor()
this.timeAgo = new TimeAgo('en-US')
getTimeText = (timeObject: any) =>
// Convert to time text once it's of type firestore.Timestamp
const getTextFromTimestamp = (timestamp: app.firestore.Timestamp) =>
return this.timeAgo.format(timestamp.toDate())
if (timeObject instanceof app.firestore.Timestamp)
// Check if Timestamp (accessed from client SDK)
return getTextFromTimestamp(timeObject)
else if (Object.prototype.toString.call(timeObject) === '[object Object]')
// Check if it's a Map
const seconds = timeObject['_seconds']
const nanoseconds = timeObject['_nanoseconds']
if (seconds && nanoseconds)
const timestamp = new app.firestore.Timestamp(seconds, nanoseconds)
return getTextFromTimestamp(timestamp)
console.log('Couldn\'t parse time', timeObject)
// Fallback
return 'some time ago'
【讨论】:
【参考方案5】:我通过将时间戳作为字符串发送解决了我的问题。
"timestamp": DateTime.now().toString()
因为现在我的时间戳现在是字符串,所以我从 JSON 中获取确切的时间戳作为字符串。
现在我所做的是使用名为timeago
的颤振插件将其转换为时间前格式,例如:“10 分钟前”
Text(timeago.format(DateTime.parse(timestamp)).toString);
【讨论】:
我不建议将日期作为字符串存储在 Firestore(或任何其他数据库中,就此而言。您应该坚持使用时间戳,或者如果这不起作用,请使用以unix 纪元时间。【参考方案6】:我偶然发现了这个问题,因为我试图弄清楚为什么我的云函数没有正确解析时间戳(并将其返回给调用者)。事实上,在登录时,我注意到我的日期字段显示为 Timestamp _seconds: N, _nanoseconds: NN
解决方案是在使用 Timestamp 类之前简单地将必要的字段转换为它。否则会返回地图:
const date = <field> as admin.firestore.Timestamp
【讨论】:
【参考方案7】:print(expiryObj); //_nanoseconds: 748000000, _seconds: 1612641862
Timestamp tempstamp = Timestamp(expiryObj['_seconds'], expiryObj['_nanoseconds']);
DateTime expiryDateTime = tempstamp.toDate();
print(expiryDateTime); //2021-02-06 15:04:22.748
(import import 'package:cloud_firestore/cloud_firestore.dart';)
【讨论】:
以上是关于使用云功能时,来自 Firestore 的时间戳会转换为地图的主要内容,如果未能解决你的问题,请参考以下文章
Firebase云功能Firestore触发onWrite在本地测试时未按预期运行