Embrace Firebase, Firebase Realtime Database. (including github source code), you are welcome to pay attention.

Keywords: Database snapshot network JSON

 1 

R

E      I


•COMIC•


D

  3

5

   O 




Firebase can help you build better mobile applications and expand your business.


How does it work?

Firebase Realtime Database allows direct and secure access to databases from client code, so you can build rich collaborative applications. Data is kept locally, and real-time events continue to trigger even when they are offline, providing a responsive experience for end users. When the device is reconnected, Realtime Database synchronizes local data changes with remote updates that occur during the client's offline period and automatically merges any inconsistent data.





Implementation steps
  1. Integrate Firebase Realtime Database SDK.

    Include clients quickly through Gradle, CococaPods, or script inclusion.

  2. Create a Realtime Database reference.

    To set up data or subscription data changes, refer to your JSON data, such as "users/user:1234/phone_number".

  3. Set up data and listen for changes.

    Use this reference to write data or subscribe to changes.

  4. Enable offline retention.

    Allow data to be written to the device's local disk for offline use.

  5. Guarantee data security.

    Use Firebase Realtime Database security rules to secure your data.



What's wrong with a little practice?
  1. Install the Firebase SDK.

    See Adding Firebase to a Project

    No hyperlinks, please let me know. Thank you.

  2. Add a project of your own to the Firebase console.

  3. Add dependency

  4. compile 'com.google.firebase:firebase-database:10.2.6'

4. Configuring Firebase Database Rules

By default, your database rules grant full read and write access, but only to authenticated users. The following are the default rules:

{
 
"rules": {
   
".read": "auth != null",
   
".write": "auth != null"
 
}
}

If you are just starting to use the database and want to try it out before configuring security rules, you can grant full public database access using the following rules:

{
 
"rules": {
   
".read": true,
   
".write": true
 
}
}

Before publishing your application, you must configure these rules correctly to ensure that your users can only access the data they should be able to access.



How to Use Security Rules to Ensure Data Security


Code obfuscation
# Add this global rule
-keepattributes
Signature

# This rule will properly ProGuard all the model classes in
# the package com.yourcompany.models. Modify to fit the structure
# of your app.
-keepclassmembers
class com.yourcompany.models.** {
  *;
}


Organize your database

Building a well-structured database requires a lot of planning in advance. Most importantly, you need to plan how to save the data and how to retrieve the data afterwards, so as to simplify the process of saving and retrieving as much as possible.


All Firebase Realtime Database data is stored as JSON objects. You can think of this database as a cloud managed JSON tree. This database is different from the SQL database, without any tables or records. When you add data to the JSON tree, it becomes a node in the existing JSON structure.


For example, suppose chat applications allow users to store basic personal data and contact lists. Usually the user profile is located in a path such as / users/$uid. The user's alovelace database entry may look like the following:

{
 
"users": {
   
"alovelace": {
     
"name": "Ada Lovelace",
     
"contacts": { "ghopper": true },
    },
   
"ghopper": { ... },
   
"eclarke": { ... }
  }
}

Data structure best practices

  • Avoiding nested data

Firebase Realtime Database allows the depth of nested data up to 32 layers, but official documents do not recommend nesting, because when extracting a data in the database, all sub-nodes are retrieved, and when a user is granted read and write access to a node in the database, access to all data under that node is granted to that user.

Suppose a multi-layer nested structure as shown below:

{
 
// This is a poorly nested data architecture, because iterating the children
 
// of the "chats" node to get a list of conversation titles requires
 
// potentially downloading hundreds of megabytes of messages
 
"chats": {
   
"one": {
     
"title": "Historical Tech Pioneers",
     
"messages": {
       
"m1": { "sender": "ghopper", "message": "Relay malfunction found. Cause: moth." },
       
"m2": { ... },
       
// a very long list of messages
      }
    },
   
"two": { ... }
  }
}

If this nested design is adopted, the problem of circular access to data will arise. For example, to list chat conversation titles, you need to download the entire chats tree (including all members and messages) to the client.


  • Flattening data structure

If the data is split into different paths (also known as de-normalization), it can be downloaded effectively through different calls as needed. Consider this planar structure:

