Read and Write Data on Android

This document covers the basics of reading and writing Firebase data.

Firebase data is written to aFirebaseDatabase reference and retrieved byattaching an asynchronous listener to the reference. The listener is triggeredonce for the initial state of the data and again anytime the data changes.

Note: By default, read and write access to your database is restricted so onlyauthenticated users can read or write data. To get started without setting upAuthentication, you canconfigure your rules for public access. This doesmake your database open to anyone, even people not using your app, so be sureto restrict your database again when you set up authentication.

(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 datamodels, optimizing your security rules, or working to find the mostcost-effective way to interact with the back-end, being able to work locallywithout deploying live services can be a great idea.

ARealtime Database emulator is part of theLocal Emulator Suite, whichenables your app to interact with your emulated database content and config, aswell as optionally your emulated project resources (functions, other databases,and security rules).

Using theRealtime Database emulator involves just a few steps:

  1. Adding a line of code to your app's test config to connect to the emulator.
  2. From the root of your local project directory, runningfirebase emulators:start.
  3. Making calls from your app's prototype code using aRealtime Database platformSDK as usual, or using theRealtime Database REST API.

A detailedwalkthrough involvingRealtime Database andCloud Functions is available. You should also have a look at theLocal Emulator Suite introduction.

Get a DatabaseReference

To read or write data from the database, you need an instance ofDatabaseReference:

Kotlin

privatelateinitvardatabase:DatabaseReference// ...database=Firebase.database.reference

Java

privateDatabaseReferencemDatabase;// ...mDatabase=FirebaseDatabase.getInstance().getReference();

Write data

Basic write operations

For basic write operations, you can usesetValue() to save data to a specifiedreference, 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 defaultconstructor that takes no arguments and has public getters for the propertiesto be assigned.

If you use a Java object, the contents of your object are automatically mappedto child locations in a nested fashion. Using a Java object also typically makesyour code more readable and easier to maintain. For example, if you have anapp with a basic user profile, yourUser object 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.}

Java

@IgnoreExtraPropertiespublicclassUser{publicStringusername;publicStringemail;publicUser(){// Default constructor required for calls to DataSnapshot.getValue(User.class)}publicUser(Stringusername,Stringemail){this.username=username;this.email=email;}}

You can add a user withsetValue() as follows:

Kotlin

funwriteNewUser(userId:String,name:String,email:String){valuser=User(name,email)database.child("users").child(userId).setValue(user)}

Java

publicvoidwriteNewUser(StringuserId,Stringname,Stringemail){Useruser=newUser(name,email);mDatabase.child("users").child(userId).setValue(user);}

UsingsetValue() in this way overwrites data at the specified location,including any child nodes. However, you can still update a child withoutrewriting the entire object. If you want to allow users to update their profilesyou could update the username as follows:

Kotlin

database.child("users").child(userId).child("username").setValue(name)

Java

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

Read data

Read data with persistent listeners

To read data at a path and listen for changes, use theaddValueEventListener()method to add aValueEventListener to aDatabaseReference.

ListenerEvent callbackTypical usage
ValueEventListeneronDataChange()Read and listen for changes to the entire contents of a path.

You can use theonDataChange() method to read a static snapshot of thecontents at a given path, as they existed at the time of the event. This methodis triggered once when the listener is attached and again every time the data,including children, changes. The event callback is passed a snapshot containingall data at that location, including child data. If there is no data, thesnapshot will returnfalse when you callexists() andnull when you callgetValue() on it.

Important: TheonDataChange() method is called every time data is changed atthe specified database reference, including changes to children. To limit thesize of your snapshots, attach only at the highest level needed for watchingchanges. For example, attaching a listener to the root of your database isnot recommended.

The following example demonstrates a social blogging application retrieving thedetails 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)

Java

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 aDataSnapshot that contains the data at the specifiedlocation in the database at the time of the event. CallinggetValue() on asnapshot returns the Java object representation of the data. If no data existsat the location, callinggetValue() returnsnull.

In this example,ValueEventListener also defines theonCancelled() method thatis called if the read is canceled. For example, a read can be canceled if theclient doesn't have permission to read from a Firebase database location. Thismethod is passed aDatabaseError object indicating why the failure occurred.

Read data once

Read once using get()

The SDK is designed to manage interactions with database servers whether yourapp is online or offline.

Generally, you should use theValueEventListener techniques described aboveto read data to get notified of updates to the data from the backend. Thelistener techniques reduce your usage and billing, and are optimized togive 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 thedata from the database. If for any reasonget() is unable to return the servervalue, the client will probe the local storage cache and return an error if thevalue is still not found.

Unnecessary use ofget() can increase use of bandwidth and lead to loss ofperformance, which can be prevented by using a realtime listener as shown above.

Kotlin

mDatabase.child("users").child(userId).get().addOnSuccessListener{Log.i("firebase","Got value${it.value}")}.addOnFailureListener{Log.e("firebase","Error getting data",it)}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(newOnCompleteListener<DataSnapshot>(){@OverridepublicvoidonComplete(@NonNullTask<DataSnapshot>task){if(!task.isSuccessful()){Log.e("firebase","Error getting data",task.getException());}else{Log.d("firebase",String.valueOf(task.getResult().getValue()));}}});

Read once using a listener

In some cases you may want the value from the local cache to be returnedimmediately, instead of checking for an updated value on the server. In thosecases you can useaddListenerForSingleValueEvent to get the data from thelocal disk cache immediately.

This is useful for data that only needs to be loaded once and isn't expected tochange frequently or require active listening. For instance, the blogging appin the previous examples uses this method to load a user's profile when theybegin authoring a new post.

Updating or deleting data

Update specific fields

To simultaneously write to specific children of a node without overwriting otherchild nodes, use theupdateChildren() method.

When callingupdateChildren(), you can update lower-level child values byspecifying a path for the key. If data is stored in multiple locations to scalebetter, you can update all instances of that data usingdata fan-out. For example, asocial blogging app might have aPost class like this:

Kotlin

@IgnoreExtraPropertiesdataclassPost(varuid:String?="",varauthor:String?="",vartitle:String?="",varbody:String?="",varstarCount:Int=0,varstars:MutableMap<String,Boolean>=HashMap(),){@ExcludefuntoMap():Map<String,Any?>{returnmapOf("uid"touid,"author"toauthor,"title"totitle,"body"tobody,"starCount"tostarCount,"stars"tostars,)}}

Java

@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 activityfeed and the posting user's activity feed, the blogging application usescode 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)}

Java

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 forall users at/posts/$postid and simultaneously retrieve the key withgetKey(). The key can then be used to create a second entry in the user'sposts at/user-posts/$userid/$postid.

Using these paths, you can perform simultaneous updates to multiple locations inthe JSON tree with a single call toupdateChildren(), such as how this examplecreates the new post in both locations. Simultaneous updates made this wayare 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 acompletion listener. BothsetValue() andupdateChildren() take an optionalcompletion listener that is called when the write has been successfullycommitted to the database. If the call was unsuccessful, the listener ispassed an error object indicating why the failure occurred.

Kotlin

database.child("users").child(userId).setValue(user).addOnSuccessListener{// Write was successful!// ...}.addOnFailureListener{// Write failed// ...}

Java

mDatabase.child("users").child(userId).setValue(user).addOnSuccessListener(newOnSuccessListener<Void>(){@OverridepublicvoidonSuccess(VoidaVoid){// Write was successful!// ...}}).addOnFailureListener(newOnFailureListener(){@OverridepublicvoidonFailure(@NonNullExceptione){// Write failed// ...}});

Delete data

The simplest way to delete data is to callremoveValue() on a reference to thelocation of that data.

You can also delete by specifyingnull as the value for another writeoperation such assetValue() orupdateChildren(). You can use this techniquewithupdateChildren() to delete multiple children in a single API call.

Detach listeners

Callbacks are removed by calling theremoveEventListener() method on yourFirebase database reference.

If a listener has been added multiple times to a data location, it iscalled multiple times for each event, and you must detach it the same number oftimes to remove it completely.

CallingremoveEventListener() on a parent listener does notautomatically remove listeners registered on its child nodes;removeEventListener() must also be called on any child listenersto remove the callback.

Save data as transactions

When working with data that could be corrupted by concurrentmodifications, such as incremental counters, you can use atransaction operation.You give this operation two arguments: an update function and an optionalcompletion callback. The update function takes the current state of the data asan argument and returns the new desired state you would like to write. Ifanother client writes to the location before your new value is successfullywritten, your update function is called again with the new current value, andthe write is retried.

For instance, in the example social blogging app, you could allow users tostar and unstar posts and keep track of how many stars a post has receivedas 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!!)}})}

