npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

react-native-firebase-local-cache

v0.4.0

Published

Speed up the apparent load time of screens in your app by caching content. Wraps the Firebase 'on' and 'once' methods to make caching easy!

Downloads

19

Readme

Build Status Coverage Status

react-native-firebase-local-cache

A simple wrapper to add local caching of data to Firebase on(...) and once(...) listeners, useful for improving the apparent load time of screens/pages in your app.

Updated API

The exported methods from the module have changed slightly recently with the addition of support for child_...ed events and an equivalent to once(...). The exports are:

'value' Events

onValue(dbRef, snapCallback, processedCallback, cancelCallbackOrContext, context)

offValue(dbRef)

'child_added' Events

onChildAdded(dbRef, fromCacheCallback, newDataArrivingCallback, snapCallback, cancelCallbackOrContext, context)

offChildAdded(dbRef, dataToCache)

Other Child Events

onChildRemoved(dbRef, callback, cancelCallbackOrContext, context)

onChildChanged(dbRef, callback, cancelCallbackOrContext, context)

onChildMoved(dbRef, callback, cancelCallbackOrContext, context)

Once/Twice 'value' Events

twice(dbRef, snapCallback, processedCallback, cancelCallbackOrContext, context)

Cache Control

clearCacheForRef(dbRef)

clearCache()

Simple Use Case - onValue

onValue(dbRef, snapCallback, processedCallback, cancelCallbackOrContext, context)

In the simplest example, say displaying a users name and email. Previously you could do the following:

this.userRef.on('value', function(snap) {
  this.setState({
    name: snap.val().name,
    email: snap.val().email
  });
},this);

The equivalent using this module would use onValue(dbRef, snapCallback, processedCallback, cancelCallbackOrContext, context):

import * as cachedListener from 'react-native-firebase-local-cache';

...

cachedListener.onValue(this.userRef, function(snap) {
  return {
    name: snap.val().name,
    email: snap.val().email
  });
}, this.setState, this);

There is a little bit of trickiness going on here, but essentially the return value of snapCallback is cached, then passed as an argument to the processedCallback. The next time the listener is set up, processedCallback (in this case this.setState) will be called with the cached data immediately. When fresh snapshots arrive from the server they will be processed through both callbacks.

So why not cache the snapshot directly? It is actually a pretty complex object that can't be stringified easily, it makes more sense to just process the snapshot and save that. Contributions always welcome though!

Another slightly more complex example, say you have a list showing chat rooms a user is subscribed to, you might use the following code:

this.userRoomsRef.on('value', function(snap) {
  var rooms = [];

  snap.forEach((child) => {
    rooms.push({
      roomName: child.val().name,
      _key: child.key
    });
  });
  
  this.setState({
    dataSource: this.state.dataSource.cloneWithRows(rooms))
  });
},this);

Again, until any data has been loaded, the list will remain empty. The equivalent code to cache this list for subsequent loads would be:

import * as cachedListener from 'react-native-firebase-local-cache';

...


cachedListener.onValue(this.userRoomsRef, function(snap) {
  var rooms = [];

  snap.forEach((child) => {
    rooms.push({
      roomName: child.val().name,
      _key: child.key
    });
  });

  return rooms;
}, function(rooms) {
  this.setState({
    dataSource: this.state.dataSource.cloneWithRows(rooms))
  });
},this);

The first callback does any processing of new data required and returns a JSON.stringify-able object that can be cached. (Note: objects are only committed to the cache when the corresponding cachedListener.off(...) method is called.)

The second callback is passed either the freshly processed new data, or when first started, the cached data.

A cancelCallback and/or context can optionally be passed as well.

Multiple Listeners - onValue

If you have multiple listeners attached to the same location, it is possible that they will overwrite each other, here is a very contrived example:

On one screen:

this.userRef = firebase.database().ref(`users/${this.userId}`);

cachedListener.onValue(this.userRef, function(snap) {
  return {
    name: snap.val().name,
    email: snap.val().email
  });
}, this.setState, this);

And on another:

this.userRef = firebase.database().ref(`users/${this.userId}`);

cachedListener.onValue(this.userRef, function(snap) {
  return {
    name: snap.val().name,
    email: snap.val().email,
    age: snap.val().age
  });
}, function(user) {
  this.setState(user);
  this._doSomethingWithEmail(user.email);
}, this);

When the first case runs, it will remove the age from the cache as it is not present in the returned data.

One solution is to use a helper method that will use a single method to process the snapshot, and save everything you require:

const usersRef = firebase.database().ref('users');

...