{
 
// Chats contains only meta info about each conversation
 
// stored under the chats's unique ID
 
"chats": {
   
"one": {
     
"title": "Historical Tech Pioneers",
     
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
     
"timestamp": 1459361875666
    },
   
"two": { ... },
   
"three": { ... }
  },

 
// Conversation members are easily accessible
 
// and stored by chat conversation ID
 
"members": {
   
// we'll talk about indices like this below
   
"one": {
     
"ghopper": true,
     
"alovelace": true,
     
"eclarke": true
    },
   
"two": { ... },
   
"three": { ... }
  },

 
// Messages are separate from data we may want to iterate quickly
 
// but still easily paginated and queried, and organized by chat
 
// converation ID
 
"messages": {
   
"one": {
     
"m1": {
       
"name": "eclarke",
       
"message": "The relay seems to be malfunctioning.",
       
"timestamp": 1459361875337
      },
     
"m2": { ... },
     
"m3": { ... }
    },
   
"two": { ... },
   
"three": { ... }
  }
}

Now, each conversation only needs to download a few bytes to circularly access the room list, while quickly extracting metadata, listing or displaying rooms in the UI.

When the message arrives, it can be extracted and displayed separately to ensure the timely response and speed of the UI.

  • Create extensible data

Suppose there is a two-way relationship between users and groups. Users belong to a group and the group contains a list of users. When it comes to deciding which groups users belong to, the situation becomes more complex.

What we need is a perfect way to list not only the groups to which users belong, but also to extract only the data of those groups. The "index" of the group may be of great help here:

// An index to track Ada's memberships
{
 
"users": {
   
"alovelace": {
     
"name": "Ada Lovelace",
     
// Index Ada's groups in her profile
     
"groups": {
         
// the value here doesn't matter, just that the key exists
         
"techpioneers": true,
         
"womentechmakers": true
      }
    },
    ...
  },
 
"groups": {
   
"techpioneers": {
     
"name": "Historical Tech Pioneers",
     
"members": {
       
"alovelace": true,
       
"ghopper": true,
       
"eclarke": true
      }
    },
    ...
  }
}

This index duplicates some data by storing Ada records and relationships under the group. Now, alovelace is indexed under a group, while techpioneers are listed in Ada's profile. So to delete Ada from this group, you have to update it in two places.

This is necessary redundancy for a two-way relationship. In this way, you can quickly and efficiently extract Ada's membership, and even if there are millions of records in a user or group list, or Realtime Database security rules prevent access to certain records, they are unaffected.

This method reverses the data by listing the ID as the key and setting the value to true, making key checking as simple as reading / users/$uid/groups/$group_id and checking for null. Compared with querying or scanning data, index is faster and more efficient.

Save data on Android

There are four ways to write data to Firebase Realtime Database:

setValue():

Common usage: Write or replace data to a defined path, such as users/<user-id>/<username>.


push():

Common usage: Add to the data list. Firebase every time push() is called Unique IDs are generated, such as user-posts/<user-ID>/<unique-post-ID>.


updateChildren():

Common usage: Update some keys in the defined path without replacing all data.


runTransaction():

Common usage: Update complex data that may be damaged by concurrent updates.


Write, update, or delete data in a reference

Basic write operation

For basic write operations, you can use setValue() to save data to a specific reference and replace any existing data in that path. This method can be used to perform the following operations:

  • Pass the type corresponding to the available JSON type as follows:

    • String

    • Long

    • Double

    • Boolean

    • Map<String, Object>

    • List<Object>

  • Pass in a custom Java object (if the default constructor of the class defining the object does not accept parameters and provides a common getter for the properties to be specified). If you use Java objects, the contents of the objects are automatically mapped to sub-locations in a nested manner. Using Java objects also generally improves the readability of code and makes it easier to maintain. For example, if the application contains the user's basic personal data, the User object may be as follows:

