├── .github └── workflows │ └── android.yml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build-android-start ├── .gitignore ├── app │ ├── .gitignore │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── firebase │ │ │ └── codelab │ │ │ └── friendlychat │ │ │ └── MainActivityEspressoTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── firebase │ │ │ └── codelab │ │ │ └── friendlychat │ │ │ ├── FriendlyMessageAdapter.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MyButtonObserver.kt │ │ │ ├── MyOpenDocumentContract.kt │ │ │ ├── MyScrollToBottomObserver.kt │ │ │ ├── SignInActivity.kt │ │ │ └── model │ │ │ └── FriendlyMessage.kt │ │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-mdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-xhdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable │ │ ├── button_selector.xml │ │ ├── edit_text_shadow.xml │ │ ├── outline_add_photo_alternate_24.xml │ │ ├── outline_send_24.xml │ │ ├── outline_send_gray_24.xml │ │ ├── rounded_message_blue.xml │ │ └── rounded_message_gray.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_sign_in.xml │ │ ├── image_message.xml │ │ └── message.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts ├── build-android ├── .gitignore ├── app │ ├── .gitignore │ ├── app.iml │ ├── build.gradle.kts │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── google │ │ │ └── firebase │ │ │ └── codelab │ │ │ └── friendlychat │ │ │ └── MainActivityEspressoTest.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── google │ │ │ └── firebase │ │ │ └── codelab │ │ │ └── friendlychat │ │ │ ├── FriendlyMessageAdapter.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MyButtonObserver.kt │ │ │ ├── MyOpenDocumentContract.kt │ │ │ ├── MyScrollToBottomObserver.kt │ │ │ ├── SignInActivity.kt │ │ │ └── model │ │ │ └── FriendlyMessage.kt │ │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-mdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-xhdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_account_circle_black_36dp.png │ │ └── ic_add_black_24dp.png │ │ ├── drawable │ │ ├── button_selector.xml │ │ ├── edit_text_shadow.xml │ │ ├── outline_add_photo_alternate_24.xml │ │ ├── outline_send_24.xml │ │ ├── outline_send_gray_24.xml │ │ ├── rounded_message_blue.xml │ │ └── rounded_message_gray.xml │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── activity_sign_in.xml │ │ ├── image_message.xml │ │ └── message.xml │ │ ├── menu │ │ └── main_menu.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── network_security_config.xml ├── build.gradle.kts ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle.kts ├── build.gradle.kts ├── build.sh ├── database.rules.json ├── firebase.json ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── initial_messages.json ├── mock-google-services.json ├── settings.gradle.kts ├── steps ├── .gitignore ├── firebase-android │ ├── .firebaserc │ └── firebase.json ├── img │ ├── add-android-app.png │ ├── add-message.gif │ ├── android_studio_folder.png │ ├── emulators-auth-user.png │ ├── emulators-home.png │ ├── execute.png │ ├── import-data.gif │ └── screenshot.png └── index.lab.md └── storage.rules /.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 -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /build-android-start/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | local.properties 5 | build 6 | app/google-services.json 7 | -------------------------------------------------------------------------------- /build-android-start/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /build-android-start/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("com.google.gms.google-services") 5 | } 6 | 7 | android { 8 | namespace = "com.google.firebase.codelab.friendlychat" 9 | compileSdk = 36 10 | 11 | defaultConfig { 12 | applicationId = "com.google.firebase.codelab.friendlychat" 13 | minSdk = 23 14 | targetSdk = 36 15 | versionCode = 1 16 | versionName = "1.0" 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildFeatures { 21 | viewBinding = true 22 | } 23 | 24 | buildTypes { 25 | getByName("release") { 26 | isMinifyEnabled = false 27 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 28 | } 29 | } 30 | 31 | packaging { 32 | resources.excludes += "META-INF/LICENSE" 33 | resources.excludes += "META-INF/LICENSE-FIREBASE.txt" 34 | resources.excludes += "META-INF/NOTICE" 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility = JavaVersion.VERSION_17 39 | targetCompatibility = JavaVersion.VERSION_17 40 | } 41 | kotlinOptions { 42 | jvmTarget = "17" 43 | } 44 | 45 | lint { 46 | disable += "NotificationPermission" 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation("com.google.android.material:material:1.12.0") 52 | implementation("com.github.bumptech.glide:glide:4.16.0") 53 | implementation("androidx.appcompat:appcompat:1.7.1") 54 | implementation("androidx.legacy:legacy-support-v4:1.0.0") 55 | implementation("androidx.media:media:1.7.0") 56 | implementation("androidx.core:core-ktx:1.16.0") 57 | 58 | // Google 59 | implementation("com.google.android.gms:play-services-auth:21.3.0") 60 | 61 | // Firebase 62 | implementation(platform("com.google.firebase:firebase-bom:33.14.0")) 63 | implementation("com.google.firebase:firebase-database") 64 | implementation("com.google.firebase:firebase-storage") 65 | implementation("com.google.firebase:firebase-auth") 66 | 67 | // Firebase UI 68 | implementation("com.firebaseui:firebase-ui-auth:9.0.0") 69 | implementation("com.firebaseui:firebase-ui-database:9.0.0") 70 | 71 | // Testing dependencies 72 | testImplementation("junit:junit:4.13.2") 73 | androidTestImplementation("junit:junit:4.13.2") 74 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") 75 | androidTestImplementation("androidx.test:runner:1.6.2") 76 | androidTestImplementation("androidx.test:rules:1.6.1") 77 | } 78 | -------------------------------------------------------------------------------- /build-android-start/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ~/android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle.kts. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /build-android-start/app/src/androidTest/java/com/google/firebase/codelab/friendlychat/MainActivityEspressoTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.firebase.codelab.friendlychat; 18 | 19 | 20 | import androidx.test.filters.LargeTest; 21 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; 22 | import androidx.test.rule.ActivityTestRule; 23 | 24 | import org.junit.Rule; 25 | import org.junit.runner.RunWith; 26 | 27 | @RunWith(AndroidJUnit4ClassRunner.class) 28 | @LargeTest 29 | public class MainActivityEspressoTest { 30 | 31 | @Rule 32 | public ActivityTestRule mActivityRule = 33 | new ActivityTestRule<>(MainActivity.class); 34 | 35 | // Add instrumentation test here 36 | } 37 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/FriendlyMessageAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.graphics.Color 19 | import android.util.Log 20 | import android.view.LayoutInflater 21 | import android.view.ViewGroup 22 | import android.widget.ImageView 23 | import android.widget.TextView 24 | import androidx.recyclerview.widget.RecyclerView.* 25 | import com.bumptech.glide.Glide 26 | import com.bumptech.glide.load.resource.bitmap.CircleCrop 27 | import com.firebase.ui.database.FirebaseRecyclerAdapter 28 | import com.firebase.ui.database.FirebaseRecyclerOptions 29 | import com.google.firebase.codelab.friendlychat.MainActivity.Companion.ANONYMOUS 30 | import com.google.firebase.codelab.friendlychat.databinding.ImageMessageBinding 31 | import com.google.firebase.codelab.friendlychat.databinding.MessageBinding 32 | import com.google.firebase.codelab.friendlychat.model.FriendlyMessage 33 | import com.google.firebase.ktx.Firebase 34 | import com.google.firebase.storage.ktx.storage 35 | 36 | // The FirebaseRecyclerAdapter class and options come from the FirebaseUI library 37 | // See: https://github.com/firebase/FirebaseUI-Android 38 | class FriendlyMessageAdapter( 39 | private val options: FirebaseRecyclerOptions, 40 | private val currentUserName: String? 41 | ) : FirebaseRecyclerAdapter(options) { 42 | 43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 44 | val inflater = LayoutInflater.from(parent.context) 45 | return if (viewType == VIEW_TYPE_TEXT) { 46 | val view = inflater.inflate(R.layout.message, parent, false) 47 | val binding = MessageBinding.bind(view) 48 | MessageViewHolder(binding) 49 | } else { 50 | val view = inflater.inflate(R.layout.image_message, parent, false) 51 | val binding = ImageMessageBinding.bind(view) 52 | ImageMessageViewHolder(binding) 53 | } 54 | } 55 | 56 | override fun onBindViewHolder(holder: ViewHolder, position: Int, model: FriendlyMessage) { 57 | if (options.snapshots[position].text != null) { 58 | (holder as MessageViewHolder).bind(model) 59 | } else { 60 | (holder as ImageMessageViewHolder).bind(model) 61 | } 62 | } 63 | 64 | override fun getItemViewType(position: Int): Int { 65 | return if (options.snapshots[position].text != null) VIEW_TYPE_TEXT else VIEW_TYPE_IMAGE 66 | } 67 | 68 | inner class MessageViewHolder(private val binding: MessageBinding) : ViewHolder(binding.root) { 69 | fun bind(item: FriendlyMessage) { 70 | // TODO: implement 71 | } 72 | 73 | private fun setTextColor(userName: String?, textView: TextView) { 74 | if (userName != ANONYMOUS && currentUserName == userName && userName != null) { 75 | textView.setBackgroundResource(R.drawable.rounded_message_blue) 76 | textView.setTextColor(Color.WHITE) 77 | } else { 78 | textView.setBackgroundResource(R.drawable.rounded_message_gray) 79 | textView.setTextColor(Color.BLACK) 80 | } 81 | } 82 | } 83 | 84 | inner class ImageMessageViewHolder(private val binding: ImageMessageBinding) : 85 | ViewHolder(binding.root) { 86 | fun bind(item: FriendlyMessage) { 87 | // TODO: implement 88 | } 89 | } 90 | 91 | private fun loadImageIntoView(view: ImageView, url: String, isCircular: Boolean = true) { 92 | if (url.startsWith("gs://")) { 93 | val storageReference = Firebase.storage.getReferenceFromUrl(url) 94 | storageReference.downloadUrl 95 | .addOnSuccessListener { uri -> 96 | val downloadUrl = uri.toString() 97 | loadWithGlide(view, downloadUrl, isCircular) 98 | } 99 | .addOnFailureListener { e -> 100 | Log.w( 101 | TAG, 102 | "Getting download url was not successful.", 103 | e 104 | ) 105 | } 106 | } else { 107 | loadWithGlide(view, url, isCircular) 108 | } 109 | } 110 | 111 | private fun loadWithGlide(view: ImageView, url: String, isCircular: Boolean = true) { 112 | Glide.with(view.context).load(url).into(view) 113 | var requestBuilder = Glide.with(view.context).load(url) 114 | if (isCircular) { 115 | requestBuilder = requestBuilder.transform(CircleCrop()) 116 | } 117 | requestBuilder.into(view) 118 | } 119 | 120 | companion object { 121 | const val TAG = "MessageAdapter" 122 | const val VIEW_TYPE_TEXT = 1 123 | const val VIEW_TYPE_IMAGE = 2 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.net.Uri 19 | import android.os.Bundle 20 | import android.view.Menu 21 | import android.view.MenuItem 22 | import androidx.appcompat.app.AppCompatActivity 23 | import androidx.recyclerview.widget.LinearLayoutManager 24 | import com.firebase.ui.auth.AuthUI 25 | import com.firebase.ui.auth.AuthUI.IdpConfig.* 26 | import com.google.firebase.codelab.friendlychat.BuildConfig 27 | import com.google.firebase.codelab.friendlychat.databinding.ActivityMainBinding 28 | import com.google.firebase.storage.StorageReference 29 | 30 | class MainActivity : AppCompatActivity() { 31 | private lateinit var binding: ActivityMainBinding 32 | private lateinit var manager: LinearLayoutManager 33 | 34 | private val openDocument = registerForActivityResult(MyOpenDocumentContract()) { uri -> 35 | uri?.let { onImageSelected(it) } 36 | } 37 | 38 | // TODO: implement Firebase instance variables 39 | 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | super.onCreate(savedInstanceState) 42 | 43 | // This codelab uses View Binding 44 | // See: https://developer.android.com/topic/libraries/view-binding 45 | binding = ActivityMainBinding.inflate(layoutInflater) 46 | setContentView(binding.root) 47 | 48 | // Initialize Firebase Auth and check if the user is signed in 49 | // TODO: implement 50 | 51 | // Initialize Realtime Database and FirebaseRecyclerAdapter 52 | // TODO: implement 53 | 54 | // Disable the send button when there's no text in the input field 55 | // See MyButtonObserver for details 56 | binding.messageEditText.addTextChangedListener(MyButtonObserver(binding.sendButton)) 57 | 58 | // When the send button is clicked, send a text message 59 | // TODO: implement 60 | 61 | // When the image button is clicked, launch the image picker 62 | binding.addMessageImageView.setOnClickListener { 63 | openDocument.launch(arrayOf("image/*")) 64 | } 65 | } 66 | 67 | public override fun onStart() { 68 | super.onStart() 69 | // Check if user is signed in. 70 | // TODO: implement 71 | } 72 | 73 | public override fun onPause() { 74 | super.onPause() 75 | } 76 | 77 | public override fun onResume() { 78 | super.onResume() 79 | } 80 | 81 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 82 | val inflater = menuInflater 83 | inflater.inflate(R.menu.main_menu, menu) 84 | return true 85 | } 86 | 87 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 88 | return when (item.itemId) { 89 | R.id.sign_out_menu -> { 90 | signOut() 91 | true 92 | } 93 | else -> super.onOptionsItemSelected(item) 94 | } 95 | } 96 | 97 | private fun onImageSelected(uri: Uri) { 98 | // TODO: implement 99 | } 100 | 101 | private fun putImageInStorage(storageReference: StorageReference, uri: Uri, key: String?) { 102 | // Upload the image to Cloud Storage 103 | // TODO: implement 104 | } 105 | 106 | private fun signOut() { 107 | // TODO: implement 108 | } 109 | 110 | companion object { 111 | private const val TAG = "MainActivity" 112 | const val MESSAGES_CHILD = "messages" 113 | const val ANONYMOUS = "anonymous" 114 | private const val LOADING_IMAGE_URL = "https://www.google.com/images/spin-32.gif" 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/MyButtonObserver.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.text.Editable 19 | import android.text.TextWatcher 20 | import android.widget.Button 21 | import android.widget.ImageView 22 | 23 | class MyButtonObserver(private val button: ImageView) : TextWatcher { 24 | override fun onTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) { 25 | if (charSequence.toString().trim().isNotEmpty()) { 26 | button.isEnabled = true 27 | button.setImageResource(R.drawable.outline_send_24) 28 | } else { 29 | button.isEnabled = false 30 | button.setImageResource(R.drawable.outline_send_gray_24) 31 | } 32 | } 33 | 34 | override fun beforeTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) {} 35 | override fun afterTextChanged(editable: Editable) {} 36 | } 37 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/MyOpenDocumentContract.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.content.Context 19 | import android.content.Intent 20 | import androidx.activity.result.contract.ActivityResultContracts 21 | 22 | /** 23 | * See: 24 | * https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts.OpenDocument 25 | */ 26 | class MyOpenDocumentContract : ActivityResultContracts.OpenDocument() { 27 | 28 | override fun createIntent(context: Context, input: Array): Intent { 29 | val intent = super.createIntent(context, input) 30 | intent.addCategory(Intent.CATEGORY_OPENABLE) 31 | 32 | return intent; 33 | } 34 | } -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/MyScrollToBottomObserver.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import androidx.recyclerview.widget.LinearLayoutManager 19 | import androidx.recyclerview.widget.RecyclerView 20 | import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver 21 | 22 | class MyScrollToBottomObserver( 23 | private val recycler: RecyclerView, 24 | private val adapter: FriendlyMessageAdapter, 25 | private val manager: LinearLayoutManager 26 | ) : AdapterDataObserver() { 27 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 28 | super.onItemRangeInserted(positionStart, itemCount) 29 | val count = adapter.itemCount 30 | val lastVisiblePosition = manager.findLastCompletelyVisibleItemPosition() 31 | // If the recycler view is initially being loaded or the 32 | // user is at the bottom of the list, scroll to the bottom 33 | // of the list to show the newly added message. 34 | val loading = lastVisiblePosition == -1 35 | val atBottom = positionStart >= count - 1 && lastVisiblePosition == positionStart - 1 36 | if (loading || atBottom) { 37 | recycler.scrollToPosition(positionStart) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/SignInActivity.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.content.Intent 19 | import android.os.Bundle 20 | import android.util.Log 21 | import android.widget.Toast 22 | import androidx.appcompat.app.AppCompatActivity 23 | import com.firebase.ui.auth.AuthUI 24 | import com.firebase.ui.auth.IdpResponse 25 | import com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult 26 | import com.google.firebase.codelab.friendlychat.databinding.ActivitySignInBinding 27 | 28 | class SignInActivity : AppCompatActivity() { 29 | private lateinit var binding: ActivitySignInBinding 30 | 31 | // ActivityResultLauncher 32 | // TODO: Implement 33 | 34 | // Firebase instance variables 35 | // TODO: implement 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | 40 | // This codelab uses View Binding 41 | // See: https://developer.android.com/topic/libraries/view-binding 42 | binding = ActivitySignInBinding.inflate(layoutInflater) 43 | setContentView(binding.root) 44 | 45 | // Initialize FirebaseAuth 46 | // TODO: implement 47 | } 48 | 49 | public override fun onStart() { 50 | super.onStart() 51 | // TODO: Implement 52 | } 53 | 54 | private fun signIn() { 55 | // TODO: implement 56 | } 57 | 58 | private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) { 59 | // TODO: implement 60 | } 61 | 62 | private fun goToMainActivity() { 63 | startActivity(Intent(this, MainActivity::class.java)) 64 | finish() 65 | } 66 | 67 | companion object { 68 | private const val TAG = "SignInActivity" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/java/com/google/firebase/codelab/friendlychat/model/FriendlyMessage.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat.model 17 | 18 | data class FriendlyMessage( 19 | val text: String? = null, 20 | val name: String? = null, 21 | val photoUrl: String? = null, 22 | val imageUrl: String? = null, 23 | ) 24 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-hdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-hdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-mdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-mdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-xhdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-xhdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-xxhdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-xxhdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-xxxhdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-xxxhdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/edit_text_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/outline_add_photo_alternate_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/outline_send_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/outline_send_gray_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/rounded_message_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/drawable/rounded_message_gray.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 24 | 25 | 36 | 37 | 44 | 45 | 46 | 47 | 54 | 55 | 62 | 63 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/layout/activity_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/layout/image_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 32 | 33 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/layout/message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 32 | 33 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF9800 4 | #E65100 5 | #2E7D32 6 | #ffffff 7 | #ececec 8 | #dedede 9 | #b4b4b4 10 | #2962FF 11 | 12 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Friendly Chat 3 | Welcome! 4 | Sign Out 5 | Access to account information is required to sign in. 6 | Invite 7 | Please join me for a friendly chat 8 | Call to action 9 | Friendly Chat Invite 10 | Sign In 11 | Sign Up 12 | Fresh Config 13 | Cause Crash 14 | ca-app-pub-3940256099942544/6300978111 15 | 16 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /build-android-start/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 10.0.2.2 9 | 10 | -------------------------------------------------------------------------------- /build-android-start/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id("com.android.application") version "8.10.1" apply false 4 | id("com.android.library") version "8.10.1" apply false 5 | id("org.jetbrains.kotlin.android") version "2.1.21" apply false 6 | id("com.google.gms.google-services") version "4.4.2" apply false 7 | } 8 | 9 | allprojects { 10 | repositories { 11 | google() 12 | mavenLocal() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | tasks { 18 | register("clean", Delete::class) { 19 | delete(rootProject.buildDir) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /build-android-start/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.enableJetifier=true 21 | android.useAndroidX=true 22 | -------------------------------------------------------------------------------- /build-android-start/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android-start/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /build-android-start/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /build-android-start/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /build-android-start/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%" == "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%"=="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 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /build-android-start/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | include(":app") 10 | -------------------------------------------------------------------------------- /build-android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .idea 3 | .gradle 4 | local.properties 5 | build 6 | app/google-services.json 7 | -------------------------------------------------------------------------------- /build-android/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /build-android/app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /build-android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | id("com.google.gms.google-services") 5 | } 6 | 7 | android { 8 | namespace = "com.google.firebase.codelab.friendlychat" 9 | compileSdk = 36 10 | 11 | defaultConfig { 12 | applicationId = "com.google.firebase.codelab.friendlychat" 13 | minSdk = 23 14 | targetSdk = 36 15 | versionCode = 1 16 | versionName = "1.0" 17 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildFeatures { 21 | viewBinding = true 22 | } 23 | 24 | buildTypes { 25 | getByName("release") { 26 | isMinifyEnabled = false 27 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") 28 | } 29 | } 30 | 31 | packaging { 32 | resources.excludes += "META-INF/LICENSE" 33 | resources.excludes += "META-INF/LICENSE-FIREBASE.txt" 34 | resources.excludes += "META-INF/NOTICE" 35 | } 36 | 37 | compileOptions { 38 | sourceCompatibility = JavaVersion.VERSION_17 39 | targetCompatibility = JavaVersion.VERSION_17 40 | } 41 | kotlinOptions { 42 | jvmTarget = "17" 43 | } 44 | 45 | lint { 46 | disable += "NotificationPermission" 47 | } 48 | } 49 | 50 | dependencies { 51 | implementation("com.google.android.material:material:1.12.0") 52 | implementation("com.github.bumptech.glide:glide:4.16.0") 53 | implementation("androidx.appcompat:appcompat:1.7.1") 54 | implementation("androidx.legacy:legacy-support-v4:1.0.0") 55 | implementation("androidx.media:media:1.7.0") 56 | implementation("androidx.core:core-ktx:1.16.0") 57 | 58 | // Google 59 | implementation("com.google.android.gms:play-services-auth:21.3.0") 60 | 61 | // Firebase 62 | implementation(platform("com.google.firebase:firebase-bom:33.14.0")) 63 | implementation("com.google.firebase:firebase-database") 64 | implementation("com.google.firebase:firebase-storage") 65 | implementation("com.google.firebase:firebase-auth") 66 | 67 | // Firebase UI 68 | implementation("com.firebaseui:firebase-ui-auth:9.0.0") 69 | implementation("com.firebaseui:firebase-ui-database:9.0.0") 70 | 71 | // Testing dependencies 72 | testImplementation("junit:junit:4.13.2") 73 | androidTestImplementation("junit:junit:4.13.2") 74 | androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") 75 | androidTestImplementation("androidx.test:runner:1.6.2") 76 | androidTestImplementation("androidx.test:rules:1.6.1") 77 | } 78 | 79 | -------------------------------------------------------------------------------- /build-android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ~/android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle.kts. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /build-android/app/src/androidTest/java/com/google/firebase/codelab/friendlychat/MainActivityEspressoTest.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.google.firebase.codelab.friendlychat; 18 | 19 | import androidx.test.filters.LargeTest; 20 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner; 21 | import androidx.test.rule.ActivityTestRule; 22 | 23 | import org.junit.Rule; 24 | import org.junit.runner.RunWith; 25 | 26 | @RunWith(AndroidJUnit4ClassRunner.class) 27 | @LargeTest 28 | public class MainActivityEspressoTest { 29 | 30 | @Rule 31 | public ActivityTestRule mActivityRule = 32 | new ActivityTestRule<>(MainActivity.class); 33 | 34 | // Add instrumentation test here 35 | } 36 | -------------------------------------------------------------------------------- /build-android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/FriendlyMessageAdapter.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.graphics.Color 19 | import android.util.Log 20 | import android.view.LayoutInflater 21 | import android.view.ViewGroup 22 | import android.widget.ImageView 23 | import android.widget.TextView 24 | import androidx.recyclerview.widget.RecyclerView.* 25 | import com.bumptech.glide.Glide 26 | import com.bumptech.glide.load.resource.bitmap.CircleCrop 27 | import com.firebase.ui.database.FirebaseRecyclerAdapter 28 | import com.firebase.ui.database.FirebaseRecyclerOptions 29 | import com.google.firebase.codelab.friendlychat.MainActivity.Companion.ANONYMOUS 30 | import com.google.firebase.codelab.friendlychat.databinding.ImageMessageBinding 31 | import com.google.firebase.codelab.friendlychat.databinding.MessageBinding 32 | import com.google.firebase.codelab.friendlychat.model.FriendlyMessage 33 | import com.google.firebase.ktx.Firebase 34 | import com.google.firebase.storage.ktx.storage 35 | 36 | // The FirebaseRecyclerAdapter class and options come from the FirebaseUI library 37 | // See: https://github.com/firebase/FirebaseUI-Android 38 | class FriendlyMessageAdapter( 39 | private val options: FirebaseRecyclerOptions, 40 | private val currentUserName: String? 41 | ) : FirebaseRecyclerAdapter(options) { 42 | 43 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 44 | val inflater = LayoutInflater.from(parent.context) 45 | return if (viewType == VIEW_TYPE_TEXT) { 46 | val view = inflater.inflate(R.layout.message, parent, false) 47 | val binding = MessageBinding.bind(view) 48 | MessageViewHolder(binding) 49 | } else { 50 | val view = inflater.inflate(R.layout.image_message, parent, false) 51 | val binding = ImageMessageBinding.bind(view) 52 | ImageMessageViewHolder(binding) 53 | } 54 | } 55 | 56 | override fun onBindViewHolder(holder: ViewHolder, position: Int, model: FriendlyMessage) { 57 | if (options.snapshots[position].text != null) { 58 | (holder as MessageViewHolder).bind(model) 59 | } else { 60 | (holder as ImageMessageViewHolder).bind(model) 61 | } 62 | } 63 | 64 | override fun getItemViewType(position: Int): Int { 65 | return if (options.snapshots[position].text != null) VIEW_TYPE_TEXT else VIEW_TYPE_IMAGE 66 | } 67 | 68 | inner class MessageViewHolder(private val binding: MessageBinding) : ViewHolder(binding.root) { 69 | fun bind(item: FriendlyMessage) { 70 | binding.messageTextView.text = item.text 71 | setTextColor(item.name, binding.messageTextView) 72 | 73 | binding.messengerTextView.text = item.name ?: ANONYMOUS 74 | if (item.photoUrl != null) { 75 | loadImageIntoView(binding.messengerImageView, item.photoUrl) 76 | } else { 77 | binding.messengerImageView.setImageResource(R.drawable.ic_account_circle_black_36dp) 78 | } 79 | } 80 | 81 | private fun setTextColor(userName: String?, textView: TextView) { 82 | if (userName != ANONYMOUS && currentUserName == userName && userName != null) { 83 | textView.setBackgroundResource(R.drawable.rounded_message_blue) 84 | textView.setTextColor(Color.WHITE) 85 | } else { 86 | textView.setBackgroundResource(R.drawable.rounded_message_gray) 87 | textView.setTextColor(Color.BLACK) 88 | } 89 | } 90 | } 91 | 92 | inner class ImageMessageViewHolder(private val binding: ImageMessageBinding) : 93 | ViewHolder(binding.root) { 94 | fun bind(item: FriendlyMessage) { 95 | loadImageIntoView(binding.messageImageView, item.imageUrl!!, false) 96 | 97 | binding.messengerTextView.text = item.name ?: ANONYMOUS 98 | if (item.photoUrl != null) { 99 | loadImageIntoView(binding.messengerImageView, item.photoUrl) 100 | } else { 101 | binding.messengerImageView.setImageResource(R.drawable.ic_account_circle_black_36dp) 102 | } 103 | } 104 | } 105 | 106 | private fun loadImageIntoView(view: ImageView, url: String, isCircular: Boolean = true) { 107 | if (url.startsWith("gs://")) { 108 | val storageReference = Firebase.storage.getReferenceFromUrl(url) 109 | storageReference.downloadUrl 110 | .addOnSuccessListener { uri -> 111 | val downloadUrl = uri.toString() 112 | loadWithGlide(view, downloadUrl, isCircular) 113 | } 114 | .addOnFailureListener { e -> 115 | Log.w( 116 | TAG, 117 | "Getting download url was not successful.", 118 | e 119 | ) 120 | } 121 | } else { 122 | loadWithGlide(view, url, isCircular) 123 | } 124 | } 125 | 126 | private fun loadWithGlide(view: ImageView, url: String, isCircular: Boolean = true) { 127 | Glide.with(view.context).load(url).into(view) 128 | var requestBuilder = Glide.with(view.context).load(url) 129 | if (isCircular) { 130 | requestBuilder = requestBuilder.transform(CircleCrop()) 131 | } 132 | requestBuilder.into(view) 133 | } 134 | 135 | companion object { 136 | const val TAG = "MessageAdapter" 137 | const val VIEW_TYPE_TEXT = 1 138 | const val VIEW_TYPE_IMAGE = 2 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/MainActivity.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.content.Intent 19 | import android.net.Uri 20 | import android.os.Bundle 21 | import android.util.Log 22 | import android.view.Menu 23 | import android.view.MenuItem 24 | import android.widget.ProgressBar 25 | import androidx.appcompat.app.AppCompatActivity 26 | import androidx.recyclerview.widget.LinearLayoutManager 27 | import com.firebase.ui.auth.AuthUI 28 | import com.firebase.ui.database.FirebaseRecyclerOptions 29 | import com.google.firebase.auth.FirebaseAuth 30 | import com.google.firebase.auth.ktx.auth 31 | import com.google.firebase.codelab.friendlychat.databinding.ActivityMainBinding 32 | import com.google.firebase.codelab.friendlychat.model.FriendlyMessage 33 | import com.google.firebase.database.DatabaseReference 34 | import com.google.firebase.database.FirebaseDatabase 35 | import com.google.firebase.database.ktx.database 36 | import com.google.firebase.ktx.Firebase 37 | import com.google.firebase.storage.StorageReference 38 | import com.google.firebase.storage.ktx.storage 39 | 40 | 41 | class MainActivity : AppCompatActivity() { 42 | private lateinit var binding: ActivityMainBinding 43 | private lateinit var manager: LinearLayoutManager 44 | 45 | // Firebase instance variables 46 | private lateinit var auth: FirebaseAuth 47 | private lateinit var db: FirebaseDatabase 48 | private lateinit var adapter: FriendlyMessageAdapter 49 | 50 | private val openDocument = registerForActivityResult(MyOpenDocumentContract()) { uri -> 51 | uri?.let { onImageSelected(it) } 52 | } 53 | 54 | override fun onCreate(savedInstanceState: Bundle?) { 55 | super.onCreate(savedInstanceState) 56 | 57 | // This codelab uses View Binding 58 | // See: https://developer.android.com/topic/libraries/view-binding 59 | binding = ActivityMainBinding.inflate(layoutInflater) 60 | setContentView(binding.root) 61 | 62 | // When running in debug mode, connect to the Firebase Emulator Suite 63 | // "10.0.2.2" is a special value which allows the Android emulator to 64 | // connect to "localhost" on the host computer. The port values are 65 | // defined in the firebase.json file. 66 | if (BuildConfig.DEBUG) { 67 | Firebase.database.useEmulator("10.0.2.2", 9000) 68 | Firebase.auth.useEmulator("10.0.2.2", 9099) 69 | Firebase.storage.useEmulator("10.0.2.2", 9199) 70 | } 71 | 72 | // Initialize Firebase Auth and check if the user is signed in 73 | auth = Firebase.auth 74 | if (auth.currentUser == null) { 75 | // Not signed in, launch the Sign In activity 76 | startActivity(Intent(this, SignInActivity::class.java)) 77 | finish() 78 | return 79 | } 80 | 81 | // Initialize Realtime Database 82 | db = Firebase.database 83 | val messagesRef = db.reference.child(MESSAGES_CHILD) 84 | 85 | // The FirebaseRecyclerAdapter class and options come from the FirebaseUI library 86 | // See: https://github.com/firebase/FirebaseUI-Android 87 | val options = FirebaseRecyclerOptions.Builder() 88 | .setQuery(messagesRef, FriendlyMessage::class.java) 89 | .build() 90 | adapter = FriendlyMessageAdapter(options, getUserName()) 91 | binding.progressBar.visibility = ProgressBar.INVISIBLE 92 | manager = LinearLayoutManager(this) 93 | manager.stackFromEnd = true 94 | binding.messageRecyclerView.layoutManager = manager 95 | binding.messageRecyclerView.adapter = adapter 96 | 97 | // Scroll down when a new message arrives 98 | // See MyScrollToBottomObserver for details 99 | adapter.registerAdapterDataObserver( 100 | MyScrollToBottomObserver(binding.messageRecyclerView, adapter, manager) 101 | ) 102 | 103 | // Disable the send button when there's no text in the input field 104 | // See MyButtonObserver for details 105 | binding.messageEditText.addTextChangedListener(MyButtonObserver(binding.sendButton)) 106 | 107 | // When the send button is clicked, send a text message 108 | binding.sendButton.setOnClickListener { 109 | val friendlyMessage = FriendlyMessage( 110 | binding.messageEditText.text.toString(), 111 | getUserName(), 112 | getPhotoUrl(), 113 | null 114 | ) 115 | db.reference.child(MESSAGES_CHILD).push().setValue(friendlyMessage) 116 | binding.messageEditText.setText("") 117 | } 118 | 119 | // When the image button is clicked, launch the image picker 120 | binding.addMessageImageView.setOnClickListener { 121 | openDocument.launch(arrayOf("image/*")) 122 | } 123 | } 124 | 125 | public override fun onStart() { 126 | super.onStart() 127 | // Check if user is signed in. 128 | if (auth.currentUser == null) { 129 | // Not signed in, launch the Sign In activity 130 | startActivity(Intent(this, SignInActivity::class.java)) 131 | finish() 132 | return 133 | } 134 | } 135 | 136 | public override fun onPause() { 137 | adapter.stopListening() 138 | super.onPause() 139 | } 140 | 141 | public override fun onResume() { 142 | super.onResume() 143 | adapter.startListening() 144 | } 145 | 146 | override fun onCreateOptionsMenu(menu: Menu): Boolean { 147 | val inflater = menuInflater 148 | inflater.inflate(R.menu.main_menu, menu) 149 | return true 150 | } 151 | 152 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 153 | return when (item.itemId) { 154 | R.id.sign_out_menu -> { 155 | signOut() 156 | true 157 | } 158 | else -> super.onOptionsItemSelected(item) 159 | } 160 | } 161 | 162 | private fun onImageSelected(uri: Uri) { 163 | Log.d(TAG, "Uri: $uri") 164 | val user = auth.currentUser 165 | val tempMessage = FriendlyMessage(null, getUserName(), getPhotoUrl(), LOADING_IMAGE_URL) 166 | db.reference 167 | .child(MESSAGES_CHILD) 168 | .push() 169 | .setValue( 170 | tempMessage, 171 | DatabaseReference.CompletionListener { databaseError, databaseReference -> 172 | if (databaseError != null) { 173 | Log.w( 174 | TAG, "Unable to write message to database.", 175 | databaseError.toException() 176 | ) 177 | return@CompletionListener 178 | } 179 | 180 | // Build a StorageReference and then upload the file 181 | val key = databaseReference.key 182 | val storageReference = Firebase.storage 183 | .getReference(user!!.uid) 184 | .child(key!!) 185 | .child(uri.lastPathSegment!!) 186 | putImageInStorage(storageReference, uri, key) 187 | }) 188 | } 189 | 190 | private fun putImageInStorage(storageReference: StorageReference, uri: Uri, key: String?) { 191 | // First upload the image to Cloud Storage 192 | storageReference.putFile(uri) 193 | .addOnSuccessListener( 194 | this 195 | ) { taskSnapshot -> // After the image loads, get a public downloadUrl for the image 196 | // and add it to the message. 197 | taskSnapshot.metadata!!.reference!!.downloadUrl 198 | .addOnSuccessListener { uri -> 199 | val friendlyMessage = 200 | FriendlyMessage(null, getUserName(), getPhotoUrl(), uri.toString()) 201 | db.reference 202 | .child(MESSAGES_CHILD) 203 | .child(key!!) 204 | .setValue(friendlyMessage) 205 | } 206 | } 207 | .addOnFailureListener(this) { e -> 208 | Log.w( 209 | TAG, 210 | "Image upload task was unsuccessful.", 211 | e 212 | ) 213 | } 214 | } 215 | 216 | private fun signOut() { 217 | AuthUI.getInstance().signOut(this) 218 | startActivity(Intent(this, SignInActivity::class.java)) 219 | finish() 220 | } 221 | 222 | private fun getPhotoUrl(): String? { 223 | val user = auth.currentUser 224 | return user?.photoUrl?.toString() 225 | } 226 | 227 | private fun getUserName(): String? { 228 | val user = auth.currentUser 229 | return if (user != null) { 230 | user.displayName 231 | } else ANONYMOUS 232 | } 233 | 234 | companion object { 235 | private const val TAG = "MainActivity" 236 | const val MESSAGES_CHILD = "messages" 237 | const val ANONYMOUS = "anonymous" 238 | private const val LOADING_IMAGE_URL = "https://www.google.com/images/spin-32.gif" 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/MyButtonObserver.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.text.Editable 19 | import android.text.TextWatcher 20 | import android.widget.Button 21 | import android.widget.ImageView 22 | 23 | class MyButtonObserver(private val button: ImageView) : TextWatcher { 24 | override fun onTextChanged(charSequence: CharSequence, start: Int, count: Int, after: Int) { 25 | if (charSequence.toString().trim().isNotEmpty()) { 26 | button.isEnabled = true 27 | button.setImageResource(R.drawable.outline_send_24) 28 | } else { 29 | button.isEnabled = false 30 | button.setImageResource(R.drawable.outline_send_gray_24) 31 | } 32 | } 33 | 34 | override fun beforeTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) {} 35 | override fun afterTextChanged(editable: Editable) {} 36 | } 37 | -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/MyOpenDocumentContract.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.content.Context 19 | import android.content.Intent 20 | import androidx.activity.result.contract.ActivityResultContracts 21 | 22 | /** 23 | * See: 24 | * https://developer.android.com/reference/androidx/activity/result/contract/ActivityResultContracts.OpenDocument 25 | */ 26 | class MyOpenDocumentContract : ActivityResultContracts.OpenDocument() { 27 | 28 | override fun createIntent(context: Context, input: Array): Intent { 29 | val intent = super.createIntent(context, input) 30 | intent.addCategory(Intent.CATEGORY_OPENABLE) 31 | 32 | return intent; 33 | } 34 | } -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/MyScrollToBottomObserver.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import androidx.recyclerview.widget.LinearLayoutManager 19 | import androidx.recyclerview.widget.RecyclerView 20 | import androidx.recyclerview.widget.RecyclerView.AdapterDataObserver 21 | 22 | class MyScrollToBottomObserver( 23 | private val recycler: RecyclerView, 24 | private val adapter: FriendlyMessageAdapter, 25 | private val manager: LinearLayoutManager 26 | ) : AdapterDataObserver() { 27 | override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { 28 | super.onItemRangeInserted(positionStart, itemCount) 29 | val count = adapter.itemCount 30 | val lastVisiblePosition = manager.findLastCompletelyVisibleItemPosition() 31 | // If the recycler view is initially being loaded or the 32 | // user is at the bottom of the list, scroll to the bottom 33 | // of the list to show the newly added message. 34 | val loading = lastVisiblePosition == -1 35 | val atBottom = positionStart >= count - 1 && lastVisiblePosition == positionStart - 1 36 | if (loading || atBottom) { 37 | recycler.scrollToPosition(positionStart) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/SignInActivity.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat 17 | 18 | import android.content.Intent 19 | import android.os.Bundle 20 | import android.util.Log 21 | import android.widget.Toast 22 | import androidx.activity.result.ActivityResultLauncher 23 | import androidx.appcompat.app.AppCompatActivity 24 | import com.firebase.ui.auth.AuthUI 25 | import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract 26 | import com.firebase.ui.auth.IdpResponse 27 | import com.firebase.ui.auth.data.model.FirebaseAuthUIAuthenticationResult 28 | import com.google.firebase.auth.FirebaseAuth 29 | import com.google.firebase.auth.ktx.auth 30 | import com.google.firebase.codelab.friendlychat.databinding.ActivitySignInBinding 31 | import com.google.firebase.ktx.Firebase 32 | 33 | 34 | class SignInActivity : AppCompatActivity() { 35 | private lateinit var binding: ActivitySignInBinding 36 | 37 | private val signIn: ActivityResultLauncher = 38 | registerForActivityResult(FirebaseAuthUIActivityResultContract(), this::onSignInResult) 39 | 40 | override fun onCreate(savedInstanceState: Bundle?) { 41 | super.onCreate(savedInstanceState) 42 | 43 | // This codelab uses View Binding 44 | // See: https://developer.android.com/topic/libraries/view-binding 45 | binding = ActivitySignInBinding.inflate(layoutInflater) 46 | setContentView(binding.root) 47 | } 48 | 49 | public override fun onStart() { 50 | super.onStart() 51 | 52 | // If there is no signed in user, launch FirebaseUI 53 | // Otherwise head to MainActivity 54 | if (Firebase.auth.currentUser == null) { 55 | // Sign in with FirebaseUI, see docs for more details: 56 | // https://firebase.google.com/docs/auth/android/firebaseui 57 | val signInIntent = AuthUI.getInstance() 58 | .createSignInIntentBuilder() 59 | .setLogo(R.mipmap.ic_launcher) 60 | .setAvailableProviders(listOf( 61 | AuthUI.IdpConfig.EmailBuilder().build(), 62 | AuthUI.IdpConfig.GoogleBuilder().build(), 63 | )) 64 | .build() 65 | 66 | signIn.launch(signInIntent) 67 | } else { 68 | goToMainActivity() 69 | } 70 | } 71 | 72 | private fun onSignInResult(result: FirebaseAuthUIAuthenticationResult) { 73 | if (result.resultCode == RESULT_OK) { 74 | Log.d(TAG, "Sign in successful!") 75 | goToMainActivity() 76 | } else { 77 | Toast.makeText( 78 | this, 79 | "There was an error signing in", 80 | Toast.LENGTH_LONG).show() 81 | 82 | val response = result.idpResponse 83 | if (response == null) { 84 | Log.w(TAG, "Sign in canceled") 85 | } else { 86 | Log.w(TAG, "Sign in error", response.error) 87 | } 88 | } 89 | } 90 | 91 | private fun goToMainActivity() { 92 | startActivity(Intent(this, MainActivity::class.java)) 93 | finish() 94 | } 95 | 96 | companion object { 97 | private const val TAG = "SignInActivity" 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /build-android/app/src/main/java/com/google/firebase/codelab/friendlychat/model/FriendlyMessage.kt: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright Google Inc. All Rights Reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.google.firebase.codelab.friendlychat.model 17 | 18 | data class FriendlyMessage( 19 | val text: String? = null, 20 | val name: String? = null, 21 | val photoUrl: String? = null, 22 | val imageUrl: String? = null, 23 | ) 24 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-hdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-hdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-mdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-mdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-xhdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-xhdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-xxhdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-xxhdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-xxhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-xxxhdpi/ic_account_circle_black_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-xxxhdpi/ic_account_circle_black_36dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/button_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/edit_text_shadow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/outline_add_photo_alternate_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/outline_send_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/outline_send_gray_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/rounded_message_blue.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/drawable/rounded_message_gray.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 | 24 | 25 | 36 | 37 | 44 | 45 | 46 | 47 | 54 | 55 | 62 | 63 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/layout/activity_sign_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/layout/image_message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 32 | 33 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/layout/message.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 | 32 | 33 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/menu/main_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 8 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /build-android/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FF9800 4 | #E65100 5 | #2E7D32 6 | #ffffff 7 | #ececec 8 | #dedede 9 | #b4b4b4 10 | #2962FF 11 | 12 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Friendly Chat 3 | Welcome! 4 | Sign Out 5 | Access to account information is required to sign in. 6 | Invite 7 | Please join me for a friendly chat 8 | Call to action 9 | Friendly Chat Invite 10 | Sign In 11 | Sign Up 12 | Fresh Config 13 | Cause Crash 14 | ca-app-pub-3940256099942544/6300978111 15 | 16 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 19 | 20 | -------------------------------------------------------------------------------- /build-android/app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 10.0.2.2 9 | 10 | -------------------------------------------------------------------------------- /build-android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | id("com.android.application") version "8.10.1" apply false 4 | id("com.android.library") version "8.10.1" apply false 5 | id("org.jetbrains.kotlin.android") version "2.1.21" apply false 6 | id("com.google.gms.google-services") version "4.4.2" apply false 7 | } 8 | 9 | allprojects { 10 | repositories { 11 | google() 12 | mavenLocal() 13 | mavenCentral() 14 | } 15 | } 16 | 17 | tasks { 18 | register("clean", Delete::class) { 19 | delete(rootProject.buildDir) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /build-android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | android.enableJetifier=true 21 | android.useAndroidX=true 22 | -------------------------------------------------------------------------------- /build-android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/build-android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /build-android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /build-android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or 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 UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /build-android/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%" == "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%"=="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 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /build-android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | 9 | include(":app") 10 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask 2 | 3 | plugins { 4 | id("com.android.application") version "8.10.1" apply false 5 | id("com.android.library") version "8.10.1" apply false 6 | id("org.jetbrains.kotlin.android") version "2.1.21" apply false 7 | id("com.google.gms.google-services") version "4.4.2" apply false 8 | id("com.github.ben-manes.versions") version "0.52.0" apply true 9 | } 10 | 11 | allprojects { 12 | repositories { 13 | google() 14 | mavenLocal() 15 | mavenCentral() 16 | } 17 | } 18 | 19 | fun isNonStable(candidate: ModuleComponentIdentifier): Boolean { 20 | return listOf("alpha", "beta", "rc", "snapshot").any { keyword -> 21 | keyword in candidate.version.lowercase() 22 | } 23 | } 24 | 25 | tasks.withType { 26 | rejectVersionIf { 27 | isNonStable(candidate) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit on error 4 | set -e 5 | 6 | # Copy mock google-services file 7 | echo "Using mock google-services.json" 8 | cp mock-google-services.json build-android/app/google-services.json 9 | cp mock-google-services.json build-android-start/app/google-services.json 10 | 11 | # Build 12 | ./gradlew clean build 13 | -------------------------------------------------------------------------------- /database.rules.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "messages": { 4 | ".read": "auth.uid != null", 5 | ".write": "auth.uid != null" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.defaults.buildfeatures.buildconfig=true 9 | android.nonTransitiveRClass=false 10 | android.nonFinalResIds=false 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/firebase-android/firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": ".", 4 | "ignore": [ 5 | "firebase.json", 6 | "**/.*", 7 | "**/node_modules/**" 8 | ] 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /steps/img/add-android-app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/add-android-app.png -------------------------------------------------------------------------------- /steps/img/add-message.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/add-message.gif -------------------------------------------------------------------------------- /steps/img/android_studio_folder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/android_studio_folder.png -------------------------------------------------------------------------------- /steps/img/emulators-auth-user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/emulators-auth-user.png -------------------------------------------------------------------------------- /steps/img/emulators-home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/emulators-home.png -------------------------------------------------------------------------------- /steps/img/execute.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/execute.png -------------------------------------------------------------------------------- /steps/img/import-data.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/import-data.gif -------------------------------------------------------------------------------- /steps/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/firebase/codelab-friendlychat-android/56a46a81c26e8820290287dc39d6a5583a510a24/steps/img/screenshot.png -------------------------------------------------------------------------------- /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-ktx' 110 | implementation 'com.google.firebase:firebase-storage-ktx' 111 | implementation 'com.google.firebase:firebase-auth-ktx' 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`. -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------