---
title: "Building an Android app with Jetpack Compose and Firebase"
description: "Learn how to build an Android app with Firebase and Jetpack Compose from scratch."
published: "2022-04-28"
last_updated: "2022-04-28"
authors:
  - "Marina Coelho (Developer Relations Engineer)"
categories:
  - "Android"
  - "Jetpack"
  - "Authentication"
  - "Firestore"
canonical_url: "https://firebase.blog/posts/2022/04/building-an-app-android-jetpack-compose-firebase"
---

# Building an Android app with Jetpack Compose and Firebase

*Published April 28, 2022 by Marina Coelho (Developer Relations Engineer)*

---
This is the first part of a series of articles that dive into creating a new Android application from scratch using [Jetpack Compose](https://developer.android.com/jetpack/compose) for the user interface and some other tools offered by Google, like [Firebase Authentication](https://firebase.google.com/docs/auth), [Crashlytics](https://firebase.google.com/docs/crashlytics), [Cloud Firestore](https://firebase.google.com/docs/firestore), [Performance Monitoring](https://firebase.google.com/docs/perf-mon), [Remote Config](https://firebase.google.com/docs/remote-config), [Firebase Extensions](https://firebase.google.com/docs/extensions), [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) and [Hilt](https://developer.android.com/training/dependency-injection/hilt-android).

In this first part, we will take a look at what you can do with this app, how it is structured and what technologies we will be covering in this series. [Part 2](https://firebase.blog/posts/2022/05/adding-firebase-auth-to-jetpack-compose-app) shows how to implement login and sign up flows using Firebase Authentication. [Part 3](https://firebase.blog/posts/2022/06/adding-crashlytics-to-jetpack-compose-app) covers how to add Firebase Crashlytics to handle errors. [Part 4](https://firebase.blog/posts/2022/07/adding-cloud-firestore-to-jetpack-compose-app) covers how to store data in the cloud using Cloud Firestore. [Part 5](https://firebase.blog/posts/2022/10/using-coroutines-flows-with-firebase-on-android) shows how to add [Kotlin Coroutines](https://kotlinlang.org/docs/multiplatform-mobile-concurrency-and-coroutines.html) and [Kotlin Flow](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/) to the application. [Part 6](https://firebase.blog/posts/2022/11/adding-performance-monitoring-to-jetpack-compose-app) covers how to improve the app’s quality with Firebase Performance Monitoring and [part 7](https://firebase.blog/posts/2022/11/adding-remote-config-to-jetpack-compose-app) shows how to control the behavior and appearance of the app remotely using Firebase Remote Config. [Part 8](https://firebase.blog/posts/2023/06/adding-firebase-extension-to-jetpack-compose-app) covers how to quickly and easily add new features to the app with Firebase Extensions and [Part 9](https://firebase.blog/posts/2023/08/adding-fcm-to-jetpack-compose-app) shows how to display push notifications to users with Firebase Cloud Messaging. [Part 10](https://firebase.blog/posts/2023/12/adding-complex-queries-to-jetpack-compose-app) teaches you how to build complex queries to filter, aggregate and order documents stored in Cloud Firestore.

### Make it So

#### What can I do with this application?

The app we are going to build here is called _Make It So_. It’s a simple to-do list application that allows the user to add and edit to-do items, add flags, priorities and due dates, and mark the tasks as completed. To give users the best possible experience, we want to enable them to start using the app without having to sign in first. To achieve this, we will use a Firebase Authentication feature called _Anonymous Authentication_, which we will cover in a later part of this series. This is how the app looks like:

[Video: Tasks and EditTask screens](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/video%2Fmake-it-so-start.mp4?alt=media&token=2506d773-0c2c-4c29-bc56-3cf988388b5b)

#### Why Jetpack Compose?

Compose is Android’s modern toolkit for building native UI - it’s intuitive and requires less code than writing `.xml` files and binding them to `Activities`, `Fragments` or `Views`. The first stable version of Compose was launched in 2021 and the [APIs are ready to be used in production](https://android-developers.googleblog.com/2021/07/jetpack-compose-announcement.html). It is essential that we understand how this new toolkit connects to other powerful tools like Firebase, since **this new way of making UI is [increasingly common among applications around the world](https://developer.android.com/jetpack/compose/adopt#what-developers-are-saying)** and therefore promises to be a tool that will be maintained in the long term.

Now that we already know what we can do with this application, how it looks like, and the motivation behind the toolkit we choose, let’s take a closer look at the architecture!

### Application Architecture

#### Overview

The architecture of this application follows the best practices recommended in the [Android documentation](https://developer.android.com/jetpack/guide#recommended-app-arch). We have the UI layer (that we call a `Screen`), responsible for showing data to users, and a data layer that is divided between `ViewModels` and `Services`, as we can see in the diagram below.

The data exchange between these layers must happen in the following way: the `Screen` sends events to the `ViewModels` (these events can be generated according to the screen lifecycle or through a user interaction, such as a click on a button). The `Screen` also needs to observe a state (commonly called a `UiState`) and update the data shown to the user as soon as this state changes.

The `ViewModel` is responsible for calling the methods of the `Services` according to the events received from the `Screen` and dealing with the updates published by these `Services`. In other words, the `ViewModel` is responsible for the business logic. The `Services` are responsible for calling the methods from the different Firebase APIs that we will use, and for returning the results to the `ViewModels`.

![Architecture overview diagram](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/posts%2Fmake-it-so-diagram-1.svg?alt=media&token=a710d301-934b-4931-89ef-1b2846ac2f9a)

#### Understanding Compose architecture

In Compose, each screen usually has one file for the `Screen` and another one for the `ViewModel`. Some screens might also require using a `UiState` class for managing how the data is exchanged between the `ViewModel` and the top level composable of your screen (like we saw in the diagram above). To learn more about Compose, check out the [official documentation](https://developer.android.com/jetpack/compose).

The `ViewModel` should contain all the business logic, which should be isolated from the _composable_ _functions_. Compose follows the [declarative UI model](https://developer.android.com/jetpack/compose/mental-model#paradigm) where the `Screen` observes the state of an object and, upon realizing that this state has changed, the framework automatically re-executes the composable functions, making the necessary changes on the UI. This process is called [recomposition](https://developer.android.com/jetpack/compose/mental-model#recomposition):

![Recomposition flow](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/posts%2Fmake-it-so-diagram-2.svg?alt=media&token=b894ca7e-2a41-43fa-b6e3-526e197a8220)

#### Accessing ViewModel in composable functions
In order to listen to these states, we need to provide the `ViewModel` to the composable functions. The best way to do this in Android apps is to use dependency injection: a technique to provide everything an object depends on to do everything it needs to do. This prevents objects (like services and `ViewModels`) from having to be recreated and passed between screens all the time.

There are many tools available to use dependency injection in Android. The one we are going to use in _Make it So_ is called [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) and is built on top of [Dagger](https://developer.android.com/training/dependency-injection/dagger-basics), another well known dependency injection library. We chose to use Hilt because it is very easy to learn and simple to use, and still has all the other benefits already known in Dagger (such as runtime performance, scalability and Android Studio support).

Once we’ve tagged a `ViewModel` as a [`@HiltViewModel`](https://developer.android.com/training/dependency-injection/hilt-jetpack#viewmodels), we can access it from anywhere in the code. All we need to do is specify what type of `ViewModel` we want, and call the method `hiltViewModel()`. In the code snippet below, we are calling this method from a composable function, specifying that the `ViewModel` is of type `EditTaskViewModel`:

```kt
@Composable
fun EditTaskScreen(
    taskId: String,
    popUpScreen: () -> Unit,
    viewModel: EditTaskViewModel = hiltViewModel()
) {
    val task by viewModel.task

    …
}
```

#### Accessing Services in ViewModels
Before we talk about how to access `Services`, let's remember how the data flow works in this application. We are following the unidirectional data flow, which means that the app has a one-way flow where the data can move in only one direction when being transferred between the different components of the software.

Composable functions also follow the unidirectional data flow pattern, and this is achieved by the fact that **composable functions shouldn't know anything about the services and the business logic**, they should just observe the state changes emitted by the `ViewModel`. That said, only the `ViewModels` will access the `Services`:

![Data flow between ViewModel and Service](https://firebasestorage.googleapis.com/v0/b/first-class-blog.appspot.com/o/posts%2Fmake-it-so-diagram-3.svg?alt=media&token=1e246387-1e50-4385-bef8-ed4d32babb91)

Once again, we will use Hilt to inject the dependencies into the `ViewModels`. As there is no specific method to inject `Services` (as there is the `hiltViewModel()` to inject `ViewModels` on the `Screens`), we need to add the `@Inject` annotation in the `constructor` of our class, and declare the dependencies that we want to inject inside of it. Through this annotation Hilt knows that it needs to provide all `Services` declared in the `ViewModel` `constructor`.

```kt
@HiltViewModel
class EditTaskViewModel @Inject constructor(
    private val logService: LogService,
    private val storageService: StorageService,
    private val accountService: AccountService
) : ViewModel() {
    var task = mutableStateOf(Task())
        private set

    …
}
```

The last thing is to configure the Hilt Module that will provide the service implementations to the `ViewModels`. Not to be confused with a Gradle module, a Hilt Module is a class (that must be annotated with `@Module`) that informs Hilt how to provide instances of certain types. In the example below, we are informing Hilt that whenever a class asks for an instance of the `StorageService`, it should be provided with the `StorageServiceImpl` class.

```kt
@Module
@InstallIn(ViewModelComponent::class)
abstract class ServiceModule {
    @Binds
    abstract fun provideStorageService(impl: StorageServiceImpl): StorageService

    …
}
```

#### Services interfaces and implementations
Another important decision for the project was to use service interfaces and to provide implementations for those interfaces. This makes it easier to maintain the project in the long run, as **you can quickly switch the implementation for the services methods in case it is needed**.

In the `ServiceModule` example above, we can see that we are injecting the `StorageServiceImpl` as the implementation for the `StorageService` interface. If the implementation for `StorageService `changes in the future (we might want to use a remote storage option rather than a local one, for example), we can easily and quickly reference the new implementation as a parameter for the method above, and all the `ViewModels` in the application will access the new one.

#### Initializing the composable functions
You may have noticed from the code snippets above that the composable functions of each screen receive some click listeners as parameters:

```kt
@Composable
fun TasksScreen(
    openAddTask: () -> Unit,
    openEditTask: (String) -> Unit,
    openSettings: () -> Unit,
    viewModel: TasksViewModel = hiltViewModel()
)
```

Now you might be asking: who does initialize these screens and pass these parameters forward? Who is responsible for navigating between screens? The answer is two classes: `MakeItSoApp` and `MakeItSoAppState`.

`MakeItSoApp` configures the basis for all screens: from the theme to be used, to the objects and classes that will be used on all screens, such as the navigation host and the `SnackbarManager` (which is used to display messages to the user). It's also in this file that we keep a reference to the general state of the application to observe changes within the main screen.

#### Configuring the Navigation Graph
Last (but not least!) we configure the application's navigation graph, which defines the possible screens to which we can navigate. The first three screens of our application are the `SplashScreen`, the `TasksScreen` (with the list of to-do items) and the `EditTaskScreen` (that receives the `taskId` to retrieve the task we want to edit). This is how we create the navigation graph for the app:

```kt
fun NavGraphBuilder.makeItSoGraph(appState: MakeItSoAppState) {
    composable(SPLASH_SCREEN) {
        SplashScreen(openAndPopUp = { route, popUp -> appState.navigateAndPopUp(route, popUp) })
    }

    composable(TASKS_SCREEN) {
        TasksScreen(openScreen = { route -> appState.navigate(route) })
    }

    composable(
        route = "$EDIT_TASK_SCREEN$TASK_ID_ARG",
        arguments = listOf(navArgument(TASK_ID) { defaultValue = TASK_DEFAULT_ID })
    ) {
        EditTaskScreen(
            popUpScreen = { appState.popUp() },
            taskId = it.arguments?.getString(TASK_ID) ?: TASK_DEFAULT_ID
        )
    }
}
```

**A navigation graph defines all possible routes into the application**, and what arguments each of those routes requires. Inside each route we declare which `Screen` is associated with it. Each of the screens will receive one or more callbacks as a parameter. For example: the `TasksScreen` receives the `openScreen` parameter. The moment the user clicks to edit a task, the composable function will send this click event to the `TasksViewModel`, along with the `openScreen` callback. Then the `TasksViewModel` will call the `openScreen` callback, passing the route of the new screen as a parameter (which in this case would be `EDIT_TASK_SCREEN`).

Inside the `openScreen` callback we run `appState.navigate(route)`, as seen in the snippet above. This is what the `navigate` method does:

```kt
fun navigate(route: String) {
    navController.navigate(route)
}
```

It basically uses the `NavController` (an Android class) to navigate to the next screen. The `NavController` will find the next screen to navigate by looking for it in the navigation graph. If the route does not exist in the graph, an exception is thrown. Otherwise the `NavController` adds the new screen to the backstack. Find out more about this navigation model in [this Github repository](https://github.com/android/compose-samples/tree/main/Jetsnack).

Now we already know how the app architecture looks like. We built the basis so that it is possible to create our app's functionalities. With this structure it was possible to create the UI of the initial screens and to configure the navigation between them.

### What’s next
In part 2 of this series,  we will take a closer look at the creation of the login and sign up flows.  We will also dive deeper  into how the `ViewModel` connects the business logic using composable functions.

You can clone the source code on the [Github repository](https://github.com/FirebaseExtended/make-it-so-android) - all the code you saw in this article is available in the `v1` folder. If you’re also interested in iOS/Mac development, the same application is available for these platforms. The iOS/Mac version uses SwiftUI for building the User Interface, which is very similar to how we build UIs in Compose. You can find the source code on [this GitHub repository](https://github.com/peterfriese/MakeItSo).

If you have any questions or suggestions, feel free to reach out to us on [the discussions page](https://github.com/FirebaseExtended/make-it-so-android/discussions/) of the repository.
