Skip to main content

Firebase api

caution

Working with Firestore Database implies the use of a service account. This account will have full access to read and modify data in your database. Be very careful where you deploy your Nanc build - if third-party users can get into it - they will be able to find out and change your data. Your security is on your shoulders.

note

In the future, instructions on how to customize Nanc's built-in functionality will appear here, allowing potentially dangerous Nanc builds to be published to the public.

Installing

Install it from pub.dev:

dependencies:
nanc_api_firebase: any

Configuring

Creating Firebase project

First, you need to create a Firebase project. If you want to use Nanc with an existing project, you can skip this section and go to the key generation one. Also, the official documentation from Google will be the best instruction for actions. However, here we will show you the way to create a new Firebase project too.

Create new project

Go to Firebase Console and click on Add project button

Type your project name

Enable or disable Google Analytics

Wait while project will be created

Create Firestore Database

Build Firestore Database

Get Firebase service key

info

Any configuration parameter, be it a key, url or anything else, is used only inside the loop of your Nanc build and the service for which you are applying the configuration. We do not store or receive any of your data. At all.

Go to the project settings

Then you need to go to the service accounts settings, to create service account and generate its access key

And generate the key

Then save it somewhere at your computer, for example - at the root, of your Nanc-CMS build project.

Using

So you have a database and a JSON key to access it. Now you can create the required API instances to start using Firestore as a backend. The following code will shed some light on how you can implement this:

import 'dart:async';

import 'package:nanc/nanc.dart';
import 'package:nanc_configuration/nanc_configuration.dart';
import 'package:flutter/material.dart';
import 'package:nanc_api_firebase/nanc_api_firebase.dart';

import 'firebase_key.dart';

Future<void> main() async {
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();

/// ? Creating instances of needed APIs
final FirebaseApi firebaseApi = await FirebaseApi.create(firebaseBase64EncodedKey);

/// ? Backend-first ICollectionApi implementation
final FirebaseCollectionApi firebaseCollectionApi = FirebaseCollectionApi(api: firebaseApi);

/// ? Partially-local ICollectionApi implementation
final FirebaseLocalCollectionApi firebaseLocalCollectionApi = FirebaseLocalCollectionApi(api: firebaseApi);

final FirebaseDocumentApi firebasePageApi = FirebaseDocumentApi(api: firebaseApi, firebaseCollectionApi: firebaseCollectionApi);
final FirebaseModelApi firebaseModelApi = FirebaseModelApi();

await adminRunner(
CmsConfig(
/// ? Use them here
collectionApi: firebaseCollectionApi,
documentApi: firebasePageApi,
modelApi: firebaseModelApi,
networkConfig: NetworkConfig.simple(paginationLimitParameterDefaultValue: 50),
imageBuilderDelegate: null,
adminWrapperBuilder: null,
predefinedModels: [],
customRenderers: [],
eventsHandlers: [],
customFonts: [],
sliverChecker: null,
customIcons: null,
themeBuilder: null,
),
);
}, ErrorsCatcher.catchZoneErrors);
}

The firebaseBase64EncodedKey variable holds the Base64-encoded contents of the JSON key downloaded in the previous step. This is the format in which the FirebaseApi.create constructor accepts the key from the Firestore service account.

Also, you might have noticed that there are two ICollectionApi implementations declared in the code - FirebaseCollectionApi and FirebaseLocalCollectionApi. Let's dwell on their differences a bit more.

Backend-first collection api

FirebaseCollectionApi - is an implementation of working with collections through Firestore, which implies constant use of Firestore Database - any filtering, searching, pagination operations will be performed through a new call to the server. Also, the logic of data filtering and searching is implemented in full compliance with the capabilities of Firestore itself. And these capabilities are very limited. For example - you will not be able to find any document by partial coincidence of one of the fields of the document with the value you entered. Say - "find all movies whose title begins with the substring Appo". Also, filtering by multiple fields is very limited due to the architecture of Firestore itself, so with this API implementation you are limited in the complexity of filter combinations, and in addition, some filter variations will require you to create special Firestore indexes.

However, the latter is not a big deal - just pay attention to the error notifications that may appear in the lower left corner. If you see such a message when trying to find something - from the detailed information in this message you will be able to follow the link straight to the Firestore index creation window, where you will have to click just one button. Let's take a look at what this might look like in real life - we will try to find movies, which release was from 1990 to 2000, or from 2010 to 2020, and box office is null (no data about box office):

Reduced number of documents per page

To reduce read quota usage, you can reduce the number of documents output per page by using the following network configuration:

NetworkConfig.simple(paginationLimitParameterDefaultValue: 50),

Partially-local collection api

caution

The first time any collection is queried, this API implementation will load the entire collection in its entirety. If you have large collections with more than 2-3 thousand documents, be very careful when using this API, as you may very quickly spend the free reading limits of Firestore, and if you use the paid version - it may lead to additional expenses.

FirebaseLocalCollectionApi is an implementation that supports short-term caching of loaded collections, as well as search and filtering of any level of complexity, which will be performed locally. Also, when loading data of any collection, absolutely all documents included in this collection will be loaded. This implementation will be useful if you need a very powerful search mechanism in your document collections, otherwise you should use Backend-First implementation, which will use Firebase read limit much more carefully.

Setting the cache lifetime

You can set the response caching time for both API implementations. But the purposes of these parameters are slightly different.

In FirebaseCollectionApi the default cache lifetime is zero - this means that caching is disabled. If you change the cache lifetime to something other than zero - then each unique collection request will be cached for the time you specify. For example - if you load the first page of a collection, it will be in the cache and if you flip through the pages and then back to the first page - it will be retrieved from the cache and you will save one request to Firestore that could have returned you N documents and you would have wasted N reads from the quota. If you apply filters - the current filtering configuration will also be saved in the cache, and if you use it again - the data will be retrieved from the cache.

In turn, FirebaseLocalCollectionApi caches all collection documents at once (as it loads them all at once). The lifetime of the cache is equal to how soon you will basically make the next request to Firestore to retrieve the data of a particular collection. If your data doesn't change too often, it makes sense to set the cache lifetime as long as possible.

However, keep in mind that caching is done in RAM (at least for now), which means that if you close your Nanc application - the next time you open it, there will be no cache and you will start wasting Firestore quota again.

FirebaseCollectionApi(api: firebaseApi, cacheTTL: const Duration(minutes: 5));
FirebaseLocalCollectionApi(api: firebaseApi, cacheTTL: const Duration(minutes: 10));

Deletion of models

info

Deleting a model does not delete the corresponding collection in Firestore at all. This behavior is specifically chosen to prevent accidental corruption of your data. If you want to implement this automatically as well - then you need to supplement the FirebaseModelApi.deleteModel method by implementing the required logic yourself.