├── .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 |
--------------------------------------------------------------------------------
/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 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 | generateDebugSources
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
--------------------------------------------------------------------------------
/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 |
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 | > *
**build-android-start**—Starting code that you build upon in this codelab.
57 | > *
**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 (
) 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 |
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** (
) 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** (
).
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
**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 |
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 |
--------------------------------------------------------------------------------