├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── dev │ │ └── shreyaspatil │ │ └── firebase │ │ └── coroutines │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── dev │ │ │ └── shreyaspatil │ │ │ └── firebase │ │ │ └── coroutines │ │ │ ├── Constants.kt │ │ │ ├── State.kt │ │ │ ├── model │ │ │ └── Post.kt │ │ │ ├── repository │ │ │ └── PostsRepository.kt │ │ │ └── ui │ │ │ └── main │ │ │ ├── MainActivity.kt │ │ │ ├── MainViewModel.kt │ │ │ └── MainViewModelFactory.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── dev │ └── shreyaspatil │ └── firebase │ └── coroutines │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .DS_Store 5 | /build 6 | /captures 7 | .externalNativeBuild 8 | .cxx 9 | /.idea/ 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase Flow Example 2 | A sample android application which demonstrates use of Kotlin Coroutines Flow with Firebase Cloud Firestore. 3 | 4 | ## How to setup? 5 | - Setup project on Firebase Console. 6 | - Download `google-services.json` and put it in [`/app`](/app) directory. 7 | - Run the application. 8 | 9 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | /google-services.json 3 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'com.google.gms.google-services' 5 | 6 | android { 7 | compileSdkVersion 29 8 | buildToolsVersion "29.0.3" 9 | 10 | defaultConfig { 11 | applicationId "dev.shreyaspatil.firebase.coroutines" 12 | minSdkVersion 21 13 | targetSdkVersion 29 14 | versionCode 1 15 | versionName "1.0" 16 | 17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | 27 | buildFeatures { 28 | viewBinding { 29 | enabled = true 30 | } 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | 37 | // Kotlin 38 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 39 | 40 | // Kotlin Coroutines 41 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5" 42 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5" 43 | implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.5' 44 | 45 | // Android 46 | implementation 'androidx.appcompat:appcompat:1.1.0' 47 | implementation 'androidx.core:core-ktx:1.2.0' 48 | implementation 'androidx.constraintlayout:constraintlayout:1.1.3' 49 | 50 | // ViewModel 51 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0" 52 | 53 | // Firebase Cloud Firestore (Kotlin) 54 | implementation 'com.google.firebase:firebase-firestore-ktx:21.4.2' 55 | 56 | testImplementation 'junit:junit:4.12' 57 | androidTestImplementation 'androidx.test.ext:junit:1.1.1' 58 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' 59 | } 60 | -------------------------------------------------------------------------------- /app/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 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/dev/shreyaspatil/firebase/coroutines/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("dev.shreyaspatil.firebase.coroutines", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/Constants.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines 2 | 3 | object Constants { 4 | const val COLLECTION_POST = "posts" 5 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/State.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines 2 | 3 | sealed class State { 4 | class Loading : State() 5 | data class Success(val data: T) : State() 6 | data class Failed(val message: String) : State() 7 | 8 | companion object { 9 | fun loading() = Loading() 10 | fun success(data: T) = Success(data) 11 | fun failed(message: String) = Failed(message) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/model/Post.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines.model 2 | 3 | data class Post( 4 | val postContent: String? = null, 5 | val postAuthor: String? = null 6 | ) 7 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/repository/PostsRepository.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines.repository 2 | 3 | import com.google.firebase.firestore.DocumentReference 4 | import com.google.firebase.firestore.FirebaseFirestore 5 | import dev.shreyaspatil.firebase.coroutines.Constants 6 | import dev.shreyaspatil.firebase.coroutines.State 7 | import dev.shreyaspatil.firebase.coroutines.model.Post 8 | import kotlinx.coroutines.Dispatchers 9 | import kotlinx.coroutines.ExperimentalCoroutinesApi 10 | import kotlinx.coroutines.flow.catch 11 | import kotlinx.coroutines.flow.flow 12 | import kotlinx.coroutines.flow.flowOn 13 | import kotlinx.coroutines.tasks.await 14 | 15 | /** 16 | * Repository for the data of posts. 17 | * This will be a single source of data throughout the application. 18 | */ 19 | @ExperimentalCoroutinesApi 20 | class PostsRepository { 21 | 22 | private val mPostsCollection = 23 | FirebaseFirestore.getInstance().collection(Constants.COLLECTION_POST) 24 | 25 | /** 26 | * Returns Flow of [State] which retrieves all posts from cloud firestore collection. 27 | */ 28 | fun getAllPosts() = flow>> { 29 | 30 | // Emit loading state 31 | emit(State.loading()) 32 | 33 | val snapshot = mPostsCollection.get().await() 34 | val posts = snapshot.toObjects(Post::class.java) 35 | 36 | // Emit success state with data 37 | emit(State.success(posts)) 38 | 39 | }.catch { 40 | // If exception is thrown, emit failed state along with message. 41 | emit(State.failed(it.message.toString())) 42 | }.flowOn(Dispatchers.IO) 43 | 44 | /** 45 | * Adds post [post] into the cloud firestore collection. 46 | * @return The Flow of [State] which will store state of current action. 47 | */ 48 | fun addPost(post: Post) = flow> { 49 | 50 | // Emit loading state 51 | emit(State.loading()) 52 | 53 | val postRef = mPostsCollection.add(post).await() 54 | 55 | // Emit success state with post reference 56 | emit(State.success(postRef)) 57 | 58 | }.catch { 59 | // If exception is thrown, emit failed state along with message. 60 | emit(State.failed(it.message.toString())) 61 | }.flowOn(Dispatchers.IO) 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines.ui.main 2 | 3 | import android.os.Bundle 4 | import android.view.View 5 | import android.widget.Toast 6 | import androidx.appcompat.app.AppCompatActivity 7 | import androidx.lifecycle.ViewModelProvider 8 | import dev.shreyaspatil.firebase.coroutines.State 9 | import dev.shreyaspatil.firebase.coroutines.databinding.ActivityMainBinding 10 | import dev.shreyaspatil.firebase.coroutines.model.Post 11 | import kotlinx.coroutines.* 12 | import kotlinx.coroutines.flow.collect 13 | 14 | @InternalCoroutinesApi 15 | @ExperimentalCoroutinesApi 16 | class MainActivity : AppCompatActivity(), View.OnClickListener { 17 | 18 | private lateinit var viewModel: MainViewModel 19 | 20 | private lateinit var binding: ActivityMainBinding 21 | 22 | // Coroutine Scope 23 | private val uiScope = CoroutineScope(Dispatchers.Main) 24 | 25 | override fun onCreate(savedInstanceState: Bundle?) { 26 | super.onCreate(savedInstanceState) 27 | 28 | binding = ActivityMainBinding.inflate(layoutInflater) 29 | setContentView(binding.root) 30 | 31 | viewModel = ViewModelProvider(this, MainViewModelFactory()) 32 | .get(MainViewModel::class.java) 33 | 34 | binding.buttonLoad.setOnClickListener(this) 35 | binding.buttonAdd.setOnClickListener(this) 36 | } 37 | 38 | private suspend fun loadPosts() { 39 | viewModel.getAllPosts().collect { state -> 40 | when (state) { 41 | is State.Loading -> { 42 | showToast("Loading") 43 | } 44 | 45 | is State.Success -> { 46 | val postText = state.data.joinToString("\n") { 47 | "${it.postContent} ~ ${it.postAuthor}" 48 | } 49 | 50 | binding.textPostContent.text = postText 51 | } 52 | 53 | is State.Failed -> showToast("Failed! ${state.message}") 54 | } 55 | } 56 | } 57 | 58 | private suspend fun addPost(post: Post) { 59 | viewModel.addPost(post).collect { state -> 60 | when (state) { 61 | is State.Loading -> { 62 | showToast("Loading") 63 | binding.buttonAdd.isEnabled = false 64 | } 65 | 66 | is State.Success -> { 67 | showToast("Posted") 68 | binding.fieldPostContent.setText("") 69 | binding.buttonAdd.isEnabled = true 70 | } 71 | 72 | is State.Failed -> { 73 | showToast("Failed! ${state.message}") 74 | binding.buttonAdd.isEnabled = true 75 | } 76 | } 77 | } 78 | } 79 | 80 | override fun onClick(v: View?) { 81 | when (v!!.id) { 82 | binding.buttonLoad.id -> { 83 | uiScope.launch { 84 | loadPosts() 85 | } 86 | } 87 | 88 | binding.buttonAdd.id -> { 89 | uiScope.launch { 90 | addPost( 91 | Post( 92 | postContent = binding.fieldPostContent.text.toString(), 93 | postAuthor = "Anonymous" 94 | ) 95 | ) 96 | } 97 | } 98 | } 99 | } 100 | 101 | private fun showToast(message: String) { 102 | Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT).show() 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines.ui.main 2 | 3 | import androidx.lifecycle.ViewModel 4 | import dev.shreyaspatil.firebase.coroutines.model.Post 5 | import dev.shreyaspatil.firebase.coroutines.repository.PostsRepository 6 | import kotlinx.coroutines.ExperimentalCoroutinesApi 7 | 8 | @ExperimentalCoroutinesApi 9 | class MainViewModel(private val repository: PostsRepository) : ViewModel() { 10 | 11 | fun getAllPosts() = repository.getAllPosts() 12 | 13 | fun addPost(post: Post) = repository.addPost(post) 14 | } -------------------------------------------------------------------------------- /app/src/main/java/dev/shreyaspatil/firebase/coroutines/ui/main/MainViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.shreyaspatil.firebase.coroutines.ui.main 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import dev.shreyaspatil.firebase.coroutines.repository.PostsRepository 6 | import kotlinx.coroutines.ExperimentalCoroutinesApi 7 | 8 | @ExperimentalCoroutinesApi 9 | class MainViewModelFactory : ViewModelProvider.Factory { 10 | 11 | override fun create(modelClass: Class): T { 12 | return modelClass.getConstructor(PostsRepository::class.java) 13 | .newInstance(PostsRepository()) 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 22 | 23 |