export function createCachedUserListener(userId, callback, errorCallback, context) {
  cachedListener.onValue(usersRef.child(userId), function(snapshot) {
    //Process the snapshot to get any data that might be required.
    return {
      name: snap.val().name,
      email: snap.val().email,
      age: snap.val().age
    };
  }, callback, errorCallback, context);
}

On your original files, just use the data that is required:

Helper.createCachedUserListener(this.userId, function(user) {
  this.setState(user);
  this._doSomethingWithEmail(user.email);
}, this);

You'll still need to call the off method to commit the data to the cache, whether you do this in a helper method or by calling it directly is up to you.

Simple Use Case - twice

This is a wrapper around the firebase once method and operates in a very similar way to the onValue method listed above. The key differences are:

  • Just like once, it will only wait for one new piece of data to come from the server, then it will stop listening and disconnect.
  • snapCallback will only be called once when the new data arrives, processedCallback will be called twice - once with cached data, once with new data.
  • Data is cached as soon as it is returned from the snapCallback, rather than when the off method is called. (Since you don't need to call it...)
  • It is cached separately to the onValue data.

Simple Use Case - onChildAdded

The flow of data through the onChildAdded method is somewhat different , but this is reflective of the common use cases.

onChildAdded(dbRef, fromCacheCallback, newDataArrivingCallback, snapCallback, cancelCallbackOrContext, context)

Create an 'child_added' on listener that will first return any cached data saved by a call to offChildAdded. When fresh data arrives, newDataArrivingCallback will be called once, followed by the standard snap callback. From this point on only the snapCallback will be called.

Parameters

dbRef: firebase.database.Reference, Firebase database reference to listen at.

fromCacheCallback: *, Callback that will be called with cached data if any is available.

newDataArrivingCallback: *, Callback called immediately before fresh data starts arriving.

snapCallback: *, Callback called when new data snapshots arrive from the server.

cancelCallbackOrContext: *, Optional callback that will be called in the case of an error, e.g. forbidden.

context: *, Optional context that will be bound to this in callbacks.

Returns: Promise, Resolves when the cache has been read and listener attached to DB ref.

Example App Demo

The following is from the example app:

  _startCachedListener() {
    cachedListener.onChildAdded(this.messagesRef, function(cached){
      // Receiving cached list of messages:
      this.setState({
        dataSource: this.state.dataSource.cloneWithRows(cached),
      }); 
    }, function() {
      //Clean up messages list (not required but this is where you would do it if it was)
      this.messages = [];
    }, function(snapshot) {
      //New snapshot available.
      this.messages.push(this._messageFromSnapshot(snapshot));
    
      this.setState({
        dataSource: this.state.dataSource.cloneWithRows(this.messages),
      }); 
    },this);
  }

  _stopCachedListener() {
    //Cache the top two messages. Alternatively, use this.messages.slice(-2) for the last two - depends on how they are sorted.
    cachedListener.offChildAdded(this.messagesRef, this.messages.slice(0,2)); 
  }

The first callback , fromCacheCallback, is passed the cached data, and loads the ListView datasource with this data.

When fresh data starts arriving the second callback, newDataArrivingCallback, is called. In this case nothing needs to be done, however there are cases where cached data needs to be cleared.

After this is called, the third callback, snapCallback, is called with the new snapshot.

In the _stopCachedListener call, the first 2 messages are slices off the message array and passed to the offChildAdded method, the next time the screen is opened fromCacheCallback will be called and passed this item.

The other child_... eventypes are all wrapped in onChild... methods that do nothing other than pass data through to the native method.

Differences and Limitations

There are some subtle differences between this implementation and the Firebase one that should be noted:

  • The Firebase database.Reference.on(...) method returns the provided callback function unmodified. In this module a Promise is returned that is resolved after cached data has been loaded, the callback has been called, and the native listener has been started.
  • If passing a context, either do so as the 5th parameter (if no cancelCallback is defined), or as the 6th parameter (if a cancelCallback is defined). I.e. don't pass a null or undefined cancelCallback, either omit it completely or put in something valid.
  • You should have been calling the dbRef.off() method previously, continue to do so but call cachedListener.offValue(dbRef) or cachedListener.offChildAdded(...). This is when the data is actually saved to the cache.

API

See the auto-generated and hopefully up-to-date docs at: ./docs/index.md

The code above should give a pretty good idea of how to use it.

Or look at the future Example app which doesn't yet exist.

Other Info

Contributions/Criticism

Let me know if there are any issues/bugs/improvements and I'll have a crack at them. Otherwise, feel free to make contributions.

Acknowledgments

I stole the basic structure of this module from @jasonmerino who wrote the following module: https://github.com/jasonmerino/react-native-simple-store