Stay organized with collectionsSave and categorize content based on your preferences.
This document covers the basics of reading and writing Firebase data.
Firebase data is written to aFirebaseDatabasereference and retrieved by
attaching an asynchronous listener to the reference. The listener is triggered
once for the initial state of the data and again anytime the data changes.
(Optional) Prototype and test withFirebase Local Emulator Suite
Before talking about how your app reads from and writes toRealtime Database,
let's introduce a set of tools you can use to prototype and testRealtime Databasefunctionality:Firebase Local Emulator Suite. If you're trying out different data
models, optimizing your security rules, or working to find the most
cost-effective way to interact with the back-end, being able to work locally
without deploying live services can be a great idea.
ARealtime Databaseemulator is part of theLocal Emulator Suite, which
enables your app to interact with your emulated database content and config, as
well as optionally your emulated project resources (functions, other databases,
and security rules).
Using theRealtime Databaseemulator involves just a few steps:
Adding a line of code to your app's test config to connect to the emulator.
From the root of your local project directory, runningfirebase emulators:start.
Making calls from your app's prototype code using aRealtime Databaseplatform
SDK as usual, or using theRealtime DatabaseREST API.
For basic write operations, you can usesetValue()to save data to a specified
reference, replacing any existing data at that path. You can use this method to:
Pass types that correspond to the available JSON types as follows:
String
Long
Double
Boolean
Map<String, Object>
List<Object>
Pass a custom Java object, if the class that defines it has a default
constructor that takes no arguments and has public getters for the properties
to be assigned.
If you use a Java object, the contents of your object are automatically mapped
to child locations in a nested fashion. Using a Java object also typically makes
your code more readable and easier to maintain. For example, if you have an
app with a basic user profile, yourUserobject might look as follows:
Kotlin
@IgnoreExtraPropertiesdataclassUser(valusername:String?=null,valemail:String?=null){// Null default values create a no-argument default constructor, which is needed// for deserialization from a DataSnapshot.}
@IgnoreExtraPropertiespublicclassUser{publicStringusername;publicStringemail;publicUser(){// Default constructor required for calls to DataSnapshot.getValue(User.class)}publicUser(Stringusername,Stringemail){this.username=username;this.email=email;}}
UsingsetValue()in this way overwrites data at the specified location,
including any child nodes. However, you can still update a child without
rewriting the entire object. If you want to allow users to update their profiles
you could update the username as follows:
To read data at a path and listen for changes, use theaddValueEventListener()method to add aValueEventListenerto aDatabaseReference.
Listener
Event callback
Typical usage
ValueEventListener
onDataChange()
Read and listen for changes to the entire contents of a path.
You can use theonDataChange()method to read a static snapshot of the
contents at a given path, as they existed at the time of the event. This method
is triggered once when the listener is attached and again every time the data,
including children, changes. The event callback is passed a snapshot containing
all data at that location, including child data. If there is no data, the
snapshot will returnfalsewhen you callexists()andnullwhen you callgetValue()on it.
The following example demonstrates a social blogging application retrieving the
details of a post from the database:
Kotlin
valpostListener=object:ValueEventListener{overridefunonDataChange(dataSnapshot:DataSnapshot){// Get Post object and use the values to update the UIvalpost=dataSnapshot.getValue<Post>()// ...}overridefunonCancelled(databaseError:DatabaseError){// Getting Post failed, log a messageLog.w(TAG,"loadPost:onCancelled",databaseError.toException())}}postReference.addValueEventListener(postListener)
ValueEventListenerpostListener=newValueEventListener(){@OverridepublicvoidonDataChange(DataSnapshotdataSnapshot){// Get Post object and use the values to update the UIPostpost=dataSnapshot.getValue(Post.class);// ..}@OverridepublicvoidonCancelled(DatabaseErrordatabaseError){// Getting Post failed, log a messageLog.w(TAG,"loadPost:onCancelled",databaseError.toException());}};mPostReference.addValueEventListener(postListener);
The listener receives aDataSnapshotthat contains the data at the specified
location in the database at the time of the event. CallinggetValue()on a
snapshot returns the Java object representation of the data. If no data exists
at the location, callinggetValue()returnsnull.
In this example,ValueEventListeneralso defines theonCancelled()method that
is called if the read is canceled. For example, a read can be canceled if the
client doesn't have permission to read from a Firebase database location. This
method is passed aDatabaseErrorobject indicating why the failure occurred.
Read data once
Read once using get()
The SDK is designed to manage interactions with database servers whether your
app is online or offline.
Generally, you should use theValueEventListenertechniques described above
to read data to get notified of updates to the data from the backend. The
listener techniques reduce your usage and billing, and are optimized to
give your users the best experience as they go online and offline.
If you need the data only once, you can useget()to get a snapshot of the
data from the database. If for any reasonget()is unable to return the server
value, the client will probe the local storage cache and return an error if the
value is still not found.
Unnecessary use ofget()can increase use of bandwidth and lead to loss of
performance, which can be prevented by using a realtime listener as shown above.
In some cases you may want the value from the local cache to be returned
immediately, instead of checking for an updated value on the server. In those
cases you can useaddListenerForSingleValueEventto get the data from the
local disk cache immediately.
This is useful for data that only needs to be loaded once and isn't expected to
change frequently or require active listening. For instance, the blogging app
in the previous examples uses this method to load a user's profile when they
begin authoring a new post.
Updating or deleting data
Update specific fields
To simultaneously write to specific children of a node without overwriting other
child nodes, use theupdateChildren()method.
When callingupdateChildren(), you can update lower-level child values by
specifying a path for the key. If data is stored in multiple locations to scale
better, you can update all instances of that data usingdata fan-out. For example, a
social blogging app might have aPostclass like this:
@IgnoreExtraPropertiespublicclassPost{publicStringuid;publicStringauthor;publicStringtitle;publicStringbody;publicintstarCount=0;publicMap<String,Boolean>stars=newHashMap<>();publicPost(){// Default constructor required for calls to DataSnapshot.getValue(Post.class)}publicPost(Stringuid,Stringauthor,Stringtitle,Stringbody){this.uid=uid;this.author=author;this.title=title;this.body=body;}@ExcludepublicMap<String,Object>toMap(){HashMap<String,Object>result=newHashMap<>();result.put("uid",uid);result.put("author",author);result.put("title",title);result.put("body",body);result.put("starCount",starCount);result.put("stars",stars);returnresult;}}
To create a post and simultaneously update it to the recent activity
feed and the posting user's activity feed, the blogging application uses
code like this:
Kotlin
privatefunwriteNewPost(userId:String,username:String,title:String,body:String){// Create new post at /user-posts/$userid/$postid and at// /posts/$postid simultaneouslyvalkey=database.child("posts").push().keyif(key==null){Log.w(TAG,"Couldn't get push key for posts")return}valpost=Post(userId,username,title,body)valpostValues=post.toMap()valchildUpdates=hashMapOf<String,Any>("/posts/$key"topostValues,"/user-posts/$userId/$key"topostValues,)database.updateChildren(childUpdates)}
privatevoidwriteNewPost(StringuserId,Stringusername,Stringtitle,Stringbody){// Create new post at /user-posts/$userid/$postid and at// /posts/$postid simultaneouslyStringkey=mDatabase.child("posts").push().getKey();Postpost=newPost(userId,username,title,body);Map<String,Object>postValues=post.toMap();Map<String,Object>childUpdates=newHashMap<>();childUpdates.put("/posts/"+key,postValues);childUpdates.put("/user-posts/"+userId+"/"+key,postValues);mDatabase.updateChildren(childUpdates);}
This example usespush()to create a post in the node containing posts for
all users at/posts/$postidand simultaneously retrieve the key withgetKey(). The key can then be used to create a second entry in the user's
posts at/user-posts/$userid/$postid.
Using these paths, you can perform simultaneous updates to multiple locations in
the JSON tree with a single call toupdateChildren(), such as how this example
creates the new post in both locations. Simultaneous updates made this way
are atomic: either all updates succeed or all updates fail.
Add a Completion Callback
If you want to know when your data has been committed, you can add a
completion listener. BothsetValue()andupdateChildren()take an optional
completion listener that is called when the write has been successfully
committed to the database. If the call was unsuccessful, the listener is
passed an error object indicating why the failure occurred.
Kotlin
database.child("users").child(userId).setValue(user).addOnSuccessListener{// Write was successful!// ...}.addOnFailureListener{// Write failed// ...}
The simplest way to delete data is to callremoveValue()on a reference to the
location of that data.
You can also delete by specifyingnullas the value for another write
operation such assetValue()orupdateChildren(). You can use this technique
withupdateChildren()to delete multiple children in a single API call.
Detach listeners
Callbacks are removed by calling theremoveEventListener()method on your
Firebase database reference.
If a listener has been added multiple times to a data location, it is
called multiple times for each event, and you must detach it the same number of
times to remove it completely.
CallingremoveEventListener()on a parent listener does not
automatically remove listeners registered on its child nodes;removeEventListener()must also be called on any child listeners
to remove the callback.
Save data as transactions
When working with data that could be corrupted by concurrent
modifications, such as incremental counters, you can use atransaction operation.
You give this operation two arguments: an update function and an optional
completion callback. The update function takes the current state of the data as
an argument and returns the new desired state you would like to write. If
another client writes to the location before your new value is successfully
written, your update function is called again with the new current value, and
the write is retried.
For instance, in the example social blogging app, you could allow users to
star and unstar posts and keep track of how many stars a post has received
as follows:
Kotlin
privatefunonStarClicked(postRef:DatabaseReference){// ...postRef.runTransaction(object:Transaction.Handler{overridefundoTransaction(mutableData:MutableData):Transaction.Result{valp=mutableData.getValue(Post::class.java)?:returnTransaction.success(mutableData)if(p.stars.containsKey(uid)){// Unstar the post and remove self from starsp.starCount=p.starCount-1p.stars.remove(uid)}else{// Star the post and add self to starsp.starCount=p.starCount+1p.stars[uid]=true}// Set value and report transaction successmutableData.value=preturnTransaction.success(mutableData)}overridefunonComplete(databaseError:DatabaseError?,committed:Boolean,currentData:DataSnapshot?,){// Transaction completedLog.d(TAG,"postTransaction:onComplete:"+databaseError!!)}})}
privatevoidonStarClicked(DatabaseReferencepostRef){postRef.runTransaction(newTransaction.Handler(){@NonNull@OverridepublicTransaction.ResultdoTransaction(@NonNullMutableDatamutableData){Postp=mutableData.getValue(Post.class);if(p==null){returnTransaction.success(mutableData);}if(p.stars.containsKey(getUid())){// Unstar the post and remove self from starsp.starCount=p.starCount-1;p.stars.remove(getUid());}else{// Star the post and add self to starsp.starCount=p.starCount+1;p.stars.put(getUid(),true);}// Set value and report transaction successmutableData.setValue(p);returnTransaction.success(mutableData);}@OverridepublicvoidonComplete(DatabaseErrordatabaseError,booleancommitted,DataSnapshotcurrentData){// Transaction completedLog.d(TAG,"postTransaction:onComplete:"+databaseError);}});}
Using a transaction prevents star counts from being incorrect if multiple
users star the same post at the same time or the client had stale data. If the
transaction is rejected, the server returns the current value to the client,
which runs the transaction again with the updated value. This repeats until the
transaction is accepted or too many attempts have been made.
Atomic server-side increments
In the above use case we're writing two values to the database: the ID of
the user who stars/unstars the post, and the incremented star count. If we
already know that user is starring the post, we can use an atomic increment
operation instead of a transaction.
This code does not use a transaction operation, so it does not automatically get
re-run if there is a conflicting update. However, since the increment operation
happens directly on the database server, there is no chance of a conflict.
If you want to detect and reject application-specific conflicts, such as a user
starring a post that they already starred before, you should write custom
security rules for that use case.
Work with data offline
If a client loses its network connection, your app will continue functioning
correctly.
Every client connected to a Firebase database maintains its own internal version
of any data on which listeners are being used or which is flagged to be kept in
sync with the server. When data is read or written, this local version of the
data is used first. The Firebase client then synchronizes that data with the
remote database servers and with other clients on a "best-effort" basis.
As a result, all writes to the database trigger local events immediately, before
any interaction with the server. This means your app remains responsive
regardless of network latency or connectivity.
Once connectivity is reestablished, your app receives the appropriate set of
events so that the client syncs with the current server state, without having to
write any custom code.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-04 UTC."],[],[],null,[]]