@IgnoreExtraProperties
public class User {

   
public String username;
   
public String email;

   
public User() {
       
// Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

   
public User(String username, String email) {
       
this.username = username;
       
this.email = email;
    }

}

Users can be added using setValue(), as follows:

private void writeNewUser(String userId, String name, String email) {
   
User user = new User(name, email);

    mDatabase.child(
"users").child(userId).setValue(user);
}

Using setValue() in this way will cover the data at the specified location, including all child nodes. However, you can still update the child nodes without rewriting the entire object. If you want to allow users to update their personal data, you can update the user name as follows:

mDatabase.child("users").child(userId).child("username").setValue(name);

Append to data list

The push() method is used to append data to a list of multiuser applications. Each time a new child node is added to the specified When Firebase references, push() methods generate unique ID.

By using automatically generated keys for each new element in the list, multiple clients can add child nodes to the same location at the same time without writing conflicts.

The unique ID generated by push() is based on a timestamp, so the list items are automatically arranged in chronological order.

You can use a reference to the new data (returned by the push() method) to get the value of the key automatically generated by the child node or to set the data for the child node. Calling getKey() for a push() reference returns the automatically generated key value.

Update specific fields

To write data to a specific child of a node at the same time without overwriting other child nodes, use the updateChildren() method.

When updateChildren() is called, lower-level subvalues can be updated by specifying a path for the key.

For example, a social blogging application may have a Post class, as follows:

@IgnoreExtraProperties
public class Post {

   
public String uid;
   
public String author;
   
public String title;
   
public String body;
   
public int starCount = 0;
   
public Map<String, Boolean> stars = new HashMap<>();

   
public Post() {
       
// Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

   
public Post(String uid, String author, String title, String body) {
       
this.uid = uid;
       
this.author = author;
       
this.title = title;
       
this.body = body;
    }

   
@Exclude
   
public Map<String, Object> toMap() {
       
HashMap<String, Object> result = new HashMap<>();
        result.put(
"uid", uid);
        result.put(
"author", author);
        result.put(
"title", title);
        result.put(
"body", body);
        result.put(
"starCount", starCount);
        result.put(
"stars", stars);

       
return result;
    }

}

To create a blog post and update it to the latest activity source and to publish the user's activity source at the same time, the blog application needs to use the following code:

private void writeNewPost(String userId, String username, String title, String body) {
   
// Create new post at /user-posts/$userid/$postid and at
   
// /posts/$postid simultaneously
   
String key = mDatabase.child("posts").push().getKey();
   
Post post = new Post(userId, username, title, body);
   
Map<String, Object> postValues = post.toMap();

   
Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put(
"/posts/" + key, postValues);
    childUpdates.put(
"/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

This example uses push() to create a blog post in a node containing all user blogs in / posts/$postid, while using getKey() to retrieve the corresponding key.

You can then use this key to create a second entry in the user's blog (located in / user-posts/$userid/$postid).

Using these paths, you can synchronously update multiple locations in the JSON tree by calling updateChildren() once, for example, how the example creates new blog posts in both locations at the same time.

Synchronized updates in this way are atomic: either all updates succeed or all updates fail.

Delete data

The easiest way to delete data is to call removeValue() on the reference to where the data is located.

In addition, you can delete data by specifying null as the value of another write operation (for example, setValue() or updateChildren()). You can use this method in conjunction with updateChildren() to delete multiple child nodes in a single API call.

Receiving Complete Callback

To know the time to submit the data to the Firebase Realtime Database server, you can add a completion listener. setValue() and updateChildren() accept optional completion listeners. When the written data is submitted to the database, the system calls the listener.

If the call fails for some reason, the system will pass an error object to the listener to explain the cause of the failure.

Save data as a transaction

Transaction processing operations can be used when dealing with complex data that may be damaged by concurrent modifications, such as incremental counters. You provide two parameters for this operation: update function and optional completion callback.

The update function takes the current state of the data as a parameter and returns the new state you want to write to. If another client writes to the location before you successfully write to the new value, the update function is called again with the new current value, and then the write is retried.

For example, in a sample social blog application, you can allow users to star and cancel blog posts, and track the number of stars that blog posts get, as follows:

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(
new Transaction.Handler() {
       
@Override
       
public Transaction.Result doTransaction(MutableData mutableData) {
           
Post p = mutableData.getValue(Post.class);
           
if (p == null) {
               
return Transaction.success(mutableData);
            }

           
if (p.stars.containsKey(getUid())) {
               
// Unstar the post and remove self from stars
                p.starCount = p.starCount -
1;
                p.stars.remove(getUid());
            }
else {
               
// Star the post and add self to stars
                p.starCount = p.starCount +
1;
                p.stars.put(getUid(),
true);
            }

           
// Set value and report transaction success
            mutableData.setValue(p);
           
return Transaction.success(mutableData);
        }

       
@Override
       
public void onComplete(DatabaseError databaseError, boolean b,
                               
DataSnapshot dataSnapshot) {
           
// Transaction completed
           
Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

If multiple users have outdated data on the same blog or client at the same time, using transaction processing can prevent star counting errors. If transaction processing is rejected, the server returns the current value to the client, which then uses the updated value to run transaction processing again.

The system will repeat this process until transaction processing is accepted or attempted too many times.

Note: Because doTransaction() will be called many times, it must be able to process null data. Even if data is already available in the remote database, it may not be cached locally when running transaction processing functions, resulting in null being the initial value.

Write data offline

If the client loses the network connection, your application will continue to run normally.

Each client connected to the Firebase database maintains its own internal version of any active data. When writing data, first write it to this local version. The Firebase client then synchronizes these data to the greatest extent possible with the remote database server and other clients.

Therefore, all writes to the database immediately trigger local events before any data is written to the server. This means that the application will remain responsive at all times, regardless of network latency or connection conditions.

After reconnecting, your application will receive a set of appropriate events so that the client can synchronize with the current server state without writing any custom code.



Retrieving data on Android

Next, we will introduce the basic knowledge of retrieving data and how to sort and filter Firebase data.


Additional event listener

Firebase data can be retrieved by attaching an asynchronous listener to a Firebase Database reference. The listener is triggered once by the initial state of the data and again when any change occurs.


You can listen for the following types of data retrieval events:

ValueEventListener->onDataChange()

Typical usage:

Read and listen for changes to the entire content of the path.


ChildEventListener->onChildAdded()

Typical usage:

Retrieve the list of items, or listen for new items to be added to the list of items. It is recommended to work with onChildChanged() and onChildRemove () to monitor changes to lists.

->onChildChanged()

Typical usage:

Listen for changes to items in the list. Use in conjunction with onChildAdded() and onChildRemove () to monitor changes to lists.

->onChildRemoved()

Typical usage:

Listen for items that are being deleted from the list. Used in conjunction with onChildAdded() and onChildChanged(). To monitor changes to the list.

->onChildMoved()

Typical usage:

Used in conjunction with sorted data to listen for changes to project priorities.


To add a value event listener, use the addValueEventListener() or addListener ForSingleValueEvent () method. To add a child event listener, use the addChildEventListener() method.


In more detail:

Value event

Use the onDataChange() method to read a static snapshot of the content that exists in a given path when an event occurs. This method will be triggered once when additional listeners are added, and again when any changes in data, including child nodes, occur.


The system passes a snapshot of all data (including child node data) in the location for the event callback. If there is no data, the snapshot returned is null.


Important Notes:

Important Notes:

Important Notes:

The onDataChange() method is invoked whenever the data changes at the specified database reference, including changing child nodes. To limit the size of snapshots, attach listeners only at the lowest level that you need to observe changes. For example, it is recommended not to attach listeners to the root directory of the database.


The following example demonstrates how a social blogging application retrieves blog details from a database:

ValueEventListener postListener = new ValueEventListener() {
   
@Override
   
public void onDataChange(DataSnapshot dataSnapshot) {
       
// Get Post object and use the values to update the UI
       
Post post = dataSnapshot.getValue(Post.class);
       
// ...
   
}

   
@Override
   
public void onCancelled(DatabaseError databaseError) {
       
// Getting Post failed, log a message
       
Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
       
// ...
   
}
};
mPostReference
.addValueEventListener(postListener);

The listener receives a DataSnapshot containing the data at the specified location in the database when the event occurs. When getValue() is called on a snapshot, the data is returned Java object representation. If there is no data at this location, calling getValue() returns null.

In this example, ValueEventListener also defines the onCancelled() method. If read is cancelled, this method is called. For example, if the client does not have permission to read data from the Firebase database location, the read can be cancelled. The system will pass a DatabaseError object to this method to explain why it failed.

Sub event

In response to a specific operation performed on a node's child through an operation (e.g., adding a new child through a push() method or updating a child through an updateChildren() method), the system triggers a child event. It is very useful to use each of these methods in combination to listen for changes made to specific nodes in the database.

For example, social blogging applications can combine these methods to monitor activities in blog comments, as follows:

ChildEventListener childEventListener = new ChildEventListener() {
   
@Override
   
public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
       
Log.d(TAG, "onChildAdded:" + dataSnapshot.getKey());

       
// A new comment has been added, add it to the displayed list
       
Comment comment = dataSnapshot.getValue(Comment.class);

       
// ...
   
}

   
@Override
   
public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
       
Log.d(TAG, "onChildChanged:" + dataSnapshot.getKey());

       
// A comment has changed, use the key to determine if we are displaying this
       
// comment and if so displayed the changed comment.
       
Comment newComment = dataSnapshot.getValue(Comment.class);
       
String commentKey = dataSnapshot.getKey();

       
// ...
   
}

   
@Override
   
public void onChildRemoved(DataSnapshot dataSnapshot) {
       
Log.d(TAG, "onChildRemoved:" + dataSnapshot.getKey());

       
// A comment has changed, use the key to determine if we are displaying this
       
// comment and if so remove it.
       
String commentKey = dataSnapshot.getKey();

       
// ...
   
}

   
@Override
   
public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
       
Log.d(TAG, "onChildMoved:" + dataSnapshot.getKey());

       
// A comment has changed position, use the key to determine if we are
       
// displaying this comment and if so move it.
       
Comment movedComment = dataSnapshot.getValue(Comment.class);
       
String commentKey = dataSnapshot.getKey();

       
// ...
   
}

   
@Override
   
public void onCancelled(DatabaseError databaseError) {
       
Log.w(TAG, "postComments:onCancelled", databaseError.toException());
       
Toast.makeText(mContext, "Failed to load comments.",
               
Toast.LENGTH_SHORT).show();
   
}
};
ref.addChildEventListener(childEventListener);

The onChildAdded() callback is usually used to retrieve a list of items in a Firebase database. The onChildAdded() callback will be triggered once for each existing child node and again every time a new child node is added to the specified path.

The system passes a snapshot of the data containing the new child nodes to the listener.

Every time a child node is modified, the onChildChanged() callback is triggered. This includes any modifications to descendants of child nodes. This callback is usually used with onChildAdded() and onChildRemoved() callbacks in response to changes made to the list of items. The snapshot passed to the event listener contains updated data for the child nodes.

When a direct child node is deleted, the onChildRemove () callback is triggered. This callback is usually used with onChildAdded() and onChildChanged() callbacks. The snapshot passed to the event callback contains the data of the deleted child nodes.

Whenever onChildChanged() callbacks are triggered by updates (resulting in reordering of child nodes), the system triggers onChildMoved() callbacks. This callback is used in conjunction with data sorted by priority.

Separation listener

Callbacks can be deleted by calling removeEventListener() on Firebase database references.

If you add a listener to a data location multiple times, the listener will be called multiple times for each event, and you must separate the same number of times to delete it completely.

When removeEventListener() is called on a parent listener, the listener registered on its child node is not automatically deleted; removeEventListener() must also be called on any child listener in order to delete the callback.


Read the data once (distraught)

In some cases, you may want to call a callback and delete it immediately (for example, when initializing UI elements that are not expected to change). You can simplify this situation by using the addListenerForSingleValueEvent() method: callbacks are triggered only once, and will not be triggered again later.

This is very useful for data that only needs to be loaded once and is not expected to change frequently or need to be actively listened on. For example, the blog application in the above example uses this method to load the user's personal data when he begins to write a new blog article:

final String userId = getUid();
mDatabase
.child("users").child(userId).addListenerForSingleValueEvent(
       
new ValueEventListener() {
           
@Override
           
public void onDataChange(DataSnapshot dataSnapshot) {
               
// Get user value
               
User user = dataSnapshot.getValue(User.class);

               
// ...
           
}

           
@Override
           
public void onCancelled(DatabaseError databaseError) {
               
Log.w(TAG, "getUser:onCancelled", databaseError.toException());
               
// ...
           
}
       
});


Sorting and filtering data

You can use the Realtime Database Query class to retrieve keys, values by value, values by subnodes, or data sorted by priority. You can also filter the sorted results to get a specific number of results or a series of keys or values.

Note: Filtering and sorting operations can be costly, especially when performed by clients. If your application uses queries, define the.indexOn rule to index these keys on the server and improve query performance

Sorting data

To retrieve sorted data, first specify one of the sorting criteria and determine how to sort the results (four methods):

orderByChild()

Usage: Sort the results by the value of the specified subkey.


orderByKey()

Usage: Sort the results by subkey.


orderByValue()

Usage: Sort the results by subvalues.


orderByPriority()

Usage: Sort the results by the specified priority of the node.

Only one sort basis method can be used at a time. Calling the sorting method multiple times for the same query can cause errors.

The following example demonstrates how to retrieve a list of user top blog posts (ranked by star number):

// My top posts by number of stars
String myUserId = getUid();
Query myTopPostsQuery = databaseReference.child("user-posts").child(myUserId)
       
.orderByChild("starCount");

Filtering data

To filter data, you can construct queries by combining one of the restriction or scope methods with the sorting basis method (five methods).

limitToFirst()

Usage: Set the maximum number of items to be returned from the beginning of the sorted results list.


limitToLast()

Usage: Set the maximum number of items to return from the end of the sorted results list.


startAt()

Usage: Returns items greater than or equal to the specified key, value, or priority, depending on the sorting method selected.


endAt()

Usage: Returns items less than or equal to the specified key, value, or priority, depending on the sorting method selected.


equalTo()

Usage: Returns items equal to the specified key, value or priority, depending on the method of sorting selected.


Unlike sorting by method, you can use a combination of restriction or range functions. For example, you can combine startAt() with endAt() to limit the results to a specified range of values.


Limited number of results

Use the limitToFirst() and limitToLast() methods to set the maximum number of child nodes to synchronize for a given callback. For example, if you limit the use of limitToFirst() to 100, you will initially receive up to 100 onChildAdded() callbacks.

If you store fewer than 100 items in the Firebase database, each item triggers an onChildAdded() callback.

As the project changes, you will receive onChildAdded() callbacks for items entering the query, and onChildRemoved() callbacks for items exiting the query, so that the total is always maintained as follows 100.

The following example demonstrates how the sample blog application defines queries to retrieve a list of 100 latest blog posts by all users:

// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys
Query recentPostsQuery = databaseReference.child("posts")
       
.limitToFirst(100);

This example defines only one query and requires additional listeners to actually synchronize data.

Key, Value or Priority Filtering

You can use startAt(), endAt(), and equalTo() to select any starting point, end point, and equivalent point for the query. This is very useful for paging data or finding items whose child nodes are specific values.


Sorting query data

Describes how to sort data by each sorting method in the Query class.

When orderByChild() is used, the system sorts the data containing the specified subkeys in the following way:

  1. The child node with the specified subkey null value is displayed at the front.

  2. The child node that specifies the child key as false follows. If the values of multiple sub-nodes are false, the keys are sorted in dictionary order.

  3. The child node that specifies the child key as the true value follows. If the values of multiple sub-nodes are true, the keys are sorted in dictionary order.

  4. The child nodes that specify the number of subkeys follow closely, sorted in ascending order. If the specified subkeys of multiple subnodes have the same value, the keys sort them.

  5. Strings are displayed after numbers and arranged in ascending order in dictionary order. If the specified subkeys of multiple subnodes have the same value, the keys are sorted in dictionary order.

  6. Objects are placed at the end and arranged in ascending order in dictionary order according to key names.

orderByKey

When orderByKey() is used to sort the data, the system will press the key name to return the data in ascending order.

  1. The keys can be parsed as 32-bit integers with child nodes displayed in front, sorted in ascending order.

  2. Subnodes with string values as keys follow closely, and are arranged in ascending order in dictionary order.

orderByValue

When orderByValue() is used, the system will sort the child nodes according to the corresponding values. The sorting criteria are the same as those in orderByChild(), but the value of the node is used here instead of specifying the value of the subkey.

orderByPriority

If the priority of the data has been specified through setPriority(), orderByPriority() can be used to sort the data. In this case, the ordering of subnodes depends on their priorities and keys, as follows:

  1. Priority-free (default) child nodes are shown in front.

  2. Subnodes that prioritize numbers follow. They are sorted numerically from small to large by priority.

  3. Subnodes with string as priority are placed at the end. They are sorted in dictionary order by priority.

  4. When two sub-nodes have the same priority (including no priority), the keys are sorted. Number keys are displayed at the front (in ascending order of numbers), followed by the rest (in ascending order of dictionaries).

Priority values can only be numbers or strings. Digital priorities are stored and sorted in the form of IEEE 754 double-precision floating-point numbers. Keys are always stored as strings and are treated as numbers only if they can be parsed as 32-bit integers.


Enable offline functionality in Android

Firebase applications have powerful offline capabilities, and several features can significantly enhance the offline experience. Enabling disk persistence allows applications to retain all their state after restart. We provide tools for monitoring online and connection status.

Firebase automatically processes the data cache when the network is interrupted, and sends the cached data again when the network reconnects. Disk persistence is used when running Firebase caching through code, so that even if the app is restarted, the data is still valid.

FirebaseDatabase.getInstance().setPersistenceEnabled(true);


Keep up-to-date data

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef
.keepSynced(true);

In this way, the client will automatically download the updated data from the server, if automatic updates are prohibited:

scoresRef.keepSynced(false);

Firebase's default cache space is 10MB, and how to cache data over 10MB, the earliest data will be cleared. If the data is kept Synced, it will not be cleared.


Offline Data Query

When an app is offline, it queries the data from the cached data. When an app reconnects to the network, Firebase downloads the latest data from the network and triggers the query.


The following code example demonstrates the four latest scoring data for querying blogs.

DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores");
scoresRef
.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() {
   
@Override
   
public void onChildAdded(DataSnapshot snapshot, String previousChild) {
     
System.out.println("The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
   
}
});

Now, if the app network connection is disconnected, or the above query, we query the latest two data:

scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() {
   
@Override
   
public void onChildAdded(DataSnapshot snapshot, String previousChild) {
       
System.out.println("The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue());
     
}
});

Handling offline transactions

Any offline transaction operation will be placed in the request queue and resend when the app reconnects to the network.


Note: Transaction requests will not be persisted to disk, and app will disappear after restart.


Handling the moment when the network is disconnected (hands-on)

DatabaseRef presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage");
// Write a string when this client loses connection
presenceRef
.onDisconnect().setValue("I disconnected!");

The following example ensures successful offline operations:

presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() {
   
@Override
   
public void onComplete(DatabaseError error, DatabaseReference firebase) {
       
if (error != null) {
           
System.out.println("could not establish onDisconnect event:" + error.getMessage());
       
}
   
}
});

The onDisconnect event operation can also be cancelled halfway (I regret it)

OnDisconnect onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef
.setValue("I disconnected");
// some time later when we change our minds
onDisconnectRef
.cancel();


Handling the moment of network connection (hands-on)


Firebase provides a special location for network connection status /.info/connected

DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected");
connectedRef
.addValueEventListener(new ValueEventListener() {
 
@Override
 
public void onDataChange(DataSnapshot snapshot) {
   
boolean connected = snapshot.getValue(Boolean.class);
   
if (connected) {
     
System.out.println("connected");
   
} else {
     
System.out.println("not connected");
   
}
 
}

 
@Override
 
public void onCancelled(DatabaseError error) {
   
System.err.println("Listener was cancelled");
 
}
});


On android devices, Firebase automatically manages network connections for data, saving bandwidth and power. If the client has no active listener, no waiting write or onDisconnect, and no explicit go Offline, Firebase automatically disconnects the network after 60 seconds.


Handling exceptions

Firebase can insert a timestamp. The following examples clearly show the client's short-term time:

DatabaseReference userLastOnlineRef = FirebaseDatabse.getInstance().getReference("users/joe/lastOnline");
userLastOnlineRef
.onDisconnect().setValue(ServerValue.TIMESTAMP);

Clock Skew Processing

DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset");
offsetRef
.addValueEventListener(new ValueEventListener() {
 
@Override
 
public void onDataChange(DataSnapshot snapshot) {
   
double offset = snapshot.getValue(Double.class);
   
double estimatedServerTimeMs = System.currentTimeMillis() + offset;
 
}

 
@Override
 
public void onCancelled(DatabaseError error) {
   
System.err.println("Listener was cancelled");
 
}
});

Sample Presence App

// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
final FirebaseDatabase database = FirebaseDatabase.getInstance();
final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections");

// stores the timestamp of my last disconnect (the last time I was seen online)
final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline");

final DatabaseReference connectedRef = database.getReference(".info/connected");
connectedRef
.addValueEventListener(new ValueEventListener() {
 
@Override
 
public void onDataChange(DataSnapshot snapshot) {
   
boolean connected = snapshot.getValue(Boolean.class);
   
if (connected) {
     
// add this device to my connections list
     
// this value could contain info about the device or a timestamp too
     
DatabaseReference con = myConnectionsRef.push();
      con
.setValue(Boolean.TRUE);

     
// when this device disconnects, remove it
      con
.onDisconnect().removeValue();

     
// when I disconnect, update the last time I was seen online
      lastOnlineRef
.onDisconnect().setValue(ServerValue.TIMESTAMP);
   
}
 
}

 
@Override
 
public void onCancelled(DatabaseError error) {
   
System.err.println("Listener was cancelled at .info/connected");
 
}
});



github Source

https://github.com/zhangbinangel/FirebaseExample.git


There are plans and facts:










Posted by andysez on Mon, 17 Dec 2018 02:18:06 -0800