Skip to content

Commit 984eefa

Browse files
thatfiredevMarina Coelho
and
Marina Coelho
authored
Add Firebase Data Connect quickstart (#1671)
* Initial commit for dataconnect sample app * chore: add Firebase dependencies * add first schema * update with new schema * add kotlin serialization and androidx.lifecycle * firebase init * setup bottom navigation * feat: create movies screen * add firebase data connect logo * add top 10 movies and latest to the MoviesScreen * create GenresScreen * create GenreDetailScreen * create MovieDetailScreen * chore: bump gradle plugins * test: delete test modules * chore: add compose compiler * chore: move location from firebase.json to dataconnect.yaml * feat: create an auth screen * create user profile favorites list and sign out button * ellipsize text in Movie Tile * refactor: reuse MovieTile UI component * delete UserRepository * refactor: delete MovieRepository * refactor: delete the data package * feat: list actors in the movie details screen * refactor: movie review list to bottom of profile screen * feat: add reviews to movie detail screen * refactor: dateformat for reviews * feat: mark movies as watched and/or favorite * refactor: move AuthScreen into its own file * docs: add a README file * feat: support reviewing movies :) * feat: create actor details screen * refactor: make actor list reusable * refactor: make movies list reusable * refactor: make error and loading reusable components * refactor: make toggle button reusable * refactor: make actor details screen stateless * refactor: move UserReviews into its own dedicated file * refactor: turn actor tile into a card * docs: update the getting started * docs: update README.md * docs: update README.md * add data_seed.gql and useEmulator() * Update README.md Co-authored-by: Marina Coelho <[email protected]> * refactor: use Navigation type safety * refactor: delete extra uiState * refactor favoriteActor for readability * refactor: make error messages nullable and add default error message * refactor: ensure a user is logged in before marking as favorite * update GenreDetailScreen * chore: use v1beta * ci: remove Data Connect from build * chore: upgrade to 16.0.0-beta01 * ci: remove -i parameter * ci: output file * ci: overwrite instead of append * chore: agp 8.7.0 * testing * revert ci changes * ci: add python script to remove fdc * docs: update README.md * docs: remove deploy step --------- Co-authored-by: Marina Coelho <[email protected]>
1 parent 0892912 commit 984eefa

File tree

70 files changed

+4307
-0
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

70 files changed

+4307
-0
lines changed

.github/workflows/android.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ jobs:
1818
uses: gradle/gradle-build-action@v2
1919
- name: Check Snippets
2020
run: python scripts/checksnippets.py
21+
# TODO(thatfiredev): remove this once github.com/firebase/quickstart-android/issues/1672 is fixed
22+
- name: Remove Firebase Data Connect from CI
23+
run: python scripts/ci_remove_fdc.py
2124
- name: Copy mock google_services.json
2225
run: ./copy_mock_google_services_json.sh
2326
- name: Build with Gradle (Pull Request)

build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ plugins {
99
id("com.google.firebase.firebase-perf") version "1.4.2" apply false
1010
id("androidx.navigation.safeargs") version "2.8.1" apply false
1111
id("com.github.ben-manes.versions") version "0.51.0" apply true
12+
id("org.jetbrains.kotlin.plugin.compose") version "2.0.20" apply false
1213
}
1314

