使用云功能时,firestore的时间戳会转换为地图

时间:2019-05-21 19:24:57

标签: json flutter google-cloud-firestore timestamp google-cloud-functions

因此我在Cloud Firestore中有一个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;
    });
  }

7 个答案:

答案 0 :(得分:2)

如果要处理的时间戳已被序列化为具有秒和纳秒级组件的对象,则可以使用这些组件来创建具有new Timestamp(seconds, nanoseconds)的新Timestamp对象。

答案 1 :(得分:2)

您可以像这样使用转换来接收日期时间:

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

答案 2 :(得分:1)

安德烈的答案很好。这是包装在类中的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.log('Couldn\'t parse time', timeObject)
        // Fallback
        return 'some time ago'
    }
}

答案 3 :(得分:0)

我已经通过将时间戳作为字符串发送解决了我的问题。

"timestamp": DateTime.now().toString()

因为现在我的时间戳记现在是字符串形式,所以我可以从JSON作为字符串获得确切的时间戳记。
现在,我使用了名为timeago的flutter插件将其转换为以前的格式,例如:“ 10分钟前”

Text(timeago.format(DateTime.parse(timestamp)).toString);

答案 4 :(得分:0)

实际上,使用Cloud函数时,时间戳将作为纯Map返回。但是,如果您使用Firebase SDK,它将返回Timestamp对象。 我使用以下函数来处理这两种情况:

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;

答案 5 :(得分:0)

我偶然发现了这个问题,因为我试图弄清楚为什么我的云函数没有正确解析时间戳(并将其返回给调用者)。事实上,在登录时,我注意到我的日期字段显示为 Timestamp { _seconds: N, _nanoseconds: NN }

解决方案是在使用之前简单地将必要的字段转换为 Timestamp 类。否则会返回地图:

const date = <field> as admin.firestore.Timestamp

答案 6 :(得分:0)

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';)