Flutter Firestore辅助插件firestore_helpers的使用
Flutter Firestore辅助插件firestore_helpers的使用
DISCLAIMER
由于一些用户指出,Firestore目前在比较GeoPoints时存在不正确的问题,这可能导致从服务器返回的数据比服务器查询预期的更多。只要您始终通过提供距离访问器来使用客户端过滤,您将获得正确的结果,但如果获取大量数据条目,性能会下降。Google希望在未来对此进行更改,但没有具体时间表。请参阅 GitHub上的问题。
Warning
V3.0.0有一个重大变化,因为它修复了一个拼写错误 clientSite
> < clientSide
。
FirestoreHelpers
Firestore是一个易于使用的数据库。为了使生活更加轻松,这里有一个包。它包含用于动态构建查询和位置查询的功能。
这些地理计算相关的数学是从 Stack Overflow上的一段JS源代码 由 Stanton Parham 编写的。
创建动态查询
如果您想在运行时修改查询,请考虑使用 builtQuery()
:
///
/// Builds a query dynamically based on a list of [QueryConstraint] and orders the result based on a list of [OrderConstraint].
/// [collection] : the source collection for the new query
/// [constraints] : a list of constraints that should be applied to the [collection].
/// [orderBy] : a list of order constraints that should be applied to the [collection] after the filtering by [constraints] was done.
/// Important all limitation of FireStore apply for this method two on how you can query fields in collections and order them.
Query buildQuery({Query collection, List<QueryConstraint> constraints,
List<OrderConstraint> orderBy})
/// Used by [buildQuery] to define a list of constraints. Important besides the [field] property not more than one of the others can ne [!=null].
/// They corespond to the possisble parameters of Firestore`s [where()] method.
class QueryConstraint {
QueryConstraint(
{this.field,
this.isEqualTo,
this.isLessThan,
this.isLessThanOrEqualTo,
this.isGreaterThan,
this.isGreaterThanOrEqualTo,
this.isNull,
this.arrayContains});
}
/// Used by [buildQuery] to define how the results should be ordered. The fields
/// corespond to the poss isible parameters of Firestore`s [oderby()] method.
class OrderConstraint {
OrderConstraint(this.field, this.descending);
}
示例
假设我们有一个名为 events
的的事件集合,并且我们想要获取满足某些约束的所有事件:
// 假设我们的 Event 类看起来像这样
class Event{
String id;
String name;
DateTime startTime;
GeoPoint location;
}
Stream<List<Event>> getEvents({List<QueryConstraint> constraints}) {
try {
Query ref = buildQuery(
collection: eventCollection,
constraints: constraints, orderBy: [
new OrderConstraint("startTime", false),
]);
return ref.snapshots().map((snapShot) => snapShot.documents.map(eventDoc) {
var event = _eventSerializer.fromMap(eventDoc.data);
event.id = eventDoc.documentID;
return event;
}).toList());
} on Exception catch (ex) {
print(ex);
}
return null;
}
/// 并在其他地方调用
getEvents(constraints: [new QueryConstraint(field: "creatorId", isEqualTo: _currentUser.id)]);
为了使其更舒适和 更强大,有 getDataFromQuery()
方法:
typedef DocumentMapper<T> = T Function(DocumentSnapshot document);
typedef ItemFilter<T> = bool Function(T);
typedef ItemComparer<T> = int Function(T item1, T item2);
///
/// Convenience Method to access the data of a Query as a stream while applying
/// a mapping function on each document with optional client side filtering and sorting
/// [qery] : the data source
/// [mapper] : mapping function that gets applied to every document in the query.
/// Typically used to deserialize the Map returned from FireStore
/// [clientSideFilters] : optional list of filter functions that execute a `.where()`
/// on the result on the client side
/// [orderComparer] : optional comparisson function. If provided your resulting data
/// will be sorted based on it on the client side
Stream<List<T>> getDataFromQuery<T>({
Query query,
DocumentMapper<T> mapper,
List<ItemFilter> clientSidefilters,
ItemComparer<T> orderComparer,
});
在这个例子中,我们的事件具有以下功能:
我们需要按名称对 未来事件排序。 在这种情况下,我们在服务器端不做排序,而是在客户端侧后执行排序。
Stream<List<Event>> getEvents({List<QueryConstraint> constraints}) {
try {
Query query = buildQuery(collection: eventCollection, constraints: constraints);
return getDataFromQuery(
query: query,
mapper: (eventDoc) {
var event = _eventSerializer.fromMap(eventDoc.data);
event.id = eventDoc.documentID;
return event;
},
clientSidefilters: (event) => event.startTime > DateTime.now() // 只有未来的事件
orderComparer: (event1, event2) => event.name.compareTo(event2.name)
);
} on Exception catch (ex) {
print(ex);
}
return null;
}
地理位置查询
不幸的是,FireStore 不支持真正的地理查询,但我们可以通过比较 Geopoints
来查询小于和 大于。 这允许定义搜索区域的南西和 北东角。
大多数应用程序需要定义一个搜索区域为中心点 和 半径。 我们有 calculateBoundingBoxCoordinates
方法。
/// Defines the boundingbox for the query based
/// on its south-west and north-east corners
class GeoBoundingBox {
final GeoPoint swCorner;
final GeoPoint neCorner;
GeoBoundingBox({this.swCorner, this.neCorner});
}
///
/// Defines the search area by a circle [center] / [radiusInKilometers]
/// Based on the limitations of FireStore we can only search in rectangles
/// which means that from this definition a final search square is calculated
/// that contains the circle
class Area {
final GeoPoint center;
final double radiusInKilometers;
Area(this.center, this.radiusInKilometers):
assert(geoPointValid(center)), assert(radiusInKilometers >= 0);
factory Area.inMeters(GeoPoint gp, int radiusInMeters) {
return new Area(gp, radiusInMeters / 1000.0);
}
factory Area.inMiles(GeoPoint gp, int radiusMiles) {
return new Area(gp, radiusMiles * 1.60934);
}
/// returns the distance in km of [point] to center
double distanceToCenter(GeoPoint point) {
return distanceInKilometers(center, point);
}
}
///
///Calculates the SW and NE corners of a bounding box around a center point for a given radius;
/// [area] with the center given as .latitude and .longitude
/// and the radius of the box (in kilometers)
GeoBoundingBox boundingBoxCoordinates(Area area)
如果使用 buildQuery()
则可以更简单地使用 getLocationsConstraint
方法:
/// Creates the necessary constraints to query for items in a FireStore collection that are inside a specific range from a center point
/// [fieldName] : the name of the field in FireStore where the location of the items is stored
/// [area] : Area within that the returned items should be
List<QueryConstraint> getLocationsConstraint(String fieldName, Area area)
/// function type used to acces the field that contains the loaction inside
/// the generic type
typedef LocationAccessor<T> = GeoPoint Function(Templele item);
/// function typse used to access the distance field that contains the
/// distance to the target inside the generic type
typedef DistanceAccessor<T> = double Function(Tempele item);
typedef DistanceMapper<T> = Tempele Function(Tempele item, double itemsDistance);
getDataInArea()
结合了上述所有函数成为一个非常强大的函数:
更多关于Flutter Firestore辅助插件firestore_helpers的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter Firestore辅助插件firestore_helpers的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用firestore_helpers
插件来简化与Firestore交互的示例。firestore_helpers
是一个辅助库,它提供了一些方便的函数和扩展来处理常见的Firestore操作。
首先,确保你的pubspec.yaml
文件中包含了firestore_helpers
依赖:
dependencies:
flutter:
sdk: flutter
cloud_firestore: ^3.1.0 # 确保版本与你的项目兼容
firestore_helpers: ^2.0.0 # 确保版本与你的项目兼容
然后运行flutter pub get
来安装依赖。
接下来,我们来看一个使用firestore_helpers
的示例。假设我们有一个User
模型,并且我们希望从Firestore中读取和写入这些数据。
定义User模型
import 'package:cloud_firestore/cloud_firestore.dart';
class User {
String id;
String name;
int age;
User({required this.id, required this.name, required this.age});
// 从DocumentSnapshot转换
factory User.fromSnap(DocumentSnapshot snap) {
Map<String, dynamic> data = snap.data()! as Map<String, dynamic>;
return User(
id: snap.id,
name: data['name'] as String,
age: data['age'] as int,
);
}
// 转换为Map用于Firestore存储
Map<String, dynamic> toMap() {
return {
'name': name,
'age': age,
};
}
}
使用firestore_helpers进行读写操作
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firestore_helpers/firestore_helpers.dart';
import 'user_model.dart'; // 假设User类定义在这个文件中
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: FirestoreExample(),
);
}
}
class FirestoreExample extends StatefulWidget {
@override
_FirestoreExampleState createState() => _FirestoreExampleState();
}
class _FirestoreExampleState extends State<FirestoreExample> {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
CollectionReference<Map<String, dynamic>> usersCollection =
_firestore.collection('users');
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Firestore Helpers Example'),
),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ElevatedButton(
onPressed: () async {
// 写入数据
User user = User(id: '1', name: 'Alice', age: 30);
await usersCollection.doc(user.id).set(user.toMap());
},
child: Text('Add User'),
),
ElevatedButton(
onPressed: () async {
// 读取数据
QuerySnapshot<Map<String, dynamic>> snapshot = await usersCollection.get();
List<User> users = snapshot.docs.map((doc) => User.fromSnap(doc)).toList();
print('Users: $users');
},
child: Text('Get Users'),
),
ElevatedButton(
onPressed: () async {
// 使用firestore_helpers的批量写入
Batch batch = _firestore.batch();
List<User> newUsers = [
User(id: '2', name: 'Bob', age: 25).toMap(),
User(id: '3', name: 'Charlie', age: 35).toMap(),
].map((user) {
DocumentReference ref = usersCollection.doc(user['id'] as String);
batch.set(ref, user);
return ref;
}).toList();
await batch.commit();
print('Batch write completed');
},
child: Text('Batch Add Users'),
),
],
),
);
}
}
说明
- User模型:我们定义了一个
User
类,它包含了从Firestore文档转换和映射到Firestore文档的方法。 - Firestore操作:
- 添加单个用户:使用
set
方法将单个用户文档写入Firestore。 - 获取用户列表:使用
get
方法从Firestore集合中读取所有用户文档,并将它们转换为User
对象列表。 - 批量添加用户:使用
Batch
对象来执行批量写入操作,这是firestore_helpers
本身不提供的功能,但它是Firestore SDK的一部分,这里展示如何结合使用。
- 添加单个用户:使用
firestore_helpers
库本身可能不包含上述所有功能的直接实现,但它通常会提供一些方便的扩展函数或类来简化Firestore操作。上述代码展示了如何使用Flutter和Firestore SDK结合一些常见的模式来处理数据。