├── .gitignore
├── .idea
├── .gitignore
├── .name
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── compiler.xml
├── gradle.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── jarRepositories.xml
├── misc.xml
└── vcs.xml
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── build.gradle
├── database
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── rosariopfernandes
│ │ └── firebasecompose
│ │ └── database
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── io
│ │ └── github
│ │ └── rosariopfernandes
│ │ └── firebasecompose
│ │ └── database
│ │ ├── DatabaseListRefState.kt
│ │ ├── DatabaseRefState.kt
│ │ ├── RTDBData.kt
│ │ └── RTDBDataList.kt
│ └── test
│ └── java
│ └── io
│ └── github
│ └── rosariopfernandes
│ └── firebasecompose
│ └── database
│ └── ExampleUnitTest.kt
├── demo
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── rosariopfernandes
│ │ └── firebasecompose
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── io
│ │ │ └── github
│ │ │ └── rosariopfernandes
│ │ │ └── firebasecompose
│ │ │ ├── model
│ │ │ └── Snack.kt
│ │ │ └── ui
│ │ │ ├── DetailActivity.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── components
│ │ │ ├── LoadingBar.kt
│ │ │ └── OnlyText.kt
│ │ │ └── theme
│ │ │ ├── Color.kt
│ │ │ ├── Shape.kt
│ │ │ ├── Theme.kt
│ │ │ └── Type.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── io
│ └── github
│ └── rosariopfernandes
│ └── firebasecompose
│ └── ExampleUnitTest.kt
├── firestore
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── rosariopfernandes
│ │ └── firebasecompose
│ │ └── firestore
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ └── java
│ │ └── io
│ │ └── github
│ │ └── rosariopfernandes
│ │ └── firebasecompose
│ │ └── firestore
│ │ ├── CollectionState.kt
│ │ ├── DocumentState.kt
│ │ ├── FirestoreCollection.kt
│ │ └── FirestoreDocument.kt
│ └── test
│ └── java
│ └── io
│ └── github
│ └── rosariopfernandes
│ └── firebasecompose
│ └── firestore
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── jitpack.yml
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 |
--------------------------------------------------------------------------------
/.idea/.name:
--------------------------------------------------------------------------------
1 | Firebase Compose
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 | xmlns:android
18 |
19 | ^$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | xmlns:.*
29 |
30 | ^$
31 |
32 |
33 | BY_NAME
34 |
35 |
36 |
37 |
38 |
39 |
40 | .*:id
41 |
42 | http://schemas.android.com/apk/res/android
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | .*:name
52 |
53 | http://schemas.android.com/apk/res/android
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | name
63 |
64 | ^$
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | style
74 |
75 | ^$
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 | .*
85 |
86 | ^$
87 |
88 |
89 | BY_NAME
90 |
91 |
92 |
93 |
94 |
95 |
96 | .*
97 |
98 | http://schemas.android.com/apk/res/android
99 |
100 |
101 | ANDROID_ATTRIBUTE_ORDER
102 |
103 |
104 |
105 |
106 |
107 |
108 | .*
109 |
110 | .*
111 |
112 |
113 | BY_NAME
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing Guidelines
2 |
3 | We love contributions! But we'd like you to follow these guidelines to make the contribution process
4 | easy for everyone involved:
5 |
6 | ## Issue Tracker
7 |
8 | The issue tracker is our main channel to report a bug in the source code or a mistake in the
9 | documentation, and to request a new feature.
10 |
11 | Before submitting your issue, please search the archive. Maybe your question was already answered,
12 | your bug has already been reported or your feature has already been requested.
13 |
14 | Providing the following information will increase the chances of your issue being dealt with
15 | quickly:
16 |
17 | * **Bug reports** - if an error is being thrown, please include a stack trace and the steps to
18 | reproduce the error. If there is no error, please explain why do you consider it a bug.
19 |
20 | * **Feature Requests** - please make it clear whether you're willing to write the code for it or you
21 | need someone else to do it.
22 |
23 | * **Related Issues** - if you found a similar issue that has been reported before, be sure to
24 | mention it.
25 |
26 | ## Pull Requests
27 |
28 | Before making any changes, consider following these steps:
29 |
30 | 1. Search for an open or closed Pull Request related to your changes.
31 |
32 | 2. Search the issue tracker for issues related to your changes.
33 |
34 | 3. Open a [new issue](github.com/rosariopfernandes/FirebaseCompose/issues/new) to discuss your changes
35 | with the project owners. If they approve it, send the Pull Request.
36 |
37 | ### Sending Pull Requests
38 | If your change has been approved, follow this process:
39 |
40 | 1. [Fork](http://help.github.com/fork-a-repo/) the project, clone your fork and configure the
41 | remotes:
42 |
43 | ```bash
44 | # Clone your fork into the current directory
45 | git clone https://github.com//
46 | # Navigate to the newly cloned directory
47 | cd
48 | # Assign the original repo to a remote called "upstream"
49 | git remote add upstream https://github.com/rosariopfernandes/FirebaseCompose
50 | ```
51 |
52 | 2. Make your changes in a new branch:
53 |
54 | ```bash
55 | git checkout -b my-fix-branch master
56 | ```
57 |
58 | 3. Commit the changes using a descriptive commit message
59 |
60 | ```bash
61 | git commit -a
62 | ```
63 |
64 | Note: the optional commit `-a` command line option will automatically "add" and "rm" edited files.
65 |
66 | 4. Push your branch to GitHub:
67 |
68 | ```bash
69 | git push origin my-fix-branch
70 | ```
71 |
72 | 5. In GitHub, [send a Pull Request](https://help.github.com/articles/using-pull-requests/) with a
73 | clear title and description.
74 |
75 | * If we suggest changes then:
76 | * Make the required changes;
77 | * Rebase your branch and force push to your GitHub repository (this updates your Pull Request):
78 | ```bash
79 | # Rebase the branch
80 | git rebase master -i
81 | # Update the Pull Request
82 | git push origin my-fix-branch -f
83 | ```
84 | That's it! Thank you for you contribution!
85 |
86 | ### After your Pull Request is merged
87 |
88 | You can delete your branch and pull changes from the original
89 | (upstream) repository:
90 |
91 | 1. Delete the remote branch on GitHub either through the GitHub UI or your local shell as follows:
92 |
93 | ```bash
94 | git push origin --delete my-fix-branch
95 | ```
96 |
97 | 2. Check out the master branch:
98 |
99 | ```bash
100 | git checkout master -f
101 | ```
102 |
103 | 3. Delete the local branch:
104 |
105 | ```bash
106 | git branch -D my-fix-branch
107 | ```
108 |
109 | 4. Update your master with the latest upstream version:
110 |
111 | ```bash
112 | git pull --ff upstream master
113 | ```
114 |
115 | ## Coding Rules
116 |
117 | We generally follow the
118 | [Android Kotlin Style Guide](https://android.github.io/kotlin-guides/style.html).
119 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Rosário Pereira Fernandes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Firebase Compose
2 |
3 | Firebase Compose is an Open Source library for Android that allows you to
4 | quickly connect a Jetpack Compose app to [Firebase](https://firebase.google.com) APIs.
5 |
6 | ## Table of contents
7 |
8 | 1. [Usage](#usage)
9 | 1. [Installation](#installation)
10 | 1. [Dependencies](#dependencies)
11 | 1. [Compatibility](#compatibility-with-firebase-libraries)
12 | 1. [Sample App](#sample-app)
13 | 1. [Contributing](#contributing)
14 | 1. [License](#license)
15 | 1. [Acknowledgment](#acknowledgment)
16 |
17 | ## Usage
18 |
19 | Firebase Compose has separate modules for using Firebase Realtime Database and Cloud Firestore.
20 | To get started, see the individual instructions for each module:
21 |
22 | * [Firebase Compose Firestore](firestore/README.md)
23 | * [Firebase Compose Database](database/README.md)
24 |
25 | ## Installation
26 |
27 | Firebase Compose is published as a collection of libraries separated by the
28 | Firebase API they target. Each Firebase Compose library has a transitive
29 | dependency on the appropriate Firebase SDK so there is no need to include
30 | those separately in your app.
31 |
32 | **Step 1** - Add the jitpack maven in your root `build.gradle` at the end of repositories:
33 | ```gradle
34 | allprojects {
35 | repositories {
36 | ...
37 | maven { url 'https://jitpack.io' }
38 | }
39 | }
40 | ```
41 |
42 | **Step 2** - In your `app/build.gradle` file add a dependency on one of the Firebase Compose
43 | libraries.
44 |
45 | ```groovy
46 | dependencies {
47 | // Firebase Compose for Firebase Realtime Database
48 | implementation 'com.github.rosariopfernandes.firebase-compose:database:1.0.0-beta01'
49 |
50 | // Firebase Compose for Cloud Firestore
51 | implementation 'com.github.rosariopfernandes.firebase-compose:firestore:1.0.0-beta01'
52 | }
53 | ```
54 |
55 | After the project is synchronized, we're ready to start using Firebase functionality in our Compose app.
56 |
57 | ## Dependencies
58 |
59 | ### Compatibility with Firebase libraries
60 |
61 | Firebase Compose libraries have the following transitive dependencies on the Firebase SDK:
62 | ```
63 | firebase-compose:database
64 | |--- com.google.firebase:firebase-database-ktx
65 |
66 | firebase-compose:firestore
67 | |--- com.google.firebase:firebase-firestore-ktx
68 | ```
69 |
70 | ## Sample app
71 | A sample app is available in the [demo](/demo/) directory.
72 |
73 | ## Contributing
74 |
75 | Anyone and everyone is welcome to contribute. Please take a moment to
76 | review the [contributing guidelines](CONTRIBUTING.md).
77 |
78 | ## License
79 |
80 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
81 |
82 | ## Acknowledgment
83 | README files inspired by [FirebaseUI](https://github.com/firebase/FirebaseUI-Android/)
84 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | ext {
4 | compose_version = '1.0.0-beta01'
5 | }
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:7.0.0-alpha08'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.30"
13 | classpath 'com.google.gms:google-services:4.3.5'
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | jcenter()
24 | }
25 | }
26 |
27 | task clean(type: Delete) {
28 | delete rootProject.buildDir
29 | }
--------------------------------------------------------------------------------
/database/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/database/README.md:
--------------------------------------------------------------------------------
1 | # Firebase Compose for the Realtime Database
2 |
3 | Coming Soon
4 |
--------------------------------------------------------------------------------
/database/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | }
5 |
6 | android {
7 | compileSdkVersion 30
8 | buildToolsVersion "30.0.3"
9 |
10 | defaultConfig {
11 | minSdkVersion 21
12 | targetSdkVersion 30
13 | versionCode 1
14 | versionName "1.0.0-beta01"
15 |
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | useIR = true
33 | }
34 | buildFeatures {
35 | compose true
36 | }
37 | composeOptions {
38 | kotlinCompilerExtensionVersion compose_version
39 | }
40 | }
41 |
42 | dependencies {
43 |
44 | implementation 'androidx.core:core-ktx:1.3.2'
45 | implementation 'androidx.appcompat:appcompat:1.2.0'
46 | implementation 'com.google.android.material:material:1.3.0'
47 | implementation "androidx.compose.ui:ui:$compose_version"
48 |
49 | api 'com.google.firebase:firebase-database-ktx:19.6.0'
50 |
51 | testImplementation 'junit:junit:4.+'
52 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
53 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
54 | }
--------------------------------------------------------------------------------
/database/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/database/consumer-rules.pro
--------------------------------------------------------------------------------
/database/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/database/src/androidTest/java/io/github/rosariopfernandes/firebasecompose/database/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.database
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals(
23 | "io.github.rosariopfernandes.firebasecompose.database.test",
24 | appContext.packageName
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/database/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/database/src/main/java/io/github/rosariopfernandes/firebasecompose/database/DatabaseListRefState.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.database
2 |
3 | import androidx.compose.runtime.State
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import androidx.lifecycle.OnLifecycleEvent
11 | import com.google.firebase.database.DataSnapshot
12 | import com.google.firebase.database.DatabaseError
13 | import com.google.firebase.database.DatabaseReference
14 | import com.google.firebase.database.ValueEventListener
15 |
16 | /**
17 | * A value holder where reads to the [value] property during the execution of a
18 | * [androidx.compose.runtime.Composable] function, the current
19 | * [androidx.compose.runtime.RecomposeScope] will be subscribed to changes
20 | * of that value. When the [value] property is changed in that Database Reference,
21 | * a recomposition of any subscribed [androidx.compose.runtime.RecomposeScope]s
22 | * will be scheduled.
23 | *
24 | * @see [State]
25 | * @see [databaseListRefStateOf]
26 | */
27 | interface DatabaseListRefState : State, LifecycleObserver {
28 | override val value: RTDBDataList
29 | fun startListening()
30 | fun stopListening()
31 | operator fun component1(): RTDBDataList
32 | }
33 |
34 | /**
35 | * Return a new [DatabaseListRefState] initialized with the passed [databaseRef]
36 | *
37 | * The DatabaseListRefState class is a single value holder whose reads are observed by
38 | * Compose.
39 | *
40 | * @param databaseRef the database reference whose children will be observed
41 | * @param lifecycleOwner the lifecycle owner that the state should react to
42 | *
43 | * @see State
44 | * @see DatabaseRefState
45 | */
46 | fun databaseListRefStateOf(
47 | databaseRef: DatabaseReference,
48 | lifecycleOwner: LifecycleOwner? = null
49 | ) = object : DatabaseListRefState {
50 | private var listener: ValueEventListener? = null
51 | private var dataState: RTDBDataList by mutableStateOf(RTDBDataList.Loading)
52 |
53 | override val value: RTDBDataList
54 | get() = dataState
55 |
56 | init {
57 | lifecycleOwner?.lifecycle?.addObserver(this)
58 | }
59 |
60 | @OnLifecycleEvent(Lifecycle.Event.ON_START)
61 | override fun startListening() {
62 | if (listener == null) {
63 | listener = databaseRef.addValueEventListener(object : ValueEventListener {
64 | override fun onDataChange(snapshot: DataSnapshot) {
65 | val list = snapshot.children.toList()
66 | dataState = RTDBDataList.SnapshotList(list)
67 | }
68 |
69 | override fun onCancelled(error: DatabaseError) {
70 | dataState = RTDBDataList.Error(error)
71 | }
72 | })
73 | }
74 | }
75 |
76 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
77 | override fun stopListening() {
78 | listener?.let { databaseRef.removeEventListener(it) }
79 | }
80 |
81 | override fun component1() = value
82 | }
83 |
--------------------------------------------------------------------------------
/database/src/main/java/io/github/rosariopfernandes/firebasecompose/database/DatabaseRefState.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.database
2 |
3 | import androidx.compose.runtime.State
4 | import androidx.compose.runtime.getValue
5 | import androidx.compose.runtime.mutableStateOf
6 | import androidx.compose.runtime.setValue
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import androidx.lifecycle.OnLifecycleEvent
11 | import com.google.firebase.database.DataSnapshot
12 | import com.google.firebase.database.DatabaseError
13 | import com.google.firebase.database.DatabaseReference
14 | import com.google.firebase.database.ValueEventListener
15 |
16 | /**
17 | * A value holder where reads to the [value] property during the execution of a
18 | * [androidx.compose.runtime.Composable] function, the current
19 | * [androidx.compose.runtime.RecomposeScope] will be subscribed to changes of that value. When the
20 | * [value] property is changed in that Database Reference, a recomposition of any subscribed
21 | * [androidx.compose.runtime.RecomposeScope]s will be scheduled.
22 | *
23 | * @see [State]
24 | * @see [databaseRefStateOf]
25 | */
26 | interface DatabaseRefState : State, LifecycleObserver {
27 | override val value: RTDBData
28 | fun startListening()
29 | fun stopListening()
30 | operator fun component1(): RTDBData
31 | }
32 |
33 | /**
34 | * Return a new [DatabaseRefState] initialized with the passed [databaseRef]
35 | *
36 | * The DatabaseRefState class is a single value holder whose reads are observed by
37 | * Compose.
38 | *
39 | * @param databaseRef the database reference to be observed
40 | * @param lifecycleOwner the lifecycle owner that the state should react to
41 | *
42 | * @see State
43 | * @see DatabaseRefState
44 | */
45 | fun databaseRefStateOf(
46 | databaseRef: DatabaseReference,
47 | lifecycleOwner: LifecycleOwner? = null
48 | ) = object : DatabaseRefState {
49 | private var listener: ValueEventListener? = null
50 | private var dataState: RTDBData by mutableStateOf(RTDBData.Loading)
51 |
52 | override val value: RTDBData
53 | get() = dataState
54 |
55 | init {
56 | lifecycleOwner?.lifecycle?.addObserver(this)
57 | }
58 |
59 | @OnLifecycleEvent(Lifecycle.Event.ON_START)
60 | override fun startListening() {
61 | if (listener == null) {
62 | listener = databaseRef.addValueEventListener(object : ValueEventListener {
63 | override fun onDataChange(snapshot: DataSnapshot) {
64 | dataState = RTDBData.Snapshot(snapshot)
65 | }
66 |
67 | override fun onCancelled(error: DatabaseError) {
68 | dataState = RTDBData.Error(error)
69 | }
70 | })
71 | }
72 | }
73 |
74 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
75 | override fun stopListening() {
76 | listener?.let { databaseRef.removeEventListener(it) }
77 | }
78 |
79 | override fun component1() = value
80 | }
81 |
--------------------------------------------------------------------------------
/database/src/main/java/io/github/rosariopfernandes/firebasecompose/database/RTDBData.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.database
2 |
3 | import com.google.firebase.database.DataSnapshot
4 | import com.google.firebase.database.DatabaseError
5 |
6 | sealed class RTDBData {
7 | data class Snapshot(val snapshot: DataSnapshot) : RTDBData()
8 | data class Error(val databaseError: DatabaseError) : RTDBData()
9 | object Loading : RTDBData()
10 | }
--------------------------------------------------------------------------------
/database/src/main/java/io/github/rosariopfernandes/firebasecompose/database/RTDBDataList.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.database
2 |
3 | import com.google.firebase.database.DataSnapshot
4 | import com.google.firebase.database.DatabaseError
5 |
6 | sealed class RTDBDataList {
7 | data class SnapshotList(val snapshot: List) : RTDBDataList()
8 | data class Error(val databaseError: DatabaseError) : RTDBDataList()
9 | object Loading : RTDBDataList()
10 | }
--------------------------------------------------------------------------------
/database/src/test/java/io/github/rosariopfernandes/firebasecompose/database/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.database
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'com.google.gms.google-services'
5 | }
6 |
7 | android {
8 | compileSdkVersion 30
9 | buildToolsVersion "30.0.3"
10 |
11 | defaultConfig {
12 | applicationId "io.github.rosariopfernandes.firebasecompose"
13 | minSdkVersion 21
14 | targetSdkVersion 30
15 | versionCode 1
16 | versionName "1.0"
17 |
18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | kotlinOptions {
32 | jvmTarget = '1.8'
33 | useIR = true
34 | }
35 | buildFeatures {
36 | compose true
37 | }
38 | composeOptions {
39 | kotlinCompilerExtensionVersion compose_version
40 | }
41 | }
42 |
43 | dependencies {
44 |
45 | implementation 'androidx.core:core-ktx:1.3.2'
46 | implementation 'androidx.appcompat:appcompat:1.2.0'
47 | implementation 'com.google.android.material:material:1.3.0'
48 | implementation "androidx.compose.ui:ui:$compose_version"
49 | implementation "androidx.compose.ui:ui-util:$compose_version"
50 | implementation "androidx.activity:activity-compose:1.3.0-alpha02"
51 | implementation "androidx.compose.material:material:$compose_version"
52 | implementation "androidx.compose.ui:ui-tooling:$compose_version"
53 | implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.0'
54 |
55 | // Firebase Compose
56 | implementation project(':firestore')
57 |
58 | // Helper libraries for UI
59 | implementation "dev.chrisbanes.accompanist:accompanist-glide:0.6.0"
60 | implementation "dev.chrisbanes.accompanist:accompanist-insets:0.6.0"
61 |
62 | testImplementation 'junit:junit:4.+'
63 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
64 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
65 | }
--------------------------------------------------------------------------------
/demo/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/demo/src/androidTest/java/io/github/rosariopfernandes/firebasecompose/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("io.github.rosariopfernandes.firebasecompose", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/model/Snack.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.model
2 |
3 | data class Snack(
4 | val id: Long = 1L,
5 | val name: String = "Donut",
6 | val imageUrl: String = "",
7 | val price: Long = 100L,
8 | val tagline: String = "Dessert",
9 | )
10 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/DetailActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui
2 |
3 | import android.os.Bundle
4 | import androidx.activity.compose.setContent
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.compose.foundation.*
7 | import androidx.compose.foundation.layout.*
8 | import androidx.compose.foundation.shape.CircleShape
9 | import androidx.compose.material.*
10 | import androidx.compose.material.icons.Icons
11 | import androidx.compose.material.icons.outlined.ArrowBack
12 | import androidx.compose.runtime.Composable
13 | import androidx.compose.runtime.remember
14 | import androidx.compose.ui.Modifier
15 | import androidx.compose.ui.graphics.Brush
16 | import androidx.compose.ui.graphics.Color
17 | import androidx.compose.ui.graphics.graphicsLayer
18 | import androidx.compose.ui.unit.lerp
19 | import androidx.compose.ui.util.lerp
20 | import androidx.compose.ui.layout.Layout
21 | import androidx.compose.ui.platform.LocalDensity
22 | import androidx.compose.ui.res.stringResource
23 | import androidx.compose.ui.unit.Constraints
24 | import androidx.compose.ui.unit.dp
25 | import androidx.compose.ui.unit.sp
26 | import com.bumptech.glide.request.RequestOptions
27 | import com.google.firebase.firestore.ktx.firestore
28 | import com.google.firebase.firestore.ktx.toObject
29 | import com.google.firebase.ktx.Firebase
30 | import dev.chrisbanes.accompanist.glide.GlideImage
31 | import dev.chrisbanes.accompanist.insets.ProvideWindowInsets
32 | import dev.chrisbanes.accompanist.insets.navigationBarsPadding
33 | import dev.chrisbanes.accompanist.insets.statusBarsPadding
34 | import io.github.rosariopfernandes.firebasecompose.R
35 | import io.github.rosariopfernandes.firebasecompose.firestore.FirestoreDocument
36 | import io.github.rosariopfernandes.firebasecompose.firestore.documentStateOf
37 | import io.github.rosariopfernandes.firebasecompose.model.Snack
38 | import io.github.rosariopfernandes.firebasecompose.ui.components.LoadingBar
39 | import io.github.rosariopfernandes.firebasecompose.ui.components.OnlyText
40 | import io.github.rosariopfernandes.firebasecompose.ui.theme.FirebaseComposeTheme
41 | import io.github.rosariopfernandes.firebasecompose.ui.theme.purple200
42 | import io.github.rosariopfernandes.firebasecompose.ui.theme.purple500
43 | import io.github.rosariopfernandes.firebasecompose.ui.theme.purple700
44 | import kotlin.math.max
45 | import kotlin.math.min
46 |
47 | private val BottomBarHeight = 56.dp
48 | private val TitleHeight = 128.dp
49 | private val GradientScroll = 180.dp
50 | private val ImageOverlap = 115.dp
51 | private val MinTitleOffset = 56.dp
52 | private val MinImageOffset = 12.dp
53 | private val MaxTitleOffset = ImageOverlap + MinTitleOffset + GradientScroll
54 | private val ExpandedImageSize = 300.dp
55 | private val CollapsedImageSize = 150.dp
56 | private val HzPadding = Modifier.padding(horizontal = 24.dp)
57 |
58 | class DetailActivity : AppCompatActivity() {
59 | @ExperimentalFoundationApi
60 | override fun onCreate(savedInstanceState: Bundle?) {
61 | super.onCreate(savedInstanceState)
62 |
63 | val snackId = intent.extras!!.getString("snackId")!!
64 | setContent {
65 | FirebaseComposeTheme {
66 | ProvideWindowInsets {
67 | val docRef = Firebase.firestore.collection("snacks").document(snackId)
68 | val (result) = remember { documentStateOf(docRef, this@DetailActivity) }
69 | when (result) {
70 | is FirestoreDocument.Loading -> {
71 | LoadingBar()
72 | }
73 | is FirestoreDocument.Error -> {
74 | val exception = result.exception
75 | OnlyText(title = stringResource(id = R.string.title_error),
76 | message = exception.message ?: "")
77 | }
78 | is FirestoreDocument.Snapshot -> {
79 | val snack = result.snapshot.toObject()!!
80 | SnackDetail(snack = snack, upPress = { finish() })
81 | }
82 | }
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
89 | @Composable
90 | fun SnackDetail(
91 | snack: Snack,
92 | upPress: () -> Unit
93 | ) {
94 | Box(Modifier.fillMaxSize()) {
95 | val scroll = rememberScrollState(0)
96 | Header()
97 | Body(scroll)
98 | Title(snack, scroll.value)
99 | Image(snack.imageUrl, scroll.value)
100 | Up(upPress)
101 | }
102 | }
103 |
104 | @Composable
105 | private fun Header() {
106 | Spacer(
107 | modifier = Modifier
108 | .height(280.dp)
109 | .fillMaxWidth()
110 | .background(Brush.horizontalGradient(listOf(purple200, purple500)))
111 | )
112 | }
113 |
114 | @Composable
115 | private fun Up(upPress: () -> Unit) {
116 | IconButton(
117 | onClick = upPress,
118 | modifier = Modifier
119 | .statusBarsPadding()
120 | .padding(horizontal = 16.dp, vertical = 10.dp)
121 | .size(36.dp)
122 | .background(
123 | color = purple200,
124 | shape = CircleShape
125 | )
126 | ) {
127 | Icon(
128 | imageVector = Icons.Outlined.ArrowBack,
129 | tint = Color.White,
130 | contentDescription = stringResource(R.string.label_back)
131 | )
132 | }
133 | }
134 |
135 | @Composable
136 | private fun Body(
137 | scroll: ScrollState
138 | ) {
139 | Column {
140 | Spacer(
141 | modifier = Modifier
142 | .fillMaxWidth()
143 | .statusBarsPadding()
144 | .height(MinTitleOffset)
145 | )
146 | Column(
147 | modifier = Modifier.verticalScroll(scroll)
148 | ) {
149 | Spacer(Modifier.height(GradientScroll))
150 | Surface(Modifier.fillMaxWidth()) {
151 | Column {
152 | Spacer(Modifier.height(ImageOverlap))
153 | Spacer(Modifier.height(TitleHeight))
154 |
155 | Spacer(Modifier.height(16.dp))
156 | Text(
157 | text = stringResource(R.string.detail_header),
158 | style = MaterialTheme.typography.overline,
159 | color = Color.DarkGray,
160 | modifier = HzPadding
161 | )
162 | Spacer(Modifier.height(4.dp))
163 | Text(
164 | text = stringResource(R.string.detail_placeholder),
165 | style = MaterialTheme.typography.body1,
166 | color = Color.Gray,
167 | modifier = HzPadding
168 | )
169 |
170 | Spacer(Modifier.height(40.dp))
171 | Text(
172 | text = stringResource(R.string.ingredients),
173 | style = MaterialTheme.typography.overline,
174 | color = Color.DarkGray,
175 | modifier = HzPadding
176 | )
177 | Spacer(Modifier.height(4.dp))
178 | Text(
179 | text = stringResource(R.string.ingredients_list),
180 | style = MaterialTheme.typography.body1,
181 | color = Color.Gray,
182 | modifier = HzPadding
183 | )
184 |
185 | Spacer(Modifier.height(16.dp))
186 | Divider( color = Color.LightGray, thickness = 1.dp, startIndent = 0.dp )
187 |
188 | Spacer(
189 | modifier = Modifier
190 | .padding(bottom = BottomBarHeight)
191 | .navigationBarsPadding(left = false, right = false)
192 | .height(8.dp)
193 | )
194 | }
195 | }
196 | }
197 | }
198 | }
199 |
200 | @Composable
201 | private fun Title(snack: Snack, scroll: Int) {
202 | val maxOffset = with(LocalDensity.current) { MaxTitleOffset.toPx() }
203 | val minOffset = with(LocalDensity.current) { MinTitleOffset.toPx() }
204 | val offset = (maxOffset - scroll).coerceAtLeast(minOffset)
205 | Column(
206 | verticalArrangement = Arrangement.Bottom,
207 | modifier = Modifier
208 | .heightIn(min = TitleHeight)
209 | .statusBarsPadding()
210 | .graphicsLayer { translationY = offset }
211 | .background(color = Color.White)
212 | ) {
213 | Spacer(Modifier.height(16.dp))
214 | Text(
215 | text = snack.name,
216 | style = MaterialTheme.typography.h4,
217 | color = purple500,
218 | modifier = HzPadding
219 | )
220 | Text(
221 | text = snack.tagline,
222 | style = MaterialTheme.typography.subtitle2,
223 | fontSize = 20.sp,
224 | color = Color.DarkGray,
225 | modifier = HzPadding
226 | )
227 | Spacer(Modifier.height(4.dp))
228 | Text(
229 | text = "${snack.price} MZN",
230 | style = MaterialTheme.typography.h6,
231 | color = purple700,
232 | modifier = HzPadding
233 | )
234 |
235 | Spacer(Modifier.height(8.dp))
236 | Divider( color = Color.LightGray, thickness = 1.dp, startIndent = 0.dp )
237 | }
238 | }
239 |
240 | @Composable
241 | private fun Image(
242 | imageUrl: String,
243 | scroll: Int
244 | ) {
245 | val collapseRange = with(LocalDensity.current) { (MaxTitleOffset - MinTitleOffset).toPx() }
246 | val collapseFraction = (scroll / collapseRange).coerceIn(0f, 1f)
247 |
248 | CollapsingImageLayout(
249 | collapseFraction = collapseFraction,
250 | modifier = HzPadding.then(Modifier.statusBarsPadding())
251 | ) {
252 | GlideImage(
253 | data = imageUrl,
254 | contentDescription = null,
255 | modifier = Modifier.fillMaxSize(),
256 | requestBuilder = {
257 | val options = RequestOptions()
258 | options.circleCrop()
259 | apply(options)
260 | }
261 | )
262 | }
263 | }
264 |
265 | @Composable
266 | private fun CollapsingImageLayout(
267 | collapseFraction: Float,
268 | modifier: Modifier = Modifier,
269 | content: @Composable () -> Unit
270 | ) {
271 | Layout(
272 | modifier = modifier,
273 | content = content
274 | ) { measurables, constraints ->
275 | check(measurables.size == 1)
276 |
277 | val imageMaxSize = min(ExpandedImageSize.roundToPx(), constraints.maxWidth)
278 | val imageMinSize = max(CollapsedImageSize.roundToPx(), constraints.minWidth)
279 | val imageWidth = lerp(imageMaxSize, imageMinSize, collapseFraction)
280 | val imagePlaceable = measurables[0].measure(Constraints.fixed(imageWidth, imageWidth))
281 |
282 | val imageY = lerp(MinTitleOffset, MinImageOffset, collapseFraction).roundToPx()
283 | val imageX = lerp(
284 | (constraints.maxWidth - imageWidth) / 2, // centered when expanded
285 | constraints.maxWidth - imageWidth, // right aligned when collapsed
286 | collapseFraction
287 | )
288 | layout(
289 | width = constraints.maxWidth,
290 | height = imageY + imageWidth
291 | ) {
292 | imagePlaceable.place(imageX, imageY)
293 | }
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.os.Bundle
6 | import android.widget.Toast
7 | import androidx.activity.compose.setContent
8 | import androidx.appcompat.app.AppCompatActivity
9 | import androidx.compose.foundation.ExperimentalFoundationApi
10 | import androidx.compose.foundation.clickable
11 | import androidx.compose.foundation.layout.*
12 | import androidx.compose.foundation.lazy.GridCells
13 | import androidx.compose.foundation.lazy.LazyVerticalGrid
14 | import androidx.compose.foundation.lazy.items
15 | import androidx.compose.material.*
16 | import androidx.compose.runtime.Composable
17 | import androidx.compose.runtime.remember
18 | import androidx.compose.ui.Alignment
19 | import androidx.compose.ui.Modifier
20 | import androidx.compose.ui.graphics.Color
21 | import androidx.compose.ui.res.stringResource
22 | import androidx.compose.ui.unit.dp
23 | import com.bumptech.glide.request.RequestOptions
24 | import com.google.firebase.firestore.DocumentSnapshot
25 | import com.google.firebase.firestore.ktx.firestore
26 | import com.google.firebase.firestore.ktx.toObject
27 | import com.google.firebase.ktx.Firebase
28 | import dev.chrisbanes.accompanist.glide.GlideImage
29 | import io.github.rosariopfernandes.firebasecompose.R
30 | import io.github.rosariopfernandes.firebasecompose.firestore.FirestoreCollection
31 | import io.github.rosariopfernandes.firebasecompose.firestore.collectionStateOf
32 | import io.github.rosariopfernandes.firebasecompose.model.Snack
33 | import io.github.rosariopfernandes.firebasecompose.ui.components.LoadingBar
34 | import io.github.rosariopfernandes.firebasecompose.ui.components.OnlyText
35 | import io.github.rosariopfernandes.firebasecompose.ui.theme.FirebaseComposeTheme
36 |
37 | const val FIRESTORE_COLLECTION = "snacks"
38 |
39 | class MainActivity : AppCompatActivity() {
40 | @ExperimentalFoundationApi
41 | override fun onCreate(savedInstanceState: Bundle?) {
42 | super.onCreate(savedInstanceState)
43 | setContent {
44 | FirebaseComposeTheme {
45 | Scaffold(
46 | topBar = { Toolbar(this@MainActivity) },
47 | content = {
48 | val query = Firebase.firestore.collection(FIRESTORE_COLLECTION)
49 | val (result) = remember { collectionStateOf (query, this) }
50 |
51 | when (result) {
52 | is FirestoreCollection.Error -> {
53 | OnlyText(
54 | title = stringResource(R.string.title_error),
55 | message = result.exception.message ?: ""
56 | )
57 | }
58 | is FirestoreCollection.Loading -> {
59 | LoadingBar()
60 | }
61 | is FirestoreCollection.Snapshot -> {
62 | if (result.list.isEmpty()) {
63 | OnlyText(
64 | title = stringResource(id = R.string.title_empty),
65 | message = stringResource(id = R.string.message_empty)
66 | )
67 | } else {
68 | SnackList(context = this@MainActivity, items = result.list)
69 | }
70 | }
71 | }
72 | }
73 | )
74 | }
75 | }
76 | }
77 | }
78 |
79 | @Composable
80 | fun Toolbar(context: Context) {
81 | TopAppBar(
82 | title = { Text(text = "FireSnacks") },
83 | actions = {
84 | Button(onClick = { addItems(context) }) {
85 | Text(text = "Add Items")
86 | }
87 | }
88 | )
89 | }
90 |
91 | fun addItems(context: Context) {
92 | val firestore = Firebase.firestore
93 | val collection = firestore.collection(FIRESTORE_COLLECTION)
94 | firestore.runBatch { batch ->
95 | for (item in getItems()) {
96 | val docRef = collection.document()
97 | batch.set(docRef, item)
98 | }
99 | }.addOnSuccessListener {
100 | Toast.makeText(context, "Items added!", Toast.LENGTH_SHORT).show()
101 | }.addOnFailureListener { e ->
102 | Toast.makeText(context, "Error adding items: ${e.message}", Toast.LENGTH_LONG).show()
103 | }
104 | }
105 |
106 | private fun getItems() = listOf(
107 | Snack(id = 1, name = "Cupcake", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/5/56/Chocolate_cupcakes.jpg"),
108 | Snack(id = 2, name = "Donut", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/a/a5/Glazed-Donut.jpg/640px-Glazed-Donut.jpg"),
109 | Snack(id = 3, name = "Eclair", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bb/Ginger_Spice_Eclair_-_39843168741.jpg/640px-Ginger_Spice_Eclair_-_39843168741.jpg"),
110 | Snack(id = 4, name = "Froyo", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/0/06/ICE_CREAM_%28FROYO%29_01.jpg/640px-ICE_CREAM_%28FROYO%29_01.jpg"),
111 | Snack(id = 5, name = "Gingerbread", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/4/49/Gingerbread_men.jpg/401px-Gingerbread_men.jpg"),
112 | Snack(id = 6, name = "Honey", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/37/Bienenwabe_mit_Eiern_und_Brut_5.jpg/640px-Bienenwabe_mit_Eiern_und_Brut_5.jpg"),
113 | Snack(id = 7, name = "Ice Cream", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Bananas_Foster_Ice_Cream_at_Little_Giant_Ice_Cream.jpg/480px-Bananas_Foster_Ice_Cream_at_Little_Giant_Ice_Cream.jpg"),
114 | Snack(id = 8, name = "Jelly Beans", imageUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/JellyBellyBeans.jpg/616px-JellyBellyBeans.jpg")
115 | )
116 |
117 | @ExperimentalFoundationApi
118 | @Composable
119 | fun SnackList(
120 | context: Context,
121 | items: List
122 | ) {
123 | LazyVerticalGrid(cells = GridCells.Fixed(2), content = {
124 | items(items) { snapshot ->
125 | val snack = snapshot.toObject()!!
126 | SnackItem(snack = snack, onSnackClick = {
127 | val intent = Intent(context, DetailActivity::class.java)
128 | intent.putExtra("snackId", snapshot.id)
129 | context.startActivity(intent)
130 | })
131 | }
132 | })
133 | }
134 |
135 | @Composable
136 | fun SnackItem(
137 | snack: Snack,
138 | onSnackClick: () -> Unit,
139 | modifier: Modifier = Modifier
140 | ) {
141 | Card(
142 | shape = MaterialTheme.shapes.medium,
143 | modifier = modifier.padding(start = 4.dp, end = 4.dp, bottom = 8.dp)
144 | ) {
145 | Column(
146 | horizontalAlignment = Alignment.CenterHorizontally,
147 | modifier = Modifier
148 | .clickable(onClick = { onSnackClick() })
149 | .padding(8.dp)
150 | ) {
151 | GlideImage(
152 | data = snack.imageUrl,
153 | contentDescription = null,
154 | modifier = Modifier.size(120.dp),
155 | requestBuilder = {
156 | val options = RequestOptions()
157 | options.circleCrop()
158 | apply(options)
159 | }
160 | )
161 | Text(
162 | text = snack.name,
163 | style = MaterialTheme.typography.subtitle1,
164 | color = Color.DarkGray,
165 | modifier = Modifier.padding(top = 8.dp)
166 | )
167 | }
168 | }
169 | }
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/components/LoadingBar.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui.components
2 |
3 | import androidx.compose.foundation.layout.*
4 | import androidx.compose.material.CircularProgressIndicator
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Alignment
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.unit.dp
9 |
10 | @Composable
11 | fun LoadingBar() {
12 | Column(
13 | horizontalAlignment = Alignment.CenterHorizontally,
14 | verticalArrangement = Arrangement.Center,
15 | modifier = Modifier.fillMaxWidth().fillMaxHeight()
16 | ) {
17 | CircularProgressIndicator(modifier = Modifier.width(64.dp).height(64.dp))
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/components/OnlyText.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui.components
2 |
3 | import androidx.compose.foundation.layout.Arrangement
4 | import androidx.compose.foundation.layout.Column
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.material.MaterialTheme
7 | import androidx.compose.material.Text
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.text.style.TextAlign
12 | import io.github.rosariopfernandes.firebasecompose.ui.theme.purple500
13 |
14 | @Composable
15 | fun OnlyText(
16 | title: String,
17 | message: String
18 | ) {
19 | Column(
20 | horizontalAlignment = Alignment.CenterHorizontally,
21 | verticalArrangement = Arrangement.Center,
22 | modifier = Modifier.fillMaxSize()
23 | ) {
24 | Text(text = title, style = MaterialTheme.typography.h4, color = purple500)
25 | Text(text = message, textAlign = TextAlign.Center)
26 | }
27 | }
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/theme/Color.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui.theme
2 |
3 | import androidx.compose.ui.graphics.Color
4 |
5 | val purple200 = Color(0xFFBB86FC)
6 | val purple500 = Color(0xFF6200EE)
7 | val purple700 = Color(0xFF3700B3)
8 | val teal200 = Color(0xFF03DAC5)
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/theme/Shape.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui.theme
2 |
3 | import androidx.compose.foundation.shape.RoundedCornerShape
4 | import androidx.compose.material.Shapes
5 | import androidx.compose.ui.unit.dp
6 |
7 | val shapes = Shapes(
8 | small = RoundedCornerShape(4.dp),
9 | medium = RoundedCornerShape(4.dp),
10 | large = RoundedCornerShape(0.dp)
11 | )
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui.theme
2 |
3 | import androidx.compose.foundation.isSystemInDarkTheme
4 | import androidx.compose.material.MaterialTheme
5 | import androidx.compose.material.darkColors
6 | import androidx.compose.material.lightColors
7 | import androidx.compose.runtime.Composable
8 |
9 | private val DarkColorPalette = darkColors(
10 | primary = purple200,
11 | primaryVariant = purple700,
12 | secondary = teal200
13 | )
14 |
15 | private val LightColorPalette = lightColors(
16 | primary = purple500,
17 | primaryVariant = purple700,
18 | secondary = teal200
19 |
20 | /* Other default colors to override
21 | background = Color.White,
22 | surface = Color.White,
23 | onPrimary = Color.White,
24 | onSecondary = Color.Black,
25 | onBackground = Color.Black,
26 | onSurface = Color.Black,
27 | */
28 | )
29 |
30 | @Composable
31 | fun FirebaseComposeTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) {
32 | val colors = if (darkTheme) {
33 | DarkColorPalette
34 | } else {
35 | LightColorPalette
36 | }
37 |
38 | MaterialTheme(
39 | colors = colors,
40 | typography = typography,
41 | shapes = shapes,
42 | content = content
43 | )
44 | }
--------------------------------------------------------------------------------
/demo/src/main/java/io/github/rosariopfernandes/firebasecompose/ui/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.ui.theme
2 |
3 | import androidx.compose.material.Typography
4 | import androidx.compose.ui.text.TextStyle
5 | import androidx.compose.ui.text.font.FontFamily
6 | import androidx.compose.ui.text.font.FontWeight
7 | import androidx.compose.ui.unit.sp
8 |
9 | // Set of Material typography styles to start with
10 | val typography = Typography(
11 | body1 = TextStyle(
12 | fontFamily = FontFamily.Default,
13 | fontWeight = FontWeight.Normal,
14 | fontSize = 16.sp
15 | )
16 | /* Other default text styles to override
17 | button = TextStyle(
18 | fontFamily = FontFamily.Default,
19 | fontWeight = FontWeight.W500,
20 | fontSize = 14.sp
21 | ),
22 | caption = TextStyle(
23 | fontFamily = FontFamily.Default,
24 | fontWeight = FontWeight.Normal,
25 | fontSize = 12.sp
26 | )
27 | */
28 | )
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/demo/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/demo/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/demo/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Firebase Compose
3 | Navigate Up
4 | Details
5 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud..
6 | Ingredients
7 | Vanilla, Almond Flour, Eggs, Butter, Cream, Sugar
8 |
9 | An error occured :(
10 | No Snacks :/
11 | You havent added any snacks yet. Press the top right button to add some snacks.
12 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/demo/src/test/java/io/github/rosariopfernandes/firebasecompose/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/firestore/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/firestore/README.md:
--------------------------------------------------------------------------------
1 | # Firebase Compose for Cloud Firestore
2 |
3 | Firebase Compose makes it simple to bind data from Cloud Firestore to your app's UI.
4 |
5 | Before using this library, you should be familiar with the following topics:
6 |
7 | * [Structuring and querying data in Cloud Firestore][firestore-docs].
8 | * [Compose's LazyColumn][lazycolumn].
9 |
10 | ## Data model
11 |
12 | Suppose you have an app that displays a list of snacks and each snack is a document
13 | in the `snacks` collection of your database. In your app, you may represent a snack like this:
14 |
15 |
16 | ```kotlin
17 | data class Snack(
18 | val name: String = ",
19 | val price: Long = 0L,
20 | // Default values are needed for Firestore
21 | )
22 | ```
23 |
24 | For a model class with default values like the `Snack` class above, Firestore can perform automatic
25 | serialization in `DocumentReference#set()` and automatic deserialization in
26 | `DocumentSnapshot#toObject()`. For more information on data mapping in Firestore, see the
27 | documentation on [custom objects][firestore-custom-objects].
28 |
29 | ## Querying
30 |
31 | On the main screen of your app, you may want to show the cheapest 20 snacks.
32 | In Firestore, you would use the following query:
33 |
34 | ```kotlin
35 | val query = Firebase.firestore.collection("snacks")
36 | .orderBy("price")
37 | .limit(20)
38 | ```
39 |
40 | To retrieve this data without FirebaseUI, you might use `addSnapshotListener` to listen for
41 | live query updates:
42 |
43 | ```kotlin
44 | query.addSnapshotListener { snapshot, e ->
45 | e?.let {
46 | // Handle error
47 | return@addSnapshotListener
48 | }
49 |
50 | val chats = snapshot.toObjects()
51 |
52 | // Update UI
53 | // ...
54 | }
55 | ```
56 |
57 | ## Using Firebase Compose to display a list of Documents from a Firestore Collection
58 |
59 | If you're displaying a list of data, you likely want to bind the `Snack` objects to one of the
60 | `Lazy` Composables, such as `LazyColumn` or `LazyRow`.
61 |
62 | Firebase Compose can help you do this instantly!
63 |
64 |
65 | ### Using the `CollectionState`
66 |
67 | The `CollectionState` binds a `Query` to a `FirestoreCollection` sealed class, which has 3 finite states:
68 | - `FirestoreCollection.Snapshot` - contains the list of documents (as `DocumentSnapshot`) returned from that query.
69 | - `FirestoreCollection.Error` - contains the error returned from the query.
70 | - `FirestoreCollection.Loading` - indicates that no data or error was emitted from the query yet.
71 |
72 | When documents are added, removed, or change these updates are automatically applied to your UI
73 | in real time.
74 |
75 | First, create the state using the destructuring declaration provided by the library and the
76 | `remember { }` function to handle recompositions:
77 |
78 | ```kotlin
79 | val lifecycleOwner = this@MainActivity // You can also pass a fragment to it
80 | val (result) = remember { collectionStateOf(query, lifecycleOwner) }
81 | ```
82 |
83 | Now you can get the list of snacks by checking the `result` state and passing the `result.list` property
84 | to the `items()` extension function of a `LazyColumn` or `LazyRow` composable:
85 | ```kotlin
86 | if (result is FirestoreCollection.Snapshot) {
87 | LazyColumn {
88 | items(result.items) { documentSnapshot ->
89 | // parse the DocumentSnapshot to your custom class
90 | val snack = documentSnapshot.toObject()!!
91 |
92 | // Pass the snack object to your Item Composable
93 | // ...
94 | }
95 | }
96 | }
97 | ```
98 |
99 | And that's it! It's that easy!
100 |
101 | #### `CollectionState` lifecycle
102 |
103 | ##### Start/stop listening
104 |
105 | The `CollectionState` uses a snapshot listener to monitor changes to the Firestore query.
106 | To begin listening for data, call the `startListening()` method. You may want to call this
107 | in your `onStart()` method. Make sure you have finished any authentication necessary to read the
108 | data before calling `startListening()` or your query will fail.
109 |
110 | ```kotlin
111 | val state = collectionStateOf(query)
112 |
113 | override fun onStart() {
114 | super.onStart()
115 | state.startListening()
116 | }
117 | ```
118 |
119 | Similarly, the `stopListening()` call removes the snapshot listener.
120 | Call this method when the containing Activity or Fragment stops:
121 |
122 | ```kotlin
123 | override fun onStop() {
124 | super.onStop()
125 | state.stopListening()
126 | }
127 | ```
128 |
129 | ##### Automatic listening
130 |
131 | If you don't want to manually start/stop listening you can use
132 | [Android Architecture Components][arch-components] to automatically manage the lifecycle of the
133 | `CollectionState`. Pass a `LifecycleOwner` to it and Firebase Compose will automatically
134 | start and stop listening in `onStart()` and `onStop()`.
135 |
136 | #### Handling Error and Loading States
137 |
138 | If you would like to handle all the possible states, you can use a `when` expression:
139 |
140 | ```kotlin
141 | val (result) = remember { collectionStateOf(query, lifecycleOwner) }
142 |
143 | when (result) {
144 | is FirestoreCollection.Snapshot -> {
145 | val items = result.items
146 | // call a Composable with this value
147 | }
148 | is FirestoreCollection.Error -> {
149 | val exception = result.exception
150 | // call a Composable to tell the user an error occurred
151 | }
152 | is FirestoreCollection.Loading -> {
153 | // call a Composable that shows a loading state
154 | }
155 | }
156 | ```
157 |
158 |
159 | ### Using Pagination
160 |
161 | Coming Soon.
162 |
163 | ## Using Firebase Compose to display a single Firestore Document
164 |
165 | Now if you need to display a single `Snack` in your app, you can use `DocumentState`.
166 |
167 | `DocumentState` binds a `DocumentReference` to a `FirestoreDocument` sealed class, which has 3 finite states:
168 | - `FirestoreDocument.Snapshot` - contains `DocumentSnapshot` returned from that document reference.
169 | - `FirestoreDocument.Error` - contains any exception thrown when reading the document reference.
170 | - `FirestoreDocument.Loading` - indicates that no data or error was emitted from the document reference yet.
171 |
172 | Its usage is similar to `CollectionState`. The main difference is that `DocumentState`
173 | returns a single `DocumentSnapshot` instead of `List`:
174 |
175 | ```kotlin
176 | // getting a reference to the snack with key/id 'snack1'
177 | val documentRef = Firebase.firestore.collection("snacks").document("snack1")
178 |
179 | val (result) = remember { documentStateOf(documentRef, lifecycleOwner) }
180 |
181 | when (result) {
182 | is FirestoreDocument.Snapshot -> {
183 | val items = result.snapshot
184 | // call a Composable with this value
185 | }
186 | is FirestoreDocument.Error -> {
187 | val exception = result.exception
188 | // call a Composable to tell the user an error occurred
189 | }
190 | is FirestoreDocument.Loading -> {
191 | // call a Composable that shows a loading state
192 | }
193 | }
194 | ```
195 |
196 | -----
197 |
198 | This README file was inspired by [FirebaseUI](https://github.com/firebase/FirebaseUI-Android/).
199 |
200 | [firestore-docs]: https://firebase.google.com/docs/firestore/
201 | [firestore-custom-objects]: https://firebase.google.com/docs/firestore/manage-data/add-data#custom_objects
202 | [lazycolumn]: https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary#lazycolumn
203 | [arch-components]: https://developer.android.com/topic/libraries/architecture/index.html
204 | [paging-support]: https://developer.android.com/topic/libraries/architecture/paging.html
205 |
--------------------------------------------------------------------------------
/firestore/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'maven-publish'
5 | }
6 |
7 | android {
8 | compileSdkVersion 30
9 | buildToolsVersion "30.0.3"
10 |
11 | defaultConfig {
12 | minSdkVersion 21
13 | targetSdkVersion 30
14 | versionCode 1
15 | versionName "1.0.0-beta01"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | consumerProguardFiles "consumer-rules.pro"
19 | }
20 |
21 | buildTypes {
22 | release {
23 | minifyEnabled false
24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
25 | }
26 | }
27 | compileOptions {
28 | sourceCompatibility JavaVersion.VERSION_1_8
29 | targetCompatibility JavaVersion.VERSION_1_8
30 | }
31 | kotlinOptions {
32 | jvmTarget = '1.8'
33 | useIR = true
34 | }
35 | buildFeatures {
36 | compose true
37 | }
38 | composeOptions {
39 | kotlinCompilerExtensionVersion compose_version
40 | }
41 |
42 | afterEvaluate {
43 | publishing {
44 | publications {
45 | // Creates a Maven publication called "release".
46 | release(MavenPublication) {
47 | // Applies the component for the release build variant.
48 | from components.release
49 |
50 | // You can then customize attributes of the publication as shown below.
51 | groupId = 'com.github.rosariopfernandes'
52 | artifactId = 'firestore'
53 | version = '1.0.0-beta01'
54 | }
55 | // Creates a Maven publication called “debug”.
56 | debug(MavenPublication) {
57 | // Applies the component for the debug build variant.
58 | from components.debug
59 |
60 | groupId = 'com.github.rosariopfernandes'
61 | artifactId = 'firestore-debug'
62 | version = '1.0.0-beta01'
63 | }
64 | }
65 | }
66 |
67 | }
68 | }
69 |
70 | dependencies {
71 |
72 | implementation 'androidx.core:core-ktx:1.3.2'
73 | implementation 'androidx.appcompat:appcompat:1.2.0'
74 | implementation 'com.google.android.material:material:1.3.0'
75 | implementation "androidx.compose.ui:ui:$compose_version"
76 |
77 | api 'com.google.firebase:firebase-firestore-ktx:22.1.0'
78 |
79 | testImplementation 'junit:junit:4.+'
80 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
81 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
82 | }
--------------------------------------------------------------------------------
/firestore/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/firestore/consumer-rules.pro
--------------------------------------------------------------------------------
/firestore/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/firestore/src/androidTest/java/io/github/rosariopfernandes/firebasecompose/firestore/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.firestore
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals(
23 | "io.github.rosariopfernandes.firebasecompose.firestore.test",
24 | appContext.packageName
25 | )
26 | }
27 | }
--------------------------------------------------------------------------------
/firestore/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
--------------------------------------------------------------------------------
/firestore/src/main/java/io/github/rosariopfernandes/firebasecompose/firestore/CollectionState.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.firestore
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.compose.runtime.State
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import androidx.lifecycle.OnLifecycleEvent
11 | import com.google.firebase.firestore.ListenerRegistration
12 | import com.google.firebase.firestore.Query
13 |
14 | /**
15 | * A value holder where reads to the [value] property during the execution of a [Composable]
16 | * function, the current [RecomposeScope] will be subscribed to changes of that value. When the
17 | * [value] property is changed in that Firestore Collection, a recomposition of any subscribed
18 | * [RecomposeScope]s will be scheduled.
19 | *
20 | * @see [State]
21 | * @see [collectionStateOf]
22 | */
23 | interface CollectionState : State, LifecycleObserver {
24 | override val value: FirestoreCollection
25 | fun startListening()
26 | fun stopListening()
27 |
28 | operator fun component1(): FirestoreCollection
29 | }
30 |
31 | /**
32 | * Return a new [CollectionState] initialized with the passed [query]
33 | *
34 | * The CollectionState class is a single value holder whose reads are observed by
35 | * Compose.
36 | *
37 | * @param query the collection query to be observed
38 | * @param lifecycleOwner the lifecycle owner that the state should react to
39 | *
40 | * @see State
41 | * @see CollectionState
42 | */
43 | fun collectionStateOf(
44 | query: Query,
45 | lifecycleOwner: LifecycleOwner? = null
46 | ) = object : CollectionState {
47 | private var listener: ListenerRegistration? = null
48 | private var valueState: FirestoreCollection by mutableStateOf(FirestoreCollection.Loading)
49 |
50 | override val value: FirestoreCollection
51 | get() = valueState
52 |
53 | init {
54 | lifecycleOwner?.lifecycle?.addObserver(this)
55 | }
56 |
57 | @OnLifecycleEvent(Lifecycle.Event.ON_START)
58 | override fun startListening() {
59 | if (listener == null) {
60 | listener = query.addSnapshotListener { value, error ->
61 | value?.let { querySnapshot ->
62 | valueState = FirestoreCollection.Snapshot(querySnapshot.documents)
63 | }
64 | error?.let { exception ->
65 | valueState = FirestoreCollection.Error(exception)
66 | }
67 | }
68 | }
69 | }
70 |
71 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
72 | override fun stopListening() {
73 | listener?.remove()
74 | }
75 |
76 | override fun component1(): FirestoreCollection = value
77 | }
78 |
79 |
--------------------------------------------------------------------------------
/firestore/src/main/java/io/github/rosariopfernandes/firebasecompose/firestore/DocumentState.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.firestore
2 |
3 | import androidx.compose.runtime.getValue
4 | import androidx.compose.runtime.mutableStateOf
5 | import androidx.compose.runtime.setValue
6 | import androidx.compose.runtime.State
7 | import androidx.lifecycle.Lifecycle
8 | import androidx.lifecycle.LifecycleObserver
9 | import androidx.lifecycle.LifecycleOwner
10 | import androidx.lifecycle.OnLifecycleEvent
11 | import com.google.firebase.firestore.DocumentReference
12 | import com.google.firebase.firestore.ListenerRegistration
13 |
14 | /**
15 | * A value holder where reads to the [value] property during the execution of a [Composable]
16 | * function, the current [RecomposeScope] will be subscribed to changes of that value. When the
17 | * [value] property is changed in that Firestore Document, a recomposition of any subscribed
18 | * [RecomposeScope]s will be scheduled.
19 | *
20 | * @see [State]
21 | * @see [documentStateOf]
22 | */
23 | interface DocumentState : State, LifecycleObserver {
24 | override val value: FirestoreDocument
25 | fun startListening()
26 | fun stopListening()
27 | operator fun component1(): FirestoreDocument
28 | }
29 |
30 | /**
31 | * Return a new [DocumentState] initialized with the passed [documentReference]
32 | *
33 | * The DocumentState class is a single value holder whose reads are observed by
34 | * Compose.
35 | *
36 | * @param documentReference the document to be observed
37 | * @param lifecycleOwner the lifecycle owner that the state should react to
38 | *
39 | * @see State
40 | * @see DocumentState
41 | */
42 | fun documentStateOf(
43 | documentReference: DocumentReference,
44 | lifecycleOwner: LifecycleOwner? = null
45 | ) = object : DocumentState {
46 | private var listener: ListenerRegistration? = null
47 | private var snapshotState: FirestoreDocument by mutableStateOf(FirestoreDocument.Loading)
48 |
49 | override val value: FirestoreDocument
50 | get() = snapshotState
51 |
52 | init {
53 | lifecycleOwner?.lifecycle?.addObserver(this)
54 | }
55 |
56 | @OnLifecycleEvent(Lifecycle.Event.ON_START)
57 | override fun startListening() {
58 | if (listener == null) {
59 | listener = documentReference.addSnapshotListener { value, error ->
60 | value?.let { snapshot ->
61 | snapshotState = FirestoreDocument.Snapshot(snapshot)
62 | }
63 | error?.let { exception ->
64 | snapshotState = FirestoreDocument.Error(exception)
65 | }
66 | }
67 | }
68 | }
69 |
70 | @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
71 | override fun stopListening() {
72 | listener?.remove()
73 | }
74 |
75 | override fun component1() = value
76 | }
77 |
78 |
--------------------------------------------------------------------------------
/firestore/src/main/java/io/github/rosariopfernandes/firebasecompose/firestore/FirestoreCollection.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.firestore
2 |
3 | import com.google.firebase.firestore.DocumentSnapshot
4 |
5 | sealed class FirestoreCollection {
6 | data class Snapshot(val list: List) : FirestoreCollection()
7 | data class Error(val exception: Exception) : FirestoreCollection()
8 | object Loading : FirestoreCollection()
9 | }
--------------------------------------------------------------------------------
/firestore/src/main/java/io/github/rosariopfernandes/firebasecompose/firestore/FirestoreDocument.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.firestore
2 |
3 | import com.google.firebase.firestore.DocumentSnapshot
4 |
5 | sealed class FirestoreDocument {
6 | data class Snapshot(val snapshot: DocumentSnapshot) : FirestoreDocument()
7 | data class Error(val exception: Exception) : FirestoreDocument()
8 | object Loading : FirestoreDocument()
9 | }
10 |
--------------------------------------------------------------------------------
/firestore/src/test/java/io/github/rosariopfernandes/firebasecompose/firestore/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.rosariopfernandes.firebasecompose.firestore
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/thatfiredev/firebase-compose/c6eebe2e6d0c9aeafed704a8634b94025e13da59/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sat Feb 13 19:02:30 CAT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https://services.gradle.org/distributions/gradle-6.8.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/jitpack.yml:
--------------------------------------------------------------------------------
1 | jdk:
2 | - openjdk11
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = "Firebase Compose"
2 | //include ':demo'
3 | include ':firestore'
4 | include ':database'
5 |
--------------------------------------------------------------------------------