1415
allprojects {

copy_mock_google_services_json.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ cp mock-google-services.json auth/app/google-services.json
1212
cp mock-google-services.json config/app/google-services.json
1313
cp mock-google-services.json crash/app/google-services.json
1414
cp mock-google-services.json database/app/google-services.json
15+
cp mock-google-services.json dataconnect/app/google-services.json
1516
cp mock-google-services.json dynamiclinks/app/google-services.json
1617
cp mock-google-services.json firestore/app/google-services.json
1718
cp mock-google-services.json functions/app/google-services.json

dataconnect/.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
*.iml
2+
.gradle
3+
/local.properties
4+
/.idea/caches
5+
/.idea/libraries
6+
/.idea/modules.xml
7+
/.idea/workspace.xml
8+
/.idea/navEditor.xml
9+
/.idea/assetWizardSettings.xml
10+
.DS_Store
11+
/build
12+
/captures
13+
.externalNativeBuild
14+
.cxx
15+
local.properties
16+
.dataconnect/
17+
.firebaserc

dataconnect/README.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Firebase Data Connect Quickstart
2+
3+
## Introduction
4+
5+
This quickstart is a movie review app to demonstrate the use of Firebase Data Connect
6+
with a Cloud SQL database.
7+
For more information about Firebase Data Connect visit [the docs](https://firebase.google.com/docs/data-connect/).
8+
9+
## Getting Started
10+
11+
Follow these steps to get up and running with Firebase Data Connect. For more detailed instructions,
12+
check out the [official documentation](https://firebase.google.com/docs/data-connect/quickstart).
13+
14+
### 0. Prerequisites
15+
- Latest version of [Android Studio](https://developer.android.com/studio)
16+
- Latest version of [Visual Studio Code](https://code.visualstudio.com/)
17+
- The [Firebase Data Connect VS Code Extension](https://marketplace.visualstudio.com/items?itemName=GoogleCloudTools.firebase-dataconnect-vscode)
18+
19+
### 1. Connect to your Firebase project
20+
21+
1. If you haven't already, create a Firebase project.
22+
1. In the [Firebase console](https://console.firebase.google.com), click
23+
**Add project**, then follow the on-screen instructions.
24+
25+
2. Upgrade your project to the Blaze plan. This lets you create a Cloud SQL
26+
for PostgreSQL instance.
27+
28+
> Note: Though you set up billing in your Blaze upgrade, you won't be
29+
charged for usage of Firebase Data Connect or the
30+
[default Cloud SQL for PostgreSQL configuration](https://firebase.google.com/docs/data-connect/#pricing)
31+
during the preview.
32+
33+
3. Navigate to the [Data Connect section](https://console.firebase.google.com/u/0/project/_/dataconnect)
34+
of the Firebase console, click on the "Get Started" button and follow the setup workflow:
35+
- Select a location for your Cloud SQL for PostgreSQL database (this sample uses `us-central1`). If you choose a different location, you'll also need to change the `quickstart-android/dataconnect/dataconnect/dataconnect.yaml` file.
36+
- Select the option to create a new Cloud SQL instance and fill in the following fields:
37+
- Service ID: `dataconnect`
38+
- Cloud SQL Instance ID: `fdc-sql`
39+
- Database name: `fdcdb`
40+
4. Allow some time for the Cloud SQL instance to be provisioned. After it's provisioned, the instance
41+
can be managed in the [Cloud Console](https://console.cloud.google.com/sql).
42+
43+
5. If you haven’t already, add an Android app to your Firebase project, with the android package name `com.google.firebase.example.dataconnect`.
44+
Click **Download google-services.json** to obtain your Firebase Android config file.
45+
46+
### 2. Cloning the repository
47+
48+
1. Clone this repository to your local machine:
49+
```sh
50+
git clone https://github.com/firebase/quickstart-android.git
51+
```
52+
53+
2. Move the `google-services.json` config file (downloaded in the previous step) into the
54+
`quickstart-android/dataconnect/app/` directory.
55+
56+
### 3. Open in Visual Studio Code (VS Code)
57+
58+
1. Open the `quickstart-android/dataconnect` directory in VS Code.
59+
2. Click on the Firebase Data Connect icon on the VS Code sidebar to load the Extension.
60+
a. Sign in with your Google Account if you haven't already.
61+
3. Click on "Connect a Firebase project" and choose the project where you have set up Data Connect.
62+
4. Click on "Start Emulators" - this should generate the Kotlin SDK for you and start the emulators.
63+
64+
### 4. Populate the database
65+
In VS Code, open the `quickstart-android/dataconnect/dataconnect/data_seed.gql` file and click the
66+
`Run (local)` button at the top of the file.
67+
68+
If you’d like to confirm that the data was correctly inserted,
69+
open `quickstart-android/dataconnect/connectors/queries.gql` and run the `ListMovies` query.
70+
71+
### 5. Running the app
72+
73+
Press the Run button in Android Studio to run the sample app on your device.

dataconnect/app/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

dataconnect/app/build.gradle.kts

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
plugins {
2+
alias(libs.plugins.android.application)
3+
alias(libs.plugins.jetbrains.kotlin.android)
4+
alias(libs.plugins.kotlin.serialization)
5+
alias(libs.plugins.google.services)
6+
alias(libs.plugins.compose.compiler)
7+
}
8+
9+
android {
10+
namespace = "com.google.firebase.example.dataconnect"
11+
compileSdk = 34
12+
13+
defaultConfig {
14+
applicationId = "com.google.firebase.example.dataconnect"
15+
minSdk = 23
16+
targetSdk = 34
17+
versionCode = 1
18+
versionName = "1.0"
19+
20+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
21+
vectorDrawables {
22+
useSupportLibrary = true
23+
}
24+
}
25+
26+
buildTypes {
27+
release {
28+
isMinifyEnabled = false
29+
proguardFiles(
30+
getDefaultProguardFile("proguard-android-optimize.txt"),
31+
"proguard-rules.pro"
32+
)
33+
}
34+
}
35+
compileOptions {
36+
sourceCompatibility = JavaVersion.VERSION_1_8
37+
targetCompatibility = JavaVersion.VERSION_1_8
38+
}
39+
kotlinOptions {
40+
jvmTarget = "1.8"
41+
}
42+
buildFeatures {
43+
compose = true
44+
}
45+
composeOptions {
46+
kotlinCompilerExtensionVersion = "1.5.13"
47+
}
48+
packaging {
49+
resources {
50+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
51+
}
52+
}
53+
sourceSets.getByName("main") {
54+
java.srcDirs("build/generated/sources")
55+
}
56+
}
57+
58+
dependencies {
59+
60+
implementation(libs.androidx.core.ktx)
61+
implementation(libs.androidx.lifecycle.runtime.ktx)
62+
implementation(libs.androidx.lifecycle.viewmodel.compose)
63+
implementation(libs.androidx.activity.compose)
64+
implementation(platform(libs.androidx.compose.bom))
65+
implementation(libs.androidx.ui)
66+
implementation(libs.androidx.ui.graphics)
67+
implementation(libs.androidx.ui.tooling.preview)
68+
implementation(libs.androidx.material3)
69+
implementation(libs.compose.navigation)
70+
implementation(libs.androidx.lifecycle.runtime.compose.android)
71+
implementation(libs.coil.compose)
72+
73+
// Firebase dependencies
74+
implementation(libs.firebase.auth)
75+
implementation(libs.firebase.dataconnect)
76+
implementation(libs.kotlinx.serialization.core)
77+
78+
testImplementation(libs.junit)
79+
androidTestImplementation(libs.androidx.junit)
80+
androidTestImplementation(libs.androidx.espresso.core)
81+
androidTestImplementation(platform(libs.androidx.compose.bom))
82+
androidTestImplementation(libs.androidx.ui.test.junit4)
83+
debugImplementation(libs.androidx.ui.tooling)
84+
debugImplementation(libs.androidx.ui.test.manifest)
85+
}

dataconnect/app/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:tools="http://schemas.android.com/tools">
4+
5+
<uses-permission android:name="android.permission.INTERNET" />
6+
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
7+
8+
<application
9+
android:allowBackup="true"
10+
android:dataExtractionRules="@xml/data_extraction_rules"
11+
android:fullBackupContent="@xml/backup_rules"
12+
android:icon="@mipmap/ic_launcher"
13+
android:label="@string/app_name"
14+
android:roundIcon="@mipmap/ic_launcher_round"
15+
android:supportsRtl="true"
16+
android:theme="@style/Theme.FirebaseDataConnect"
17+
tools:targetApi="31">
18+
<activity
19+
android:name=".MainActivity"
20+
android:exported="true"
21+
android:label="@string/app_name"
22+
android:theme="@style/Theme.FirebaseDataConnect">
23+
<intent-filter>
24+
<action android:name="android.intent.action.MAIN" />
25+
26+
<category android:name="android.intent.category.LAUNCHER" />
27+
</intent-filter>
28+
</activity>
29+
</application>
30+
31+
</manifest>
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package com.google.firebase.example.dataconnect
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.activity.enableEdgeToEdge
7+
import androidx.compose.foundation.layout.consumeWindowInsets
8+
import androidx.compose.foundation.layout.fillMaxSize
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.material.icons.Icons
11+
import androidx.compose.material.icons.filled.Home
12+
import androidx.compose.material.icons.filled.Menu
13+
import androidx.compose.material.icons.filled.Person
14+
import androidx.compose.material3.Icon
15+
import androidx.compose.material3.NavigationBar
16+
import androidx.compose.material3.NavigationBarItem
17+
import androidx.compose.material3.Scaffold
18+
import androidx.compose.material3.Text
19+
import androidx.compose.runtime.getValue
20+
import androidx.compose.ui.Modifier
21+
import androidx.compose.ui.graphics.vector.ImageVector
22+
import androidx.compose.ui.res.stringResource
23+
import androidx.navigation.NavDestination.Companion.hasRoute
24+
import androidx.navigation.NavDestination.Companion.hierarchy
25+
import androidx.navigation.compose.NavHost
26+
import androidx.navigation.compose.composable
27+
import androidx.navigation.compose.currentBackStackEntryAsState
28+
import androidx.navigation.compose.rememberNavController
29+
import com.google.firebase.dataconnect.movies.MoviesConnector
30+
import com.google.firebase.dataconnect.movies.instance
31+
import com.google.firebase.example.dataconnect.feature.actordetail.ActorDetailRoute
32+
import com.google.firebase.example.dataconnect.feature.actordetail.ActorDetailScreen
33+
import com.google.firebase.example.dataconnect.feature.genredetail.GenreDetailRoute
34+
import com.google.firebase.example.dataconnect.feature.genredetail.GenreDetailScreen
35+
import com.google.firebase.example.dataconnect.feature.genres.GenresRoute
36+
import com.google.firebase.example.dataconnect.feature.genres.GenresScreen
37+
import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailRoute
38+
import com.google.firebase.example.dataconnect.feature.moviedetail.MovieDetailScreen
39+
import com.google.firebase.example.dataconnect.feature.movies.MoviesRoute
40+
import com.google.firebase.example.dataconnect.feature.movies.MoviesScreen
41+
import com.google.firebase.example.dataconnect.feature.profile.ProfileRoute
42+
import com.google.firebase.example.dataconnect.feature.profile.ProfileScreen
43+
import com.google.firebase.example.dataconnect.feature.search.searchScreen
44+
import com.google.firebase.example.dataconnect.ui.theme.FirebaseDataConnectTheme
45+
46+
data class TopLevelRoute<T : Any>(val labelResId: Int, val route: T, val icon: ImageVector)
47+
48+
val TOP_LEVEL_ROUTES = listOf(
49+
TopLevelRoute(R.string.label_movies, MoviesRoute, Icons.Filled.Home),
50+
TopLevelRoute(R.string.label_genres, GenresRoute, Icons.Filled.Menu),
51+
TopLevelRoute(R.string.label_profile, ProfileRoute, Icons.Filled.Person)
52+
)
53+
54+
class MainActivity : ComponentActivity() {
55+
override fun onCreate(savedInstanceState: Bundle?) {
56+
super.onCreate(savedInstanceState)
57+
enableEdgeToEdge()
58+
// Comment the line below to use a production environment instead
59+
MoviesConnector.instance.dataConnect.useEmulator("10.0.2.2", 9399)
60+
setContent {
61+
FirebaseDataConnectTheme {
62+
val navController = rememberNavController()
63+
Scaffold(
64+
modifier = Modifier.fillMaxSize(),
65+
bottomBar = {
66+
NavigationBar {
67+
val navBackStackEntry by navController.currentBackStackEntryAsState()
68+
val currentDestination = navBackStackEntry?.destination
69+
70+
TOP_LEVEL_ROUTES.forEach { topLevelRoute ->
71+
val label = stringResource(topLevelRoute.labelResId)
72+
NavigationBarItem(
73+
icon = { Icon(topLevelRoute.icon, contentDescription = label) },
74+
label = { Text(label) },
75+
selected = currentDestination?.hierarchy?.any {
76+
it.hasRoute(topLevelRoute.route::class)
77+
} == true,
78+
onClick = {
79+
navController.navigate(
80+
topLevelRoute.route,
81+
{ launchSingleTop = true }
82+
)
83+
}
84+
)
85+
}
86+
}
87+
}
88+
) { innerPadding ->
89+
NavHost(
90+
navController,
91+
startDestination = MoviesRoute,
92+
Modifier
93+
.padding(innerPadding)
94+
.consumeWindowInsets(innerPadding),
95+
) {
96+
composable<MoviesRoute>() {
97+
MoviesScreen(
98+
onMovieClicked = { movieId ->
99+
navController.navigate(
100+
route = MovieDetailRoute(movieId),
101+
builder = {
102+
launchSingleTop = true
103+
}
104+
)
105+
}
106+
)
107+
}
108+
composable<MovieDetailRoute> {
109+
MovieDetailScreen(
110+
onActorClicked = { actorId ->
111+
navController.navigate(
112+
ActorDetailRoute(actorId),
113+
{ launchSingleTop = true }
114+
)
115+
}
116+
)
117+
}
118+
composable<ActorDetailRoute>() { ActorDetailScreen() }
119+
composable<GenresRoute> {
120+
GenresScreen(onGenreClicked = { genre ->
121+
navController.navigate(
122+
GenreDetailRoute(genre),
123+
{ launchSingleTop = true }
124+
)
125+
})
126+
}
127+
composable<GenreDetailRoute> { GenreDetailScreen() }
128+
searchScreen()
129+
composable<ProfileRoute> { ProfileScreen() }
130+
}
131+
}
132+
}
133+
}
134+
}
135+
}

0 commit comments

Comments
 (0)