How to Use Firebase Firestore

Gülce Şenyüz
adessoTurkey
Published in
7 min readJan 5, 2024

Firebase Firestore is a cloud-based NoSQL database used for storing and synchronizing data with a flexible structure, catering to multiple users. All classified data is stored as documents within collections. It’s straightforward and ready for immediate use.

We will cover how to set up Firestore in Android Studio, master data storage, retrieval, deletion and discover the unique advantages of Firebase Realtime Database in comparison. Let’s delve into further details.

How to set up Firebase in Android Studio

First, go to Tools located above, and click on Firebase.

Android studio
IDE: Android Studio

Then, navigate to the assistant menu and select Firestore. Click on “Connect to Firebase”. Once you’ve authenticated your Google account, add your project from the opened panel, unless you already have an existing one.

  • Name your project.
  • It’s optional to enable Google Analytics for your project.
  • Create your project.

Now, let’s connect your local project to the Firestore database.

From the left menu, choose “Cloud Firestore” and create a database.

Next, let’s start by creating a collection:

  • Add a collection (e.g., “Popular Movies”).
  • It will create a document ID automatically, or you can create one manually (in this example, the ID is the movie name).
  • Then, you can add your data under the document.
Database table example

To reach your database you should know:

1- https://firestore.googleapis.com/v1/projects/ is the base URL of the Firestore REST API.

2- projectName/databases/(default)/documents/collectionName is for the rest.

Let’s code!

You need permission to access the internet in the manifest file.

<uses-permission android:name="android.permission.INTERNET" />

Import other libraries into the project that you’re going to use, such as Retrofit and GSON libraries.

To include the Retrofit library in your project, navigate to the build.gradle file located in your app module. In Android Studio, you can find this file by expanding the Gradle Scripts folder.

After accessing the build.gradle file, simply add the required dependencies to the designated “dependencies” block.

 implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'

Create models for the JSON structure we’ll be parsing.

For my example:

data class Movie(
val adult: Boolean?,
@SerializedName("backdrop_path")
val backdropPath: String?,
val id: Int?,
@SerializedName("original_language")
val originalLanguage: String?,
@SerializedName("original_title")
val originalTitle: String?,
val overview: String?,
val popularity: Double?,
@SerializedName("poster_path")
val posterPath: String?,
@SerializedName("release_date")
val releaseDate: String?,
val title: String?,
val video: Boolean?,
@SerializedName("vote_average")
val voteAverage: Double?,
@SerializedName("vote_count")
val voteCount: Int?
)
data class MovieResponse(
val results: List<Movie>,
)

To simplify making network requests in Android by converting HTTP API calls into Java interfaces with annotations, we use Retrofit. First, we need a network module:


@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

@Singleton
@Provides
fun provideOkHttp(): OkHttpClient {
return OkHttpClient.Builder()
.build()
}

@Singleton
@Provides
@Named("loggingInterceptor")
fun provideLoggingInterceptor(): HttpLoggingInterceptor {
return HttpLoggingInterceptor().apply {
this.level = HttpLoggingInterceptor.Level.BODY
}
}

@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}

@Provides
fun provideApiClient(retrofit: Retrofit): MovieApi {
return retrofit.create(MovieApi::class.java)
}

}

After creating the Retrofit instance, we will set up our interface to retrieve the data. In this article, we’ll focus on the “getPopularMovies” method.

interface MovieApi {
@GET("movie/popular?")
suspend fun getPopularMovies(
@Query("api_key") api_key: String
): Response<MovieResponse>

@GET("search/movie")
suspend fun searchMovie(
@Query("api_key") key: String,
@Query("query") query: String,
@Query("page") page: Int
): Response<MovieResponse>
}

Next, we’ll create a class that handles making the request.

class MovieRepository @Inject constructor(private val apiService: MovieApi) {
suspend fun getPopularMovies(): NetworkResult<MovieResponse> {
val response = apiService.getPopularMovies(API_KEY)
return if (response.isSuccessful) {
val responseBody = response.body()
if (responseBody != null) {
NetworkResult.Success(responseBody)
} else {
NetworkResult.Error("Something went wrong")
}
} else {
NetworkResult.Error("Something went wrong")
}
}

}

For the remaining code, feel free to check out my GitHub repository with the full code here.

Write Cloud Firestore data

After requesting popular movies, the response is parsed into storeMovieData. For each movie in the response data, a movie object is created, then the “popular_movies” collection is accessed via the “db” database instance to store these movie objects. The document ID for each movie is set as the movie’s name. Using response listeners to proceed with actions could improve the flow and ensure that the database operations are executed after receiving and parsing the response data. This helps in handling asynchronous operations effectively.

But here, the current flow is simply displaying a toast message without any further action. :)

    val db = FirebaseFirestore.getInstance()
