Firebase UI for Firestore
Firebase UI for Firestore enables you to easily integrate your application UI with your Cloud Firestore database.
flutter pub add cloud_firestore
flutter pub add firebase_ui_firestore
Import the Firebase UI for Firestore package:
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
Infinite scrolling is the concept of continuously loading more data from a database as the user scrolls through your application. This is useful when you have a large datasets, as it enables the application to render faster as well as reduces network overhead for data the user might never see.
Firebase UI for Firestore provides a convenient way to implement infinite scrolling using the Firestore database with the FirestoreListView
widget.
At a minimum the widget accepts a Firestore query and an item builder. As the user scrolls down (or across) your list, more data will be automatically fetched from the database (whilst respecting query conditions such as ordering).
To get started, create a query and provide an item builder. For this example, we'll display a list of users from the users
collection:
final usersQuery = FirebaseFirestore.instance.collection('users').orderBy('name');
FirestoreListView<Map<String, dynamic>>(
query: usersQuery,
itemBuilder: (context, snapshot) {
Map<String, dynamic> user = snapshot.data();
return Text('User name is ${user['name']}');
},
);
The FirestoreListView
widget is built on-top of Flutter's own ListView
widget, and accepts the same parameters which we can optionally provide. For example, to change the scroll-direction to horizontal:
FirestoreListView<Map<String, dynamic>>(
scrollDirection: Axis.horizontal,
// ...
);
By default, the widget will fetch 10 items from the collection at a time. This can be changed by providing a pageSize
parameter:
FirestoreListView<Map<String, dynamic>>(
pageSize: 20,
// ...
);
In general, it is good practice to keep this value as small as possible to reduce network overhead. If the height (or width) of an individual item is large, it is recommended to lower the page size.
The cloud_firestore
plugin allows us to type the responses we receive from the database using the withConverter
API. For more information, see the documentation.
The FirestoreListView
works with this out of the box. Simply provide a converted query to the widget, for example:
class User {
User({required this.name, required this.age});
User.fromJson(Map<String, Object?> json)
: this(
name: json['name']! as String,
age: json['age']! as int,
);
final String name;
final int age;
Map<String, Object?> toJson() {
return {
'name': name,
'age': age,
};
}
}
final usersQuery = FirebaseFirestore.instance.collection('users')
.orderBy('name')
.withConverter<User>(
fromFirestore: (snapshot, _) => User.fromJson(snapshot.data()!),
toFirestore: (user, _) => user.toJson(),
);
FirestoreListView<User>(
query: usersQuery,
itemBuilder: (context, snapshot) {
// Data is now typed!
User user = snapshot.data();
return Text(user.name);
},
);
By default, the widget will display a loading indicator while data is being fetched from the database, and ignore any errors which might be thrown (such as permission denied). You can override this behavior by providing a loadingBuilder
and errorBuilder
parameters to the widget:
FirestoreListView<Map<String, dynamic>>(
loadingBuilder: (context) => MyCustomLoadingIndicator(),
errorBuilder: (context, error, stackTrace) => MyCustomError(error, stackTrace),
// ...
);
In many cases, the FirestoreListView
widget is enough to render simple lists of collection data. However, you may have specific requirements which require more control over the widget's behavior (such as using a GridView
).
The FirestoreQueryBuilder
provides the building blocks for advanced configuration at the expense of requiring more boilerplate code. The widget does not provide any underlying list implementation, instead you are expected to provide this yourself.
Much like the FirestoreListView
widget, provide a query and builder:
final usersQuery = FirebaseFirestore.instance.collection('users').orderBy('name');
FirestoreQueryBuilder<Map<String, dynamic>>(
query: usersQuery,
builder: (context, snapshot, _) {
// ... TODO!
},
);
The main difference to note here is that the builder
property returns a QueryBuilderSnapshot
, rather than an individual document. The builder returns the current state of the entire query, such as whether data is loading, an error has occurred and the documents.
This requires us to implement our own list based implementation. Firstly, let's handle the loading and error states:
FirestoreQueryBuilder<Map<String, dynamic>>(
query: usersQuery,
builder: (context, snapshot, _) {
if (snapshot.isFetching) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text('Something went wrong! ${snapshot.error}');
}
// ...
},
);
Next, we now need to return a list-view based implementation for our application to display the data. For example, to display a grid of users, we can use the GridView
widget:
FirestoreQueryBuilder<Map<String, dynamic>>(
query: usersQuery,
builder: (context, snapshot, _) {
// ...
return GridView.builder(
itemCount: snapshot.docs.length,
itemBuilder: (context, index) {
// if we reached the end of the currently obtained items, we try to
// obtain more items
if (snapshot.hasMore && index + 1 == snapshot.docs.length) {
// Tell FirestoreQueryBuilder to try to obtain more items.
// It is safe to call this function from within the build method.
snapshot.fetchMore();
}
final user = snapshot.docs[index].data();
return Container(
padding: const EdgeInsets.all(8),
color: Colors.teal[100],
child: const Text("User name is ${user['name']}"),
);
},
);
},
);
With more power comes more responsibility:
itemBuilder
of our GridView
, we have to manually ensure that we call the fetchMore()
method on the snapshot when more data is required.FirestoreQueryBuilder
does not provide a list-view based handler, instead you must provide your own implementation.Run this command:
With Flutter:
$ flutter pub add firebase_ui_firestore
This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get
):
dependencies:
firebase_ui_firestore: ^1.5.2
Alternatively, your editor might support flutter pub get
. Check the docs for your editor to learn more.
Now in your Dart code, you can use:
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
// Copyright 2022, the Chromium project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_ui_firestore/firebase_ui_firestore.dart';
import 'package:firebase_ui_firestore_example/firebase_options.dart';
import 'package:flutter/material.dart';
late CollectionReference<User> collection;
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
final collectionRef = FirebaseFirestore.instance.collection('users');
collection = collectionRef.withConverter<User>(
fromFirestore: (snapshot, _) => User.fromJson(snapshot.data()!),
toFirestore: (user, _) => user.toJson(),
);
runApp(const FirebaseUIFirestoreExample());
}
class FirebaseUIFirestoreExample extends StatelessWidget {
const FirebaseUIFirestoreExample({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Contacts')),
body: FirestoreListView<User>(
query: collection,
padding: const EdgeInsets.all(8.0),
itemBuilder: (context, snapshot) {
final user = snapshot.data();
return Column(
children: [
UserTile(user: user),
const Divider(),
],
);
},
),
),
);
}
}
class UserTile extends StatelessWidget {
final User user;
const UserTile({
super.key,
required this.user,
});
@override
Widget build(BuildContext context) {
return Row(
children: [
CircleAvatar(
child: Text(user.firstName[0]),
),
const SizedBox(width: 8),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${user.firstName} ${user.lastName}',
style: Theme.of(context).textTheme.titleMedium,
),
Text(
user.number,
style: Theme.of(context).textTheme.bodySmall,
),
],
),
],
);
}
}
class User {
User({
required this.city,
required this.country,
required this.streetName,
required this.zipCode,
required this.prefix,
required this.firstName,
required this.lastName,
required this.email,
required this.userName,
required this.number,
});
User.fromJson(Map<String, Object?> json)
: this(
city: json['city'].toString(),
country: json['country'].toString(),
streetName: json['streetName'].toString(),
zipCode: json['zipCode'].toString(),
prefix: json['prefix'].toString(),
firstName: json['firstName'].toString(),
lastName: json['lastName'].toString(),
email: json['email'].toString(),
userName: json['userName'].toString(),
number: json['number'].toString(),
);
final String city;
final String country;
final String streetName;
final String zipCode;
final String prefix;
final String firstName;
final String lastName;
final String email;
final String userName;
final String number;
Map<String, Object?> toJson() {
return {
'city': city,
'country': country,
'streetName': streetName,
'zipCode': zipCode,
'prefix': prefix,
'firstName': firstName,
'lastName': lastName,
'email': email,
'userName': userName,
'number': number,
};
}
}
Download details:
Author: firebase.google.com
Source: https://github.com/firebase/flutterfire/tree/master/packages/firebase_ui_firestore
#flutter #android #web-development #web #dart #firebase #google #ui