Итак, у меня есть Timestamp в облачном магазине. Я использую облачную функцию для извлечения данных из firestore в flutter. Но 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 Я обновил свой вопрос некоторыми кодами. Я новичок в флаттере и кодировании, поэтому не знаю, какую строку кода вставить. Весь мой код очень длинный.
Вы упомянули облачные функции. Как это связано здесь? Если это не связано, пожалуйста, упростите свой вопрос, удалив его.
@DougStevenson хорошо, я только что добавил код облачной функции.
Как функция связана с вашим кодом Flutter? Это не похоже на то, как приложение что-то делает, поскольку мы не можем видеть, как оно вызывается или что именно оно генерирует.
@DougStevenson теперь добавил вызываемый код.
Сериализованный объект временной метки JSON, сгенерированный вашей функцией, всегда будет отображаться как объект с секундами и наносекундами. Вам придется воссоздать этот объект метки времени на клиенте.
Не могли бы вы показать мне, как восстановить его, потому что я не знаю, как это сделать. Я новичок в кодировании.
@DougStevenson Привет! Хорошие новости. Я решил свою проблему. Большое спасибо за уделенное время. Я очень ценю это. И я ваш большой поклонник (из видео на Youtube).

Я решил свою проблему, отправив временную метку в виде строки.
"timestamp": DateTime.now().toString()
Поскольку теперь моя временная метка находится в строке, поэтому я получаю точную временную метку из JSON в виде строки.
Теперь я использовал плагин флаттера под названием timeago, чтобы преобразовать его в формат времени назад, например: «10 минут назад».
Text(timeago.format(DateTime.parse(timestamp)).toString);
Я не рекомендую хранить даты в виде строк в Firestore (или любой другой базе данных, если на то пошло. Вы должны придерживаться метки времени или, если это не работает, использовать одно целое число, измеренное во времени эпохи Unix.
Если вы имеете дело с отметкой времени, которая была сериализована как объект с компонентами секунд и наносекунд, вы можете использовать эти компоненты для создания нового объекта Отметка времени с new Timestamp(seconds, nanoseconds).
Ух ты! это гениально. Спасибо за помощь гуру! Но JSON дает мне в этом формате {_seconds: 1552543554634, _nanoseconds: 88000000}. Как мне поместить это в метку времени (секунды, наносекунды)?
На самом деле я не занимаюсь программированием дартс. Вам нужно будет выяснить, как получить эти значения из вашего ответа Cloud Functions. Это должно быть просто, если вы уже обращаетесь к другим частям ответа.
Ок, без проблем! Ты указал мне путь. Я постараюсь поставить его в метку времени. Спасибо еще раз!
@DougStevenson в любом случае, чтобы избежать этой сериализации из облачной функции? Не удобно анализировать каждый JSON TIMESTAMP каждый раз, когда я возвращаю объект с отметкой времени из облачной функции.
Действительно, временные метки возвращаются в виде простой карты при использовании облачных функций. Но если вы используете Firebase SDK, он возвращает объект Timestamp.
Я использую следующую функцию для обработки обоих случаев:
/// https://stackoverflow.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;
Ответ Андрея довольно хорош. Вот адаптация 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://stackoverflow.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.info('Couldn\'t parse time', timeObject)
// Fallback
return 'some time ago'
}
}
Я наткнулся на этот вопрос, потому что пытался понять, почему моя облачная функция неправильно анализирует метку времени (и возвращает ее вызывающей стороне). Действительно, при регистрации я заметил, что мое поле даты отображается как Timestamp { _seconds: N, _nanoseconds: NN }
Решение состояло в том, чтобы просто привести необходимое поле к классу Timestamp перед его использованием. В противном случае вместо этого была бы возвращена карта:
const date = <field> as admin.firestore.Timestamp
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';)
Вы можете использовать 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
Вы, вероятно, делаете что-то не так. Пожалуйста, отредактируйте вопрос, чтобы показать весь соответствующий код, который не работает так, как вы ожидаете, включая как заполнение базы данных, так и ее обратное чтение. Убедитесь, что вы действительно сохранили типизированный объект с меткой времени, а не что-то еще.