private fun storeMovieData(data: List<Movie>) {

for (movieItem in data) {
val movie = HashMap<String, Any>()
movie["originalTitle"] = movieItem.originalTitle.toString()
movie["overview"] = movieItem.overview.toString()
movie["popularity"] = movieItem.popularity.toString()
movie["releaseDate"] = movieItem.releaseDate.toString()

db.collection("popular_movies").document(movieItem.originalTitle.toString()).set(movie)
.addOnSuccessListener {
Toast.makeText(context, "successful", Toast.LENGTH_LONG).show()
}.addOnFailureListener {
Toast.makeText(context, "failure", Toast.LENGTH_LONG).show()
}
}

}
The Firestore Database will be displayed as shown above.
The Firestore Database will be displayed as shown above.

Note: If the “addOnFailureListener” executes, you should review the “Rules” in the Firestore Project panel.

Retrieve Cloud Firestore data

There are two methods for fetching data from the database.

  • The “get” method document or the collection reference.
 fun fetchPopularMovies() {
val docReference = db.collection("popular_movies")
docReference.get().addOnSuccessListener { document ->
document?.let {
// Log.d(TAG, "DocumentSnapshot data: " + document.documents)
var movieList= mutableListOf<Movie>()
for (item in document) {
val data = item.toObject(Movie::class.java)
movieList.add(data)
}
val response = MovieResponse(movieList)
_movieLiveData.value=response
}
}
.addOnFailureListener { e ->
Log.d("Failure", "get failed with ", e)
}
}

// To specify a document
// val docReference = db.collection("popular_movies").document("id")
  • The app has the capability to monitor real-time data changes by establishing live listeners (snapshot listeners) to both document and collection references.
 val docReference = db.collection("popular_movies")
docReference.addSnapshotListener(EventListener<QuerySnapshot> { snapshot, e ->
if (e != null) {
Log.d("Failure", e.toString())
return@EventListener
}
if (snapshot != null) {
Log.d("Success", snapshot.documents.toString())
} else {
Log.d("Success", "Current data: null")
}
})

Delete

If you remove all documents from a collection, the collection itself will be automatically deleted.

//To delete a spesific movie
fun deleteADocument(id: String) {
// val docReference = db.collection("popular_movies").document(id) -> id of a movie that will be deleted
val docReference = db.collection("popular_movies").document("Dragon Kingdom")
docReference.delete()
}

Deleting a field in a document:

   fun deleteAFieldInDocument(){
val docReference = db.collection("popular_movies").document("Dragon Kingdom")
val deleteField = HashMap<String, Any>()
//write the field that will be deleted
deleteField["popularity"] = FieldValue.delete()
docReference.update(deleteField)
}

Firebase Cloud Storage vs Firestore

Firebase Firestore and Firebase Realtime Database are both cloud-based NoSQL databases offered by Google’s Firebase platform, but they have differences in their data models, querying capabilities, scalability, and features.

Choosing between Firestore and the Realtime Database often depends on the specific needs of your application.

Firestore uses collections and documents to structure data. Collections contain documents, and each document contains fields with associated values. This allows for a more hierarchical structure and makes it easier to organize data, while the Realtime Database is based on a JSON tree structure. It lacks collections and documents, offering a more flat structure. Firestore provides more powerful and flexible querying options. It allows compound queries, sorting, and filtering based on multiple fields and also supports indexing for complex queries, whereas Realtime Database supports limited querying capabilities. Firestore offers offline data persistence, allowing users to access data even when offline. Changes made offline are synced automatically when the device reconnects to the internet.

Fundamental facts of Firestore

  1. Document-Based Model: Firestore stores data in documents, which are organized into collections. Each document contains fields with associated values.

2. Real-Time Updates: It offers real-time synchronization, allowing immediate updates to connected clients whenever data changes. This enables live, responsive applications.

3. Scalability and Performance: Firestore automatically scales based on usage and provides low-latency access to data.

4. Offline Support: It allows seamless offline access and automatically syncs data when the device reconnects.

5. Security Rules: Security rules let you control access to data based on authentication, ensuring data remains secure.

6. Supports Web and Mobile: Firestore is available for web, iOS, and Android platforms, allowing for cross-platform app development.

7. Integration with Firebase: It’s part of the Firebase suite, offering easy integration with other Firebase services like Authentication, Cloud Functions, Storage, and more.

8. Queries and Indexing: Supports powerful querying, allowing complex filtering, sorting, and indexing to efficiently retrieve data.

Long story short Firebase Firestore is a great cloud database for storing and syncing data easily. We learned how to use it in Android Studio, manage data, and see how it’s different from Firebase Realtime Database. Firestore works well for complex data, while Realtime Database is better for simpler real-time updates. Knowing these differences helps pick the best one for your app.

For the remaining code, feel free to check out my GitHub repository the full code here.

References: https://firebase.google.com/docs

--

--

Gülce Şenyüz
adessoTurkey

Android dev & Computer Eng grad passionate about crafting user-friendly mobile apps. Currently at Adesso. Sharing insights on Medium. Let's connect & innovate!