├── steps ├── .gitignore ├── firebase-android │ ├── .firebaserc │ └── firebase.json ├── img │ ├── execute.png │ ├── add-message.gif │ ├── import-data.gif │ ├── screenshot.png │ ├── add-android-app.png │ ├── emulators-home.png │ ├── android_studio_folder.png │ └── emulators-auth-user.png └── index.lab.md ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── database.rules.json ├── storage.rules ├── .gitignore ├── settings.gradle.kts ├── gradle.properties ├── .github └── workflows │ └── android.yml ├── initial_messages.json ├── firebase.json ├── README.md ├── mock-google-services.json ├── gradlew.bat ├── CONTRIBUTING.md ├── gradlew └── LICENSE /steps/.gitignore: -------------------------------------------------------------------------------- 1 | firebase-android 2 | !firebase-android/.firebaserc 3 | !firebase-android/firebase.json 4 | -------------------------------------------------------------------------------- /steps/firebase-android/.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "fir-codelabs-89252" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /steps/img/execute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/execute.png -------------------------------------------------------------------------------- /steps/img/add-message.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/add-message.gif -------------------------------------------------------------------------------- /steps/img/import-data.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/import-data.gif -------------------------------------------------------------------------------- /steps/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/screenshot.png -------------------------------------------------------------------------------- /steps/img/add-android-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/add-android-app.png -------------------------------------------------------------------------------- /steps/img/emulators-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/emulators-home.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /steps/img/android_studio_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/android_studio_folder.png -------------------------------------------------------------------------------- /steps/img/emulators-auth-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/HEAD/steps/img/emulators-auth-user.png -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "messages": { 4 | ".read": "auth.uid != null", 5 | ".write": "auth.uid != null" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /storage.rules: -------------------------------------------------------------------------------- 1 | rules_version = '2'; 2 | service firebase.storage { 3 | match /b/{bucket}/o { 4 | match /{allPaths=**} { 5 | allow read, write: if request.auth != null; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /steps/firebase-android/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": ".", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | gen/ 3 | .DS_Store 4 | Pods/ 5 | Podfile.lock 6 | xcuserdata/ 7 | *.xcworkspace/ 8 | GoogleService-Info.plist 9 | .gradle 10 | build 11 | *.iml 12 | .idea 13 | local.properties 14 | *-debug.log 15 | .firebaserc 16 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | include(":build-android:app", 10 | ":build-android-start:app" 11 | ) 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx6g -XX:MaxMetaspaceSize=6g -XX:ReservedCodeCacheSize=2g -Dfile.encoding=UTF-8 2 | org.gradle.parallel=true 3 | org.gradle.configureondemand=true 4 | org.gradle.caching=true 5 | 6 | android.enableJetifier=true 7 | android.useAndroidX=true 8 | android.nonTransitiveRClass=false 9 | android.nonFinalResIds=false 10 | -------------------------------------------------------------------------------- /.github/workflows/android.yml: -------------------------------------------------------------------------------- 1 | name: Android CI 2 | 3 | on: 4 | - pull_request 5 | - push 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v2 12 | - name: set up JDK 17 13 | uses: actions/setup-java@v1 14 | with: 15 | java-version: 17 16 | - name: Build with Gradle 17 | run: ./build.sh -------------------------------------------------------------------------------- /initial_messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "messages" : { 3 | "-K2ib4H77rj0LYewF7dP" : { 4 | "text" : "Hello", 5 | "name" : "anonymous" 6 | }, 7 | "-K2ib5JHRbbL0NrztUfO" : { 8 | "text" : "How are you", 9 | "name" : "anonymous" 10 | }, 11 | "-K2ib62mjHh34CAUbide" : { 12 | "text" : "I am fine", 13 | "name" : "anonymous" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "database": { 3 | "rules": "database.rules.json" 4 | }, 5 | "storage": { 6 | "rules": "storage.rules" 7 | }, 8 | "emulators": { 9 | "auth": { 10 | "port": 9099 11 | }, 12 | "database": { 13 | "port": 9000 14 | }, 15 | "storage": { 16 | "port": 9199 17 | }, 18 | "ui": { 19 | "enabled": true 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase Codelab: FriendlyChat 2 | 3 | [![Actions Status][gh-actions-badge]][gh-actions] 4 | 5 | This is the source code for the Firebase FriendlyChat codelabs. To get started open the codelab instructions: 6 | 7 | - [Build FriendlyChat Codelab](https://firebase.google.com/codelabs/firebase-android/) 8 | 9 | 10 | ## How to make contributions? 11 | Please read and follow the steps in the [CONTRIBUTING.md](CONTRIBUTING.md) 12 | 13 | 14 | ## License 15 | See [LICENSE](LICENSE) 16 | 17 | [gh-actions]: https://github.com/firebase/codelab-friendlychat-android/actions 18 | [gh-actions-badge]: https://github.com/firebase/codelab-friendlychat-android/workflows/Android%20CI/badge.svg 19 | -------------------------------------------------------------------------------- /mock-google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1234567890158", 4 | "firebase_url": "https://demo-friendlychat-android.firebaseio.com", 5 | "project_id": "demo-friendlychat-android", 6 | "storage_bucket": "demo-friendlychat-android.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1234567890158:android:399077dc333751e6a5f937", 12 | "android_client_info": { 13 | "package_name": "com.google.firebase.codelab.friendlychat" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1234567890158-gv99p92abcdef88ucgfobd02abcdef7m.apps.googleusercontent.com", 19 | "client_type": 3 20 | } 21 | ], 22 | "api_key": [ 23 | { 24 | "current_key": "AIzaSyA9kwAAxbxcrkibrLOuAA8YnAWqgur0AeA" 25 | } 26 | ], 27 | "services": { 28 | "appinvite_service": { 29 | "other_platform_oauth_client": [ 30 | { 31 | "client_id": "1234567890158-gv99p92abcdef88ucgfobd02abcdef7m.apps.googleusercontent.com", 32 | "client_type": 3 33 | } 34 | ] 35 | } 36 | } 37 | } 38 | ], 39 | "configuration_version": "1" 40 | } 41 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if %ERRORLEVEL% equ 0 goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if %ERRORLEVEL% equ 0 goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | set EXIT_CODE=%ERRORLEVEL% 84 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 85 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 86 | exit /b %EXIT_CODE% 87 | 88 | :mainEnd 89 | if "%OS%"=="Windows_NT" endlocal 90 | 91 | :omega 92 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to the Firebase FriendlyChat Codelab 2 | 3 | We'd love for you to contribute to our source code and to make the Firebase FriendlyChat Codelab even better than it is today! Here are the guidelines we'd like you to follow: 4 | 5 | - [Code of Conduct](#coc) 6 | - [Question or Problem?](#question) 7 | - [Issues and Bugs](#issue) 8 | - [Feature Requests](#feature) 9 | - [Submission Guidelines](#submit) 10 | - [Coding Rules](#rules) 11 | - [Signing the CLA](#cla) 12 | 13 | ## Code of Conduct 14 | 15 | As contributors and maintainers of the Firebase FriendlyChat Codelab project, we pledge to respect everyone who contributes by posting issues, updating documentation, submitting pull requests, providing feedback in comments, and any other activities. 16 | 17 | Communication through any of Firebase's channels (GitHub, StackOverflow, Google+, Twitter, etc.) must be constructive and never resort to personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. 18 | 19 | We promise to extend courtesy and respect to everyone involved in this project regardless of gender, gender identity, sexual orientation, disability, age, race, ethnicity, religion, or level of experience. We expect anyone contributing to the project to do the same. 20 | 21 | If any member of the community violates this code of conduct, the maintainers of the Firebase Android Quickstarts project may take action, removing issues, comments, and PRs or blocking accounts as deemed appropriate. 22 | 23 | If you are subject to or witness unacceptable behavior, or have any other concerns, please drop us a line at nivco@google.com. 24 | 25 | ## Got a Question or Problem? 26 | 27 | If you have questions about how to use the Firebase FriendlyChat Codelab, please direct these to [StackOverflow][stackoverflow] and use the `firebase` tag. We are also available on GitHub issues. 28 | 29 | If you feel that we're missing an important bit of documentation, feel free to 30 | file an issue so we can help. Here's an example to get you started: 31 | 32 | ``` 33 | What are you trying to do or find out more about? 34 | 35 | Where have you looked? 36 | 37 | Where did you expect to find this information? 38 | ``` 39 | 40 | ## Found an Issue? 41 | If you find a bug in the source code or a mistake in the documentation, you can help us by 42 | submitting an issue to our [GitHub Repository][github]. Even better you can submit a Pull Request 43 | with a fix. 44 | 45 | See [below](#submit) for some guidelines. 46 | 47 | ## Submission Guidelines 48 | 49 | ### Submitting an Issue 50 | Before you submit your issue search the archive, maybe your question was already answered. 51 | 52 | If your issue appears to be a bug, and hasn't been reported, open a new issue. 53 | Help us to maximize the effort we can spend fixing issues and adding new 54 | features, by not reporting duplicate issues. Providing the following information will increase the 55 | chances of your issue being dealt with quickly: 56 | 57 | * **Overview of the Issue** - if an error is being thrown a non-minified stack trace helps 58 | * **Motivation for or Use Case** - explain why this is a bug for you 59 | * **Browsers and Operating System** - is this a problem with all browsers or only IE9? 60 | * **Reproduce the Error** - provide a live example (using JSBin) or a unambiguous set of steps. 61 | * **Related Issues** - has a similar issue been reported before? 62 | * **Suggest a Fix** - if you can't fix the bug yourself, perhaps you can point to what might be 63 | causing the problem (line of code or commit) 64 | 65 | **If you get help, help others. Good karma rulez!** 66 | 67 | Here's a template to get you started: 68 | 69 | ``` 70 | Browser: 71 | Browser version: 72 | Operating system: 73 | Operating system version: 74 | 75 | What steps will reproduce the problem: 76 | 1. 77 | 2. 78 | 3. 79 | 80 | What is the expected result? 81 | 82 | What happens instead of that? 83 | 84 | Please provide any other information below, and attach a screenshot if possible. 85 | ``` 86 | 87 | ### Submitting a Pull Request 88 | Before you submit your pull request consider the following guidelines: 89 | 90 | * Search [GitHub](https://github.com/firebase/codelab-friendlychat/pulls) for an open or closed Pull Request 91 | that relates to your submission. You don't want to duplicate effort. 92 | * Please sign our [Contributor License Agreement (CLA)](#cla) before sending pull 93 | requests. We cannot accept code without this. 94 | * Make your changes in a new git branch: 95 | 96 | ```shell 97 | git checkout -b my-fix-branch master 98 | ``` 99 | 100 | * Create your patch, **including appropriate test cases**. 101 | * Follow our [Coding Rules](#rules). 102 | * Avoid checking in files that shouldn't be tracked (e.g `node_modules`, `gulp-cache`, `.tmp`, `.idea`). We recommend using a [global](#global-gitignore) gitignore for this. 103 | * Make sure **not** to include a recompiled version of the files found in `/css` and `/js` as part of your PR. We will generate these automatically. 104 | * Commit your changes using a descriptive commit message. 105 | 106 | ```shell 107 | git commit -a 108 | ``` 109 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files. 110 | 111 | * Build your changes locally to ensure all the tests pass: 112 | 113 | ```shell 114 | gulp 115 | ``` 116 | 117 | * Push your branch to GitHub: 118 | 119 | ```shell 120 | git push origin my-fix-branch 121 | ``` 122 | 123 | * In GitHub, send a pull request to `codelab-friendlychat:master`. 124 | * If we suggest changes then: 125 | * Make the required updates. 126 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 127 | 128 | ```shell 129 | git rebase master -i 130 | git push origin my-fix-branch -f 131 | ``` 132 | 133 | That's it! Thank you for your contribution! 134 | 135 | #### After your pull request is merged 136 | 137 | After your pull request is merged, you can safely delete your branch and pull the changes 138 | from the main (upstream) repository: 139 | 140 | * Delete the remote branch on GitHub either through the GitHub Android UI or your local shell as follows: 141 | 142 | ```shell 143 | git push origin --delete my-fix-branch 144 | ``` 145 | 146 | * Check out the master branch: 147 | 148 | ```shell 149 | git checkout master -f 150 | ``` 151 | 152 | * Delete the local branch: 153 | 154 | ```shell 155 | git branch -D my-fix-branch 156 | ``` 157 | 158 | * Update your master with the latest upstream version: 159 | 160 | ```shell 161 | git pull --ff upstream master 162 | ``` 163 | 164 | ## Signing the CLA 165 | 166 | Please sign our [Contributor License Agreement][google-cla] (CLA) before sending pull requests. For any code 167 | changes to be accepted, the CLA must be signed. It's a quick process, we promise! 168 | 169 | *This guide was inspired by the [AngularJS contribution guidelines](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md).* 170 | 171 | [github]: https://github.com/firebase/friendlychat 172 | [google-cla]: https://cla.developers.google.com 173 | [js-style-guide]: http://google.github.io/styleguide/javascriptguide.xml 174 | [py-style-guide]: http://google.github.io/styleguide/pyguide.html 175 | [jsbin]: http://jsbin.com/ 176 | [stackoverflow]: http://stackoverflow.com/questions/tagged/firebase 177 | [global-gitignore]: https://help.github.com/articles/ignoring-files/#create-a-global-gitignore 178 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 84 | 85 | APP_NAME="Gradle" 86 | APP_BASE_NAME=${0##*/} 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | MAX_FD=$( ulimit -H -n ) || 147 | warn "Could not query maximum file descriptor limit" 148 | esac 149 | case $MAX_FD in #( 150 | '' | soft) :;; #( 151 | *) 152 | ulimit -n "$MAX_FD" || 153 | warn "Could not set maximum file descriptor limit to $MAX_FD" 154 | esac 155 | fi 156 | 157 | # Collect all arguments for the java command, stacking in reverse order: 158 | # * args from the command line 159 | # * the main class name 160 | # * -classpath 161 | # * -D...appname settings 162 | # * --module-path (only if needed) 163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 164 | 165 | # For Cygwin or MSYS, switch paths to Windows format before running java 166 | if "$cygwin" || "$msys" ; then 167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 169 | 170 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 171 | 172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 173 | for arg do 174 | if 175 | case $arg in #( 176 | -*) false ;; # don't mess with options #( 177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 178 | [ -e "$t" ] ;; #( 179 | *) false ;; 180 | esac 181 | then 182 | arg=$( cygpath --path --ignore --mixed "$arg" ) 183 | fi 184 | # Roll the args list around exactly as many times as the number of 185 | # args, so each arg winds up back in the position where it started, but 186 | # possibly modified. 187 | # 188 | # NB: a `for` loop captures its iteration list before it begins, so 189 | # changing the positional parameters here affects neither the number of 190 | # iterations, nor the values presented in `arg`. 191 | shift # remove old arg 192 | set -- "$@" "$arg" # push replacement arg 193 | done 194 | fi 195 | 196 | # Collect all arguments for the java command; 197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 198 | # shell script including quotes and variable substitutions, so put them in 199 | # double quotes to make sure that they get re-expanded; and 200 | # * put everything else in single quotes, so that it's not re-expanded. 201 | 202 | set -- \ 203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 204 | -classpath "$CLASSPATH" \ 205 | org.gradle.wrapper.GradleWrapperMain \ 206 | "$@" 207 | 208 | # Stop when "xargs" is not available. 209 | if ! command -v xargs >/dev/null 2>&1 210 | then 211 | die "xargs is not available" 212 | fi 213 | 214 | # Use "xargs" to parse quoted args. 215 | # 216 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 217 | # 218 | # In Bash we could simply go: 219 | # 220 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 221 | # set -- "${ARGS[@]}" "$@" 222 | # 223 | # but POSIX shell has neither arrays nor command substitution, so instead we 224 | # post-process each arg (as a line of input to sed) to backslash-escape any 225 | # character that might be a shell metacharacter, then use eval to reverse 226 | # that process (while maintaining the separation between arguments), and wrap 227 | # the whole thing up as a single "set" statement. 228 | # 229 | # This will of course break if any of these variables contains a newline or 230 | # an unmatched quote. 231 | # 232 | 233 | eval "set -- $( 234 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 235 | xargs -n1 | 236 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 237 | tr '\n' ' ' 238 | )" '"$@"' 239 | 240 | exec "$JAVACMD" "$@" 241 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2015 Google Inc 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | 204 | All code in any directories or sub-directories that end with *.html or 205 | *.css is licensed under the Creative Commons Attribution International 206 | 4.0 License, which full text can be found here: 207 | https://creativecommons.org/licenses/by/4.0/legalcode. 208 | 209 | As an exception to this license, all html or css that is generated by 210 | the software at the direction of the user is copyright the user. The 211 | user has full ownership and control over such content, including 212 | whether and how they wish to license it. 213 | -------------------------------------------------------------------------------- /steps/index.lab.md: -------------------------------------------------------------------------------- 1 | --- 2 | id: firebase-android 3 | summary: In this codelab, you'll learn how to build an Android app with Firebase platform. 4 | status: [published] 5 | authors: arthurthompson 6 | categories: Firebase,Android 7 | tags: firebase-dev-summit-2016,firebase17,gdd17,io2016,io2017,io2018,io2019,kiosk,tag-firebase,web 8 | feedback link: https://github.com/firebase/codelab-friendlychat-android/issues 9 | 10 | --- 11 | 12 | # Firebase Android Codelab - Build Friendly Chat 13 | 14 | [Codelab Feedback](https://github.com/firebase/codelab-friendlychat-android/issues) 15 | 16 | 17 | ## Overview 18 | Duration: 05:00 19 | 20 | screenshot 21 | 22 | Image: Working Friendly Chat app. 23 | 24 | Welcome to the Friendly Chat codelab. In this codelab, you'll learn how to use the Firebase platform to create a chat app on Android. 25 | 26 | #### What you'll learn 27 | 28 | * How to use Firebase Authentication to allow users to sign in. 29 | * How to sync data using the Firebase Realtime Database. 30 | * How to store binary files in Cloud Storage for Firebase. 31 | * How to use the Firebase Local Emulator Suite to develop an Android app with Firebase. 32 | 33 | #### What you'll need 34 | 35 | * [Android Studio](https://developer.android.com/studio) Flamingo (2022.2.1) or later. 36 | * An [Android Emulator](https://developer.android.com/studio/run/emulator#install) with Android 5.0+. 37 | * Node.js version 14 or higher (to use the Emulator Suite). 38 | * Java 11 or higher. To install Java use these [instructions](https://java.com/en/download/help/download_options.xml); to check your version, run `java -version`. 39 | * Familiarity with the Kotlin programming language. 40 | 41 | ## Get the sample code 42 | Duration: 05:00 43 | 44 | ### Clone the repository 45 | 46 | Clone the GitHub repository from the command line: 47 | 48 | ```console 49 | $ git clone https://github.com/firebase/codelab-friendlychat-android 50 | ``` 51 | 52 | > aside positive 53 | > 54 | > The "friendlychat-android" repository contains two directories: 55 | > 56 | > * android_studio_folder**build-android-start**—Starting code that you build upon in this codelab. 57 | > * android_studio_folder**build-android**—Completed code for the finished sample app. 58 | > 59 | > **Note**: If you want to run the finished app, you have to create a Firebase project in the Firebase console, along with a Firebase Android App that has your app's package name and SHA1. For more information, see [Step #10](https://codelabs.developers.google.com/codelabs/firebase-android/#9) of this codelab. 60 | 61 | 62 | ### Import into Android Studio 63 | 64 | In Android Studio, select **File** > **Open**, then select the `build-android-start` directory ( android_studio_folder) from the directory where you downloaded the sample code. 65 | 66 | You should now have the `build-android-start` project open in Android Studio. If you see a warning about a `google-services.json` file missing, don't worry. It will be added in a later step. 67 | 68 | 69 | ### Check dependencies 70 | 71 | In this codelab all of the dependencies you will need have already been added for you, but it's important to understand how to add the Firebase SDK to your app: 72 | 73 | **build.gradle** 74 | 75 | ```groovy 76 | buildscript { 77 | // ... 78 | 79 | dependencies { 80 | classpath 'com.android.tools.build:gradle:7.2.2' 81 | 82 | // The google-services plugin is required to parse the google-services.json file 83 | classpath 'com.google.gms:google-services:4.3.13' 84 | } 85 | } 86 | ``` 87 | 88 | **app/build.gradle** 89 | 90 | ```groovy 91 | plugins { 92 | id 'com.android.application' 93 | id 'kotlin-android' 94 | id 'com.google.gms.google-services' 95 | } 96 | 97 | android { 98 | // ... 99 | } 100 | 101 | dependencies { 102 | // ... 103 | 104 | // Google Sign In SDK 105 | implementation 'com.google.android.gms:play-services-auth:20.2.0' 106 | 107 | // Firebase SDK 108 | implementation platform('com.google.firebase:firebase-bom:30.3.2') 109 | implementation 'com.google.firebase:firebase-database' 110 | implementation 'com.google.firebase:firebase-storage' 111 | implementation 'com.google.firebase:firebase-auth' 112 | 113 | // Firebase UI Library 114 | implementation 'com.firebaseui:firebase-ui-auth:8.0.1' 115 | implementation 'com.firebaseui:firebase-ui-database:8.0.1' 116 | } 117 | ``` 118 | 119 | ## Install the Firebase CLI 120 | Duration: 05:00 121 | 122 | In this codelab you'll use the [Firebase Emulator Suite](https://firebase.google.com/docs/emulator-suite) to locally emulate Firebase Auth, the Realtime Database and Cloud Storage. This provides a safe, fast, and no-cost local development environment to build your app. 123 | 124 | ### Install the Firebase CLI 125 | 126 | First you will need to install the [Firebase CLI](https://firebase.google.com/docs/cli). If you are using macOS or Linux, you can run the following cURL command: 127 | 128 | ```console 129 | curl -sL https://firebase.tools | bash 130 | ``` 131 | 132 | If you are using Windows, read the [installation instructions](https://firebase.google.com/docs/cli#install-cli-windows) to get a standalone binary or to install via `npm`. 133 | 134 | Once you've installed the CLI, running `firebase --version` should report a version of `9.0.0` or higher: 135 | 136 | ```console 137 | $ firebase --version 138 | 9.0.0 139 | ``` 140 | 141 | ### Log In 142 | 143 | Run `firebase login` to connect the CLI to your Google account. This will open a new browser window to complete the login process. Make sure to choose the same account you used when creating your Firebase project earlier. 144 | 145 | ## Connect to the Firebase Emulator Suite 146 | Duration: 02:00 147 | 148 | ### Start the emulators 149 | 150 | In your terminal, run the following command from the root of your local `codelab-friendlychat-android` directory: 151 | 152 | ```shell 153 | firebase emulators:start --project=demo-friendlychat-android 154 | ``` 155 | 156 | You should see some logs like this. The port values were defined in the `firebase.json` file, which was included in the cloned sample code. 157 | 158 | ```shell 159 | $ firebase emulators:start --project=demo-friendlychat-android 160 | i emulators: Starting emulators: auth, database, storage 161 | i emulators: Detected demo project ID "demo-friendlychat-android", emulated services will use a demo configuration and attempts to access non-emulated services for this project will fail. 162 | i database: Database Emulator logging to database-debug.log 163 | i ui: Emulator UI logging to ui-debug.log 164 | 165 | ┌─────────────────────────────────────────────────────────────┐ 166 | │ ✔ All emulators ready! It is now safe to connect your app. │ 167 | │ i View Emulator UI at http://localhost:4000 │ 168 | └─────────────────────────────────────────────────────────────┘ 169 | 170 | ┌────────────────┬────────────────┬────────────────────────────────┐ 171 | │ Emulator │ Host:Port │ View in Emulator UI │ 172 | ├────────────────┼────────────────┼────────────────────────────────┤ 173 | │ Authentication │ localhost:9099 │ http://localhost:4000/auth │ 174 | ├────────────────┼────────────────┼────────────────────────────────┤ 175 | │ Database │ localhost:9000 │ http://localhost:4000/database │ 176 | ├────────────────┼────────────────┼────────────────────────────────┤ 177 | │ Storage │ localhost:9199 │ http://localhost:4000/storage │ 178 | └────────────────┴────────────────┴────────────────────────────────┘ 179 | Emulator Hub running at localhost:4400 180 | Other reserved ports: 4500 181 | 182 | Issues? Report them at https://github.com/firebase/firebase-tools/issues and attach the *-debug.log files. 183 | ``` 184 | 185 | Navigate to [http://localhost:4000](http://localhost:4000) in your web 186 | browser to view the Firebase Emulator Suite UI: 187 | 188 | Emulator Suite UI home 189 | 190 | Leave the `emulators:start` command running for the rest of the codelab. 191 | 192 | ### Connect your app 193 | 194 | In Android Studio, open `MainActivity.kt`, then add the following code inside the `onCreate` method: 195 | 196 | ```kotlin 197 | // When running in debug mode, connect to the Firebase Emulator Suite. 198 | // "10.0.2.2" is a special IP address which allows the Android Emulator 199 | // to connect to "localhost" on the host computer. The port values (9xxx) 200 | // must match the values defined in the firebase.json file. 201 | if (BuildConfig.DEBUG) { 202 | Firebase.database.useEmulator("10.0.2.2", 9000) 203 | Firebase.auth.useEmulator("10.0.2.2", 9099) 204 | Firebase.storage.useEmulator("10.0.2.2", 9199) 205 | } 206 | ``` 207 | 208 | ## Run the starter app 209 | Duration: 03:00 210 | 211 | ### Add google-services.json 212 | 213 | In order for your Android app to connect to Firebase, you must add a `google-services.json` file inside the `app` folder of your Android project. For the purposes of this codelab, we've provided a mock JSON file which will allow you to connect to the Firebase Emulator Suite. 214 | 215 | Copy the `mock-google-services.json` file into the `build-android-start/app` folder as `google-services.json`: 216 | 217 | ```shell 218 | cp mock-google-services.json build-android-start/app/google-services.json 219 | ``` 220 | 221 | In the final step of this codelab, you'll learn how to create a real Firebase project and Firebase Android App so that you can replace this mock JSON file with your own configuration. 222 | 223 | ### Run the app 224 | 225 | Now that you've imported the project into Android Studio and added a Firebase configuration JSON file, you're ready to run the app for the first time. 226 | 227 | > aside negative 228 | > 229 | > Note: In order for your app to communicate with the Firebase Emulator Suite, it must be running on an Android Emulator, not a real Android device. This will allow the app to communicate with the Firebase Emulator Suite on `localhost`. Make sure that the Virtual Device definition selected is running Play Store services. 230 | 231 | 1. Start your Android Emulator. 232 | 2. In Android Studio, click **Run** ( execute) in the toolbar. 233 | 234 | The app should launch on your Android Emulator. At this point, you should see an empty message list, and sending and receiving messages will not work. In the next step of this codelab, you'll authenticate users so that they can use Friendly Chat. 235 | 236 | ## Enable Authentication 237 | Duration: 05:00 238 | 239 | This app will use Firebase Realtime Database to store all chat messages. Before we add data, though, we should make sure that the app is secure and that only authenticated users can post messages. In this step, we will enable Firebase Authentication and configure Realtime Database Security Rules. 240 | 241 | ### Add basic sign-in functionality 242 | 243 | Next we'll add some basic Firebase Authentication code to the app to detect users and implement a sign-in screen. 244 | 245 | 246 | #### Check for current user 247 | 248 | First add the following instance variable to the `MainActivity.kt` class: 249 | 250 | **MainActivity.kt** 251 | 252 | ``` 253 | // Firebase instance variables 254 | private lateinit var auth: FirebaseAuth 255 | ``` 256 | 257 | Now let's modify `MainActivity` to send the user to the sign-in screen whenever they open the app and are unauthenticated. Add the following to the `onCreate()` method **after** the `binding` is attached to the view: 258 | 259 | **MainActivity.kt** 260 | 261 | ``` 262 | // Initialize Firebase Auth and check if the user is signed in 263 | auth = Firebase.auth 264 | if (auth.currentUser == null) { 265 | // Not signed in, launch the Sign In activity 266 | startActivity(Intent(this, SignInActivity::class.java)) 267 | finish() 268 | return 269 | } 270 | ``` 271 | 272 | We also want to check if the user is signed in during `onStart()`: 273 | 274 | **MainActivity.kt** 275 | 276 | ``` 277 | public override fun onStart() { 278 | super.onStart() 279 | // Check if user is signed in. 280 | if (auth.currentUser == null) { 281 | // Not signed in, launch the Sign In activity 282 | startActivity(Intent(this, SignInActivity::class.java)) 283 | finish() 284 | return 285 | } 286 | } 287 | ``` 288 | 289 | Then implement the `getUserPhotoUrl()` and `getUserName()` methods to return the appropriate information about the currently authenticated Firebase user: 290 | 291 | **MainActivity.kt** 292 | 293 | ``` 294 | private fun getPhotoUrl(): String? { 295 | val user = auth.currentUser 296 | return user?.photoUrl?.toString() 297 | } 298 | 299 | private fun getUserName(): String? { 300 | val user = auth.currentUser 301 | return if (user != null) { 302 | user.displayName 303 | } else ANONYMOUS 304 | } 305 | ``` 306 | 307 | Then implement the `signOut()` method to handle the sign out button: 308 | 309 | **MainActivity.kt** 310 | 311 | ``` 312 | private fun signOut() { 313 | AuthUI.getInstance().signOut() 314 | startActivity(Intent(this, SignInActivity::class.java)) 315 | finish() 316 | } 317 | ``` 318 | 319 | Now we have all of the logic in place to send the user to the sign-in screen when necessary. Next we need to implement the sign-in screen to properly authenticate users. 320 | 321 | #### Implement the Sign-In screen 322 | 323 | Open the file `SignInActivity.kt`. Here a simple Sign-In button is used to initiate authentication. In this section, you will use FirebaseUI to implement the logic for sign in. 324 | 325 | Add an Auth instance variable in the `SignInActivity` class under the `// Firebase instance variables` comment: 326 | 327 | **SignInActivity.kt** 328 | 329 | ``` 330 | // Firebase instance variables 331 | private lateinit var auth: FirebaseAuth 332 | ``` 333 | 334 | Then, edit the `onCreate()` method to initialize Firebase in the same way you did in `MainActivity`: 335 | 336 | **SignInActivity.kt** 337 | 338 | ``` 339 | // Initialize FirebaseAuth 340 | auth = Firebase.auth 341 | ``` 342 | 343 | Add an `ActivityResultLauncher` field to `SignInActivity`: 344 | 345 | **SignInActivity.kt** 346 | 347 | ```kotlin 348 | // ADD THIS 349 | private val signIn: ActivityResultLauncher = 350 | registerForActivityResult(FirebaseAuthUIActivityResultContract(), this::onSignInResult) 351 | 352 | override fun onCreate(savedInstanceState: Bundle?) { 353 | // ... 354 | } 355 | ``` 356 | 357 | Next, edit the `onStart()` method to kick off the FirebaseUI sign in flow: 358 | 359 | **SignInActivity.kt** 360 | 361 | ```kotlin 362 | public override fun onStart() { 363 | super.onStart() 364 | 365 | // If there is no signed in user, launch FirebaseUI 366 | // Otherwise head to MainActivity 367 | if (Firebase.auth.currentUser == null) { 368 | // Sign in with FirebaseUI, see docs for more details: 369 | // https://firebase.google.com/docs/auth/android/firebaseui 370 | val signInIntent = AuthUI.getInstance() 371 | .createSignInIntentBuilder() 372 | .setLogo(R.mipmap.ic_launcher) 373 | .setAvailableProviders(listOf( 374 | AuthUI.IdpConfig.EmailBuilder().build(), 375 | AuthUI.IdpConfig.GoogleBuilder().build(), 376 | )) 377 | .build() 378 | 379 | signIn.launch(signInIntent) 380 | } else { 381 | goToMainActivity() 382 | } 383 | } 384 | ``` 385 | 386 | Next, implement the `onSignInResult` method to handle the sign in result. If the result of the signin was successful, continue to `MainActivity`: 387 | 388 | **SignInActivity.kt** 389 | 390 | ```kotlin 391 | private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) { 392 | if (result.resultCode == RESULT_OK) { 393 | Log.d(TAG, "Sign in successful!") 394 | goToMainActivity() 395 | } else { 396 | Toast.makeText( 397 | this, 398 | "There was an error signing in", 399 | Toast.LENGTH_LONG).show() 400 | 401 | val response = result.idpResponse 402 | if (response == null) { 403 | Log.w(TAG, "Sign in canceled") 404 | } else { 405 | Log.w(TAG, "Sign in error", response.error) 406 | } 407 | } 408 | } 409 | ``` 410 | 411 | That's it! You've implemented authentication with FirebaseUI in just a few method calls and without needing to manage any server-side configuration. 412 | 413 | #### Test your work 414 | 415 | Run the app on your Android Emulator. You should be immediately sent to the sign-in screen. Tap the **Sign in with email** button, then create an account. If everything is implemented correctly, you should be sent to the messaging screen. 416 | 417 | > aside negative 418 | > 419 | > Note: Google Sign-In will not work yet because you haven't registered your app with Firebase. You'll have a chance to do this at the end of the codelab. 420 | 421 | After signing in, open the Firebase Emulator Suite UI in your browser, then click the **Authentication** tab to see this first signed-in user account. 422 | 423 | 424 | 425 | ## Read messages 426 | Duration: 05:00 427 | 428 | In this step, we will add functionality to read and display messages stored in Realtime Database. 429 | 430 | ### Import sample messages 431 | 432 | 1. In the Firebase Emulator Suite UI, select the **Realtime Database** tab. 433 | 2. Drag and drop the `initial_messages.json` file from your local copy of the codelab repository into the data viewer. 434 | 435 | 436 | 437 | You should now have a few messages under the `messages` node of the database. 438 | 439 | ### Read data 440 | 441 | #### Synchronize messages 442 | 443 | In this section we add code that synchronizes newly added messages to the app UI by: 444 | 445 | * Initializing the Firebase Realtime Database and adding a listener to handle changes made to the data. 446 | * Updating the `RecyclerView` adapter so new messages will be shown. 447 | * Adding the Database instance variables with your other Firebase instance variables in the `MainActivity` class: 448 | 449 | #### MainActivity.kt 450 | 451 | ``` 452 | // Firebase instance variables 453 | // ... 454 | private lateinit var db: FirebaseDatabase 455 | private lateinit var adapter: FriendlyMessageAdapter 456 | ``` 457 | 458 | Modify your MainActivity's `onCreate()` method under the comment `// Initialize Realtime Database and FirebaseRecyclerAdapter` with the code defined below. This code adds all existing messages from Realtime Database and then listens for new child entries under the `messages` path in your Firebase Realtime Database. It adds a new element to the UI for each message: 459 | 460 | **MainActivity.kt** 461 | 462 | ```kt 463 | // Initialize Realtime Database 464 | db = Firebase.database 465 | val messagesRef = db.reference.child(MESSAGES_CHILD) 466 | 467 | // The FirebaseRecyclerAdapter class and options come from the FirebaseUI library 468 | // See: https://github.com/firebase/FirebaseUI-Android 469 | val options = FirebaseRecyclerOptions.Builder() 470 | .setQuery(messagesRef, FriendlyMessage::class.java) 471 | .build() 472 | adapter = FriendlyMessageAdapter(options, getUserName()) 473 | binding.progressBar.visibility = ProgressBar.INVISIBLE 474 | manager = LinearLayoutManager(this) 475 | manager.stackFromEnd = true 476 | binding.messageRecyclerView.layoutManager = manager 477 | binding.messageRecyclerView.adapter = adapter 478 | 479 | // Scroll down when a new message arrives 480 | // See MyScrollToBottomObserver for details 481 | adapter.registerAdapterDataObserver( 482 | MyScrollToBottomObserver(binding.messageRecyclerView, adapter, manager) 483 | ) 484 | ``` 485 | 486 | Next in the `FriendlyMessageAdapter.kt` class implement the `bind()` method within the inner class `MessageViewHolder()`: 487 | 488 | **FriendlyMessageAdapter.kt** 489 | 490 | ```kt 491 | inner class MessageViewHolder(private val binding: MessageBinding) : ViewHolder(binding.root) { 492 | fun bind(item: FriendlyMessage) { 493 | binding.messageTextView.text = item.text 494 | setTextColor(item.name, binding.messageTextView) 495 | 496 | binding.messengerTextView.text = if (item.name == null) ANONYMOUS else item.name 497 | if (item.photoUrl != null) { 498 | loadImageIntoView(binding.messengerImageView, item.photoUrl!!) 499 | } else { 500 | binding.messengerImageView.setImageResource(R.drawable.ic_account_circle_black_36dp) 501 | } 502 | } 503 | ... 504 | } 505 | ``` 506 | 507 | We also need to display messages that are images, so also implement the `bind()` method within the inner class `ImageMessageViewHolder()`: 508 | 509 | **FriendlyMessageAdapter.kt** 510 | 511 | ``` 512 | inner class ImageMessageViewHolder(private val binding: ImageMessageBinding) : 513 | ViewHolder(binding.root) { 514 | fun bind(item: FriendlyMessage) { 515 | loadImageIntoView(binding.messageImageView, item.imageUrl!!) 516 | 517 | binding.messengerTextView.text = if (item.name == null) ANONYMOUS else item.name 518 | if (item.photoUrl != null) { 519 | loadImageIntoView(binding.messengerImageView, item.photoUrl!!) 520 | } else { 521 | binding.messengerImageView.setImageResource(R.drawable.ic_account_circle_black_36dp) 522 | } 523 | } 524 | } 525 | ``` 526 | 527 | Finally, back in `MainActivity`, start and stop listening for updates from Firebase Realtime Database. 528 | Update the *`onPause()`* and *`onResume()`* methods in `MainActivity` as shown below: 529 | 530 | **MainActivity.kt** 531 | 532 | ```kt 533 | public override fun onPause() { 534 | adapter.stopListening() 535 | super.onPause() 536 | } 537 | 538 | public override fun onResume() { 539 | super.onResume() 540 | adapter.startListening() 541 | } 542 | ``` 543 | 544 | ### Test syncing messages 545 | 546 | 1. Click **Run** ( execute). 547 | 2. In the Emulator Suite UI, return to the **Realtime Database** tab, then manually add a new message. 548 | Confirm that the message shows up in your Android app: 549 | 550 | 551 | 552 | Congratulations, you just added a realtime database to your app! 553 | 554 | 555 | ## Send Messages 556 | Duration: 05:00 557 | 558 | ### Implement text message sending 559 | 560 | In this section, you will add the ability for app users to send text messages. The code snippet below listens for click events on the send button, creates a new `FriendlyMessage` object with the contents of the message field, and pushes the message to the database. The `push()` method adds an automatically generated ID to the pushed object's path. These IDs are sequential which ensures that the new messages will be added to the end of the list. 561 | 562 | Update the click listener of the send button in the `onCreate()` method in the `MainActivity` class. 563 | This code is at the bottom of the `onCreate()` method already. Update the `onClick()` body to match the code below: 564 | 565 | **MainActivity.kt** 566 | 567 | ```kt 568 | // Disable the send button when there's no text in the input field 569 | // See MyButtonObserver for details 570 | binding.messageEditText.addTextChangedListener(MyButtonObserver(binding.sendButton)) 571 | 572 | // When the send button is clicked, send a text message 573 | binding.sendButton.setOnClickListener { 574 | val friendlyMessage = FriendlyMessage( 575 | binding.messageEditText.text.toString(), 576 | getUserName(), 577 | getPhotoUrl(), 578 | null /* no image */ 579 | ) 580 | db.reference.child(MESSAGES_CHILD).push().setValue(friendlyMessage) 581 | binding.messageEditText.setText("") 582 | } 583 | ``` 584 | 585 | ### Implement image message sending 586 | 587 | In this section, you will add the ability for app users to send image messages. Creating an image message is done with these steps: 588 | 589 | * Select image 590 | * Handle image selection 591 | * Write temporary image message to the Realtime Database 592 | * Begin to upload selected image 593 | * Update image message URL to that of the uploaded image, once upload is complete 594 | 595 | #### Select Image 596 | 597 | To add images this codelab uses Cloud Storage for Firebase. Cloud Storage is a good place to store the binary data of your app. 598 | 599 | ##### Handle image selection and write temp message 600 | 601 | Once the user has selected an image, the image selection `Intent` is launched. This is already implemented in the code at the end of the `onCreate()` method. When finished it calls the `MainActivity`'s `onImageSelected()` method. Using the code snippet below, you will write a message with a temporary image url to the database indicating the image is being uploaded. 602 | 603 | **MainActivity.kt** 604 | 605 | ```kt 606 | private fun onImageSelected(uri: Uri) { 607 | Log.d(TAG, "Uri: $uri") 608 | val user = auth.currentUser 609 | val tempMessage = FriendlyMessage(null, getUserName(), getPhotoUrl(), LOADING_IMAGE_URL) 610 | db.reference 611 | .child(MESSAGES_CHILD) 612 | .push() 613 | .setValue( 614 | tempMessage, 615 | DatabaseReference.CompletionListener { databaseError, databaseReference -> 616 | if (databaseError != null) { 617 | Log.w( 618 | TAG, "Unable to write message to database.", 619 | databaseError.toException() 620 | ) 621 | return@CompletionListener 622 | } 623 | 624 | // Build a StorageReference and then upload the file 625 | val key = databaseReference.key 626 | val storageReference = Firebase.storage 627 | .getReference(user!!.uid) 628 | .child(key!!) 629 | .child(uri.lastPathSegment!!) 630 | putImageInStorage(storageReference, uri, key) 631 | }) 632 | } 633 | ``` 634 | 635 | #### Upload image and update message 636 | 637 | Add the method `putImageInStorage()` to `MainActivity`. It is called in `onImageSelected()` to initiate the upload of the selected image. Once the upload is complete you will update the message to use the appropriate image. 638 | 639 | #### MainActivity.kt 640 | 641 | ```kt 642 | private fun putImageInStorage(storageReference: StorageReference, uri: Uri, key: String?) { 643 | // First upload the image to Cloud Storage 644 | storageReference.putFile(uri) 645 | .addOnSuccessListener( 646 | this 647 | ) { taskSnapshot -> // After the image loads, get a public downloadUrl for the image 648 | // and add it to the message. 649 | taskSnapshot.metadata!!.reference!!.downloadUrl 650 | .addOnSuccessListener { uri -> 651 | val friendlyMessage = 652 | FriendlyMessage(null, getUserName(), getPhotoUrl(), uri.toString()) 653 | db.reference 654 | .child(MESSAGES_CHILD) 655 | .child(key!!) 656 | .setValue(friendlyMessage) 657 | } 658 | } 659 | .addOnFailureListener(this) { e -> 660 | Log.w( 661 | TAG, 662 | "Image upload task was unsuccessful.", 663 | e 664 | ) 665 | } 666 | } 667 | ``` 668 | 669 | #### Test sending messages 670 | 671 | 1. In Android Studio, click the execute**Run** button. 672 | 2. In your Android Emulator, enter a message, then tap the send button. The new message should be visible in the app UI and in the Firebase Emulator Suite UI. 673 | 3. In the Android Emulator, tap the "+" image to select an image from your device. The new message should be visible first with a placeholder image, and then with the selected image once the image upload is complete. The new message should also be visible in the Emulator Suite UI, specifically as an object in the Realtime Database tab and as a blob in the Storage tab. 674 | 675 | 676 | ## Congratulations! 677 | Duration: 01:00 678 | 679 | You just built a real-time chat application using Firebase! 680 | 681 | #### What you learned 682 | 683 | * Firebase Authentication 684 | * Firebase Realtime Database 685 | * Cloud Storage for Firebase 686 | 687 | Next, try using what you learned in this codelab to add Firebase to your own Android app! To learn more about Firebase, visit [firebase.google.com](https://firebase.google.com). 688 | 689 | If you want to learn how to set up a _real_ Firebase project and use _real_ Firebase resources (instead of a demo project and _only_ emulated resources), continue to the next step. 690 | 691 | > Note: Even after you set up a real Firebase project and _especially_ when you get started building a real app, we recommend using the Firebase Local Emulator Suite for development and testing. 692 | 693 | ## Optional: Create and set up a Firebase project 694 | Duration: 06:00 695 | 696 | In this step, you'll create a real Firebase project and a Firebase Android App to use with this codelab. You'll also add your app-specific Firebase configuration to your app. And finally, you'll set up real Firebase resources to use with your app. 697 | 698 | ### Create a Firebase project 699 | 700 | 1. In your browser, go to the [Firebase console](https://console.firebase.google.com). 701 | 2. Select **Add project**. 702 | 3. Select or enter a project name. You can use any name you want. 703 | 4. You do not need Google Analytics for this codelab, so you can skip enabling it for your project. 704 | 5. Click **Create Project**. When your project is ready, click **Continue**. 705 | 706 | ### Add Firebase to your Android project 707 | 708 | Before you begin this step, get the SHA1 hash of your app. Run the following command from your local `build-android-start` directory to determine the SHA1 of your debug key: 709 | 710 | ```console 711 | ./gradlew signingReport 712 | 713 | Store: /Users//.android/debug.keystore 714 | Alias: AndroidDebugKey 715 | MD5: A5:88:41:04:8F:06:59:6A:AE:33:76:87:AA:AD:19:23 716 | SHA1: A7:89:F5:06:A8:07:A1:22:EC:90:6A:A6:EA:C3:D4:8B:3A:30:AB:18 717 | SHA-256: 05:A2:2A:35:EE:F2:51:23:72:4D:72:67:A5:6A:8A:58:22:2C:00:A6:AB:F6:45:D5:A1:82:D8:90:A4:69:C8:FE 718 | Valid until: Wednesday, August 10, 2044 719 | ``` 720 | 721 | You should see some output like the above. The important line is the `SHA1` hash. If you're unable to find your SHA1 hash, see [this page](https://developers.google.com/android/guides/client-auth) for more information. 722 | 723 | Go back to the Firebase console, and follow these steps to register your Android project with your Firebase project: 724 | 725 | 1. From the overview screen of your new project, click the Android icon to launch the setup workflow: 726 | add android app 727 | 1. On the next screen, enter `com.google.firebase.codelab.friendlychat` as the package name for your app. 728 | 1. Click **Register App**, then click **Download google-services.json** to download your Firebase configuration file. 729 | 1. Copy the `google-services.json` file into the *`app`* directory of your Android project. 730 | 1. **Skip** the next steps shown in the console's setup workflow (they've already been done for you in the `build-android-start` project). 731 | 1. Make sure that all dependencies are available to your app by syncing your project with Gradle files. From the Android Studio toolbar, select **File** > **Sync Project with Gradle Files**. You may also need to run **Build/Clean Project** and **Build/Rebuild Project** for the config changes to take place. 732 | 733 | ### Configure Firebase Authentication 734 | 735 | Before your app can access the Firebase Authentication APIs on behalf of your users, you need to enable Firebase Authentication and the sign-in providers you want to use in your app. 736 | 737 | 1. In the [Firebase console](http://console.firebase.google.com), select **Authentication** from the left-side navigation panel. 738 | 1. Select the **Sign-in method** tab. 739 | 1. Click **Email/Password**, then toggle the switch to enabled (blue). 740 | 1. Click **Google**, then toggle the switch to enabled (blue) and set a project support email. 741 | 742 | If you get errors later in this codelab with the message "CONFIGURATION_NOT_FOUND", come back to this step and double check your work. 743 | 744 | ### Configure Realtime Database 745 | 746 | The app in this codelab stores chat messages in Firebase Realtime Database. In this section, we'll create a database and configure its security via a JSON configuration language called Firebase Security Rules. 747 | 748 | 1. In the [Firebase console](http://console.firebase.google.com), select **Realtime Database** from the left-side navigation panel. 749 | 1. Click **Create Database** to create a new Realtime Database instance. When prompted, select the `us-central1` region, then click **Next**. 750 | 1. When prompted about security rules, choose **locked mode**, then click **Enable**. 751 | 1. Once the database instance has been created, select the **Rules** tab, then update the rules configuration with the following: 752 | 753 | { 754 | "rules": { 755 | "messages": { 756 | ".read": "auth.uid != null", 757 | ".write": "auth.uid != null" 758 | } 759 | } 760 | } 761 | 762 | For more information on how Security Rules work (including documentation on the "auth" variable), see the [Realtime Database security documentation](https://firebase.google.com/docs/database/security/quickstart). 763 | 764 | ### Configure Cloud Storage for Firebase 765 | 766 | 1. In the [Firebase console](http://console.firebase.google.com), select **Storage** from the left-side navigation panel. 767 | 1. Click **Get Started** to enable Cloud Storage for your project. 768 | 1. Follow the steps in the dialog to set up your bucket, using the suggested defaults. 769 | 770 | ### Connect to Firebase resources 771 | 772 | In an earlier step of this codelab, you added the following to `MainActivity.kt`. This conditional block connected your Android project to the Firebase Emulator Suite. 773 | 774 | ```kt 775 | // REMOVE OR DISABLE THIS 776 | if (BuildConfig.DEBUG) { 777 | Firebase.database.useEmulator("10.0.2.2", 9000) 778 | Firebase.auth.useEmulator("10.0.2.2", 9099) 779 | Firebase.storage.useEmulator("10.0.2.2", 9199) 780 | } 781 | ``` 782 | 783 | If you want to connect your app to your new **_real_** Firebase project and its **_real_** Firebase resources, you can either remove this block or run your app in release mode so that `BuildConfig.DEBUG` is `false`. --------------------------------------------------------------------------------