Java

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 multipleusers star the same post at the same time or the client had stale data. If thetransaction is rejected, the server returns the current value to the client,which runs the transaction again with the updated value. This repeats until thetransaction is accepted or too many attempts have been made.

Note: BecausedoTransaction() is called multiple times, it must be able tohandlenull data. Even if there is existing data in your remote database,it may not be locally cached when the transaction function is run, resultinginnull for the initial value.

Atomic server-side increments

In the above use case we're writing two values to the database: the ID ofthe user who stars/unstars the post, and the incremented star count. If wealready know that user is starring the post, we can use an atomic incrementoperation instead of a transaction.

Kotlin

privatefunonStarClicked(uid:String,key:String){valupdates:MutableMap<String,Any>=hashMapOf("posts/$key/stars/$uid"totrue,"posts/$key/starCount"toServerValue.increment(1),"user-posts/$uid/$key/stars/$uid"totrue,"user-posts/$uid/$key/starCount"toServerValue.increment(1),)database.updateChildren(updates)}

Java

privatevoidonStarClicked(Stringuid,Stringkey){Map<String,Object>updates=newHashMap<>();updates.put("posts/"+key+"/stars/"+uid,true);updates.put("posts/"+key+"/starCount",ServerValue.increment(1));updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid,true);updates.put("user-posts/"+uid+"/"+key+"/starCount",ServerValue.increment(1));mDatabase.updateChildren(updates);}

This code does not use a transaction operation, so it does not automatically getre-run if there is a conflicting update. However, since the increment operationhappens 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 userstarring a post that they already starred before, you should write customsecurity rules for that use case.

Work with data offline

If a client loses its network connection, your app will continue functioningcorrectly.

Every client connected to a Firebase database maintains its own internal versionof any data on which listeners are being used or which is flagged to be kept insync with the server. When data is read or written, this local version of thedata is used first. The Firebase client then synchronizes that data with theremote database servers and with other clients on a "best-effort" basis.

As a result, all writes to the database trigger local events immediately, beforeany interaction with the server. This means your app remains responsiveregardless of network latency or connectivity.

Once connectivity is reestablished, your app receives the appropriate set ofevents so that the client syncs with the current server state, without having towrite any custom code.

We'll talk more about offline behavior inLearn more about online and offline capabilities.

Next steps

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-02-18 UTC.