├── .gitignore ├── .idea ├── .gitignore ├── compiler.xml ├── gradle.xml ├── misc.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── google-services.json ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── firebasewithmvvm │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── firebasewithmvvm │ │ │ ├── MainActivity.kt │ │ │ ├── MyApplication.kt │ │ │ ├── data │ │ │ ├── model │ │ │ │ ├── Note.kt │ │ │ │ ├── Task.kt │ │ │ │ └── User.kt │ │ │ └── repository │ │ │ │ ├── AuthRepository.kt │ │ │ │ ├── AuthRepositoryImp.kt │ │ │ │ ├── NoteRepository.kt │ │ │ │ ├── NoteRepositoryImp.kt │ │ │ │ ├── TaskRepository.kt │ │ │ │ └── TaskRepositoryImp.kt │ │ │ ├── di │ │ │ ├── AppModule.kt │ │ │ ├── FirebaseModule.kt │ │ │ └── RepositoryModule.kt │ │ │ ├── ui │ │ │ ├── auth │ │ │ │ ├── AuthViewModel.kt │ │ │ │ ├── ForgotPasswordFragment.kt │ │ │ │ ├── LoginFragment.kt │ │ │ │ └── RegisterFragment.kt │ │ │ ├── home │ │ │ │ ├── HomeFragment.kt │ │ │ │ └── HomePagerAdapter.kt │ │ │ ├── note │ │ │ │ ├── ImageListingAdapter.kt │ │ │ │ ├── NoteDetailFragment.kt │ │ │ │ ├── NoteListingAdapter.kt │ │ │ │ ├── NoteListingFragment.kt │ │ │ │ └── NoteViewModel.kt │ │ │ └── task │ │ │ │ ├── CreateTaskFragment.kt │ │ │ │ ├── TaskListingAdapter.kt │ │ │ │ ├── TaskListingFragment.kt │ │ │ │ └── TaskViewModel.kt │ │ │ └── util │ │ │ ├── Constants.kt │ │ │ ├── Extensions.kt │ │ │ └── UiState.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── black_rect_bg.xml │ │ ├── black_white_rect_bg.xml │ │ ├── ic_baseline_add_24.xml │ │ ├── ic_baseline_add_circle_24.xml │ │ ├── ic_baseline_arrow_back_ios_24.xml │ │ ├── ic_baseline_cancel_24.xml │ │ ├── ic_baseline_check_24.xml │ │ ├── ic_baseline_delete_24.xml │ │ ├── ic_baseline_edit_24.xml │ │ ├── ic_baseline_exit_to_app_24.xml │ │ └── ic_launcher_background.xml │ │ ├── font │ │ ├── titillium_web_black.ttf │ │ ├── titillium_web_bold.ttf │ │ ├── titillium_web_light.ttf │ │ └── titillium_web_regular.ttf │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── add_tag_dialog.xml │ │ ├── fragment_create_task.xml │ │ ├── fragment_forgot_password.xml │ │ ├── fragment_home.xml │ │ ├── fragment_login.xml │ │ ├── fragment_note_detail.xml │ │ ├── fragment_note_listing.xml │ │ ├── fragment_register.xml │ │ ├── fragment_task_listing.xml │ │ ├── image_layout.xml │ │ ├── item_chip.xml │ │ ├── item_note_layout.xml │ │ ├── item_tab_layout.xml │ │ └── task_layout.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 │ │ ├── navigation │ │ ├── app_navigation.xml │ │ └── home_navigation.xml │ │ ├── values-night │ │ └── themes.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ └── test │ └── java │ └── com │ └── example │ └── firebasewithmvvm │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── screenshots ├── note-taking-detail.png ├── note-taking-forgotpassword.png ├── note-taking-listing.png ├── note-taking-login.png └── note-taking-register.png └── 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/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Firebase with MVVM 2 | 3 | In this project we will learn about Firebase Implementation with MVVM Architecture. It is a basic level Course and will go with project based approach so can understand better that how the things are working. 4 | 5 | ## Topics 6 | 7 | - [Introduction](https://www.youtube.com/watch?v=MNomjZf-8C8&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=1&t=71s) 8 | - [Github Integration](https://www.youtube.com/watch?v=-os2YCijeIA&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=2) 9 | - [Firebase Implementation](https://www.youtube.com/watch?v=jNrSNyNGskg&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=3) 10 | - [Firestore Connection Testing](https://www.youtube.com/watch?v=vSFCqzc2dF8&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=4) 11 | - [Jetpack Navigation Component](https://www.youtube.com/watch?v=tOYHlI_by64&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=5) 12 | - [MVVM Implementation](https://www.youtube.com/watch?v=jy8QzgW1oYk&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=7) 13 | - [Dagger Hilt](https://www.youtube.com/watch?v=K65IyCMUbZg&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=8) 14 | - [Resource Class to Manage Ui States](https://www.youtube.com/watch?v=kv1YWtl9ILM&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=9) 15 | - [Add Note & Listing Notes in RecyclerView](https://www.youtube.com/watch?v=7ZNk87k441U&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=10) 16 | - [Update Note](https://www.youtube.com/watch?v=oHvPmOEKpjc&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=11) 17 | - [Delete Note](https://www.youtube.com/watch?v=ngvPIiDRRv0&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=12) 18 | - [Migrating old design to new (Branch: modification/design)](https://www.youtube.com/watch?v=DYCWfCOohsQ&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=14) 19 | - [Explaining old design to new design (Branch: modification/design)](https://www.youtube.com/watch?v=dDvcGichH04&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=15) 20 | - [Divider Line Overlapping Chipgroup Ui Bug (Branch: modification/design)](https://www.youtube.com/watch?v=T9mk9ivhIOw&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=16) 21 | - [Register with email/password (Branch: feature/register)](https://www.youtube.com/watch?v=xsEDL0Y3Ays&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=17) 22 | - [Login with email/password (Branch: feature/login)](https://www.youtube.com/watch?v=r48IHaYEJTk&list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL&index=18) 23 | - [Logout (Branch: feature/logout)](https://www.youtube.com/watch?v=uUMFHZEzGQE) 24 | - [Session (Branch: feature/session-handling)](https://youtu.be/uP2R6Trij4A) 25 | - [WhereEqualTo User Notes (Branch: feature/user-notes)](https://youtu.be/UWPz6344gb0) 26 | - [Pick Image (Branch: feature/pick-images)](https://youtu.be/UWPz6344gb0) 27 | - [Upload single image to Firebase Storage Notes (Branch: feature/upload-single-image)](https://youtu.be/UWPz6344gb0) 28 | - [Upload multiple images to Firebase Storage (Branch: feature/upload-multiple-images)](https://youtu.be/UWPz6344gb0) 29 | - [Viewpager2, TabLayout & Navigation Global Action (Branch: design/tablayout-viewpager2)](https://youtu.be/meJIEA_RalU) 30 | - [Create Task using Firebase Database (Branch: feature/create-task)](https://youtu.be/TJOw6vFSPrE) 31 | - [Listing Task using Firebase Database (Branch: feature/task-listing)](https://www.youtube.com/watch?v=tZ7tSXamPn0) 32 | 33 | 34 | ## Branches 35 | - **master** has complete code with new design. 36 | - **feature/navigation-component** has related to navigation component 37 | - **dagger-hilt-impl** has related to dagger hilt implementation 38 | - **feature/add-note** has related to add note 39 | - **feature/delete-note** has related to delete note 40 | - **old-design** has complete old code with old design. 41 | - **modification/design** has complete code with new design, refacotred code and modification in logic etc. 42 | - **feature/regsiter** has related to register with email and password. 43 | - **feature/login** has related to login with email and password. 44 | - **feature/logout** has related to logout. 45 | - **feature/session-handling** has related to session handling 46 | - **feature/user-notes** has related to getting notes based on user id to see its own notes. 47 | - **(feature/pick-images)** has related to pick image from gallery. 48 | - **(feature/upload-single-image)** has related to upload single image to firebase storage. 49 | - **(feature/upload-multiple-images)** has related to upload multiple images to firebase storage. 50 | - **(design/tablayout-viewpager2)** has related to viewpager2, tablayout and jetpack navigation global action. 51 | - **(feature/create-task)** has related to creating a task using firebase database 52 | - **(feature/task-listing)** has related to listing of task from firebase database 53 | 54 | 55 | ## Youtube Playlist 56 | [Click for the whole course playlist](https://www.youtube.com/playlist?list=PLIIWAqaTrNlg7q0cfajkBj8OwG60qpBVL) 57 | 58 | 59 | ## Articles 60 | - [Upload multiple images to Firebase Storage](https://realtimecoding.com/uploading-multiple-files-to-firebasestorage) 61 | 62 | ## Demo 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 |
LoginRegisterForgot Password
76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 |
Note Listing ScreenNote Detail/Create/Update Screen
88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'org.jetbrains.kotlin.android' 4 | id 'com.google.gms.google-services' 5 | id 'dagger.hilt.android.plugin' 6 | id 'kotlin-kapt' 7 | id 'kotlin-parcelize' 8 | } 9 | 10 | android { 11 | compileSdk 32 12 | 13 | defaultConfig { 14 | applicationId "com.example.firebasewithmvvm" 15 | minSdk 21 16 | targetSdk 32 17 | versionCode 1 18 | versionName "1.0" 19 | 20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 21 | } 22 | 23 | buildFeatures{ 24 | viewBinding true 25 | } 26 | 27 | buildTypes { 28 | release { 29 | minifyEnabled false 30 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 31 | } 32 | } 33 | compileOptions { 34 | sourceCompatibility JavaVersion.VERSION_1_8 35 | targetCompatibility JavaVersion.VERSION_1_8 36 | } 37 | kotlinOptions { 38 | jvmTarget = '1.8' 39 | } 40 | } 41 | 42 | dependencies { 43 | 44 | implementation 'androidx.core:core-ktx:1.7.0' 45 | implementation 'androidx.appcompat:appcompat:1.4.1' 46 | implementation 'com.google.android.material:material:1.6.0' 47 | implementation 'androidx.viewpager2:viewpager2:1.1.0-beta01' 48 | implementation 'androidx.constraintlayout:constraintlayout:2.1.3' 49 | 50 | //Jetpack Navigation Component 51 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 52 | implementation 'androidx.navigation:navigation-fragment-ktx:2.4.2' 53 | implementation 'androidx.navigation:navigation-ui-ktx:2.4.2' 54 | 55 | // LiveData 56 | implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.4.1' 57 | //fragment 58 | implementation 'androidx.fragment:fragment-ktx:1.4.1' 59 | 60 | //Dagger Hilt 61 | implementation 'com.google.dagger:hilt-android:2.40.5' 62 | kapt 'com.google.dagger:hilt-android-compiler:2.40.5' 63 | implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha03' 64 | kapt 'androidx.hilt:hilt-compiler:1.0.0' 65 | 66 | //Testing 67 | testImplementation 'junit:junit:4.13.2' 68 | androidTestImplementation 'androidx.test.ext:junit:1.1.3' 69 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' 70 | 71 | //Firebase 72 | implementation 'com.google.firebase:firebase-firestore:24.1.2' 73 | implementation 'com.google.firebase:firebase-auth:19.2.0' 74 | implementation 'com.google.firebase:firebase-storage-ktx:20.0.1' 75 | implementation 'com.google.firebase:firebase-database-ktx:20.0.5' 76 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.3.1" 77 | 78 | //Gson 79 | implementation 'com.google.code.gson:gson:2.8.9' 80 | 81 | //ImagePicker 82 | implementation 'com.github.dhaval2404:imagepicker:2.1' 83 | 84 | } -------------------------------------------------------------------------------- /app/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "1021082332979", 4 | "firebase_url": "https://fir-withmvvm-e94f9-default-rtdb.firebaseio.com", 5 | "project_id": "fir-withmvvm-e94f9", 6 | "storage_bucket": "fir-withmvvm-e94f9.appspot.com" 7 | }, 8 | "client": [ 9 | { 10 | "client_info": { 11 | "mobilesdk_app_id": "1:1021082332979:android:f7d6017747b75214bfd846", 12 | "android_client_info": { 13 | "package_name": "com.example.firebasewithmvvm" 14 | } 15 | }, 16 | "oauth_client": [ 17 | { 18 | "client_id": "1021082332979-mu58dugjrk432rcvtucf5pabplt7ba5q.apps.googleusercontent.com", 19 | "client_type": 1, 20 | "android_info": { 21 | "package_name": "com.example.firebasewithmvvm", 22 | "certificate_hash": "df7df0c00a41127195a615f7a254e36f8363ac8f" 23 | } 24 | }, 25 | { 26 | "client_id": "1021082332979-ukbbvg9og0fhl78npjsn5si03svg0fdj.apps.googleusercontent.com", 27 | "client_type": 3 28 | } 29 | ], 30 | "api_key": [ 31 | { 32 | "current_key": "AIzaSyBUezs8nPuYl5dxWwnlfQ9OTpUTtl9yDdA" 33 | } 34 | ], 35 | "services": { 36 | "appinvite_service": { 37 | "other_platform_oauth_client": [ 38 | { 39 | "client_id": "1021082332979-ukbbvg9og0fhl78npjsn5si03svg0fdj.apps.googleusercontent.com", 40 | "client_type": 3 41 | } 42 | ] 43 | } 44 | } 45 | } 46 | ], 47 | "configuration_version": "1" 48 | } -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/firebasewithmvvm/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm 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("com.example.firebasewithmvvm", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm 2 | 3 | import android.os.Bundle 4 | import android.util.Log 5 | import androidx.appcompat.app.AppCompatActivity 6 | import androidx.navigation.NavController 7 | import androidx.navigation.NavGraph 8 | import androidx.navigation.findNavController 9 | import androidx.navigation.fragment.NavHostFragment 10 | import com.google.android.gms.tasks.OnFailureListener 11 | import com.google.android.gms.tasks.OnSuccessListener 12 | import com.google.firebase.firestore.DocumentReference 13 | import com.google.firebase.firestore.FirebaseFirestore 14 | import dagger.hilt.android.AndroidEntryPoint 15 | 16 | 17 | @AndroidEntryPoint 18 | class MainActivity : AppCompatActivity() { 19 | 20 | lateinit var navController: NavController 21 | 22 | override fun onCreate(savedInstanceState: Bundle?) { 23 | super.onCreate(savedInstanceState) 24 | setContentView(R.layout.activity_main) 25 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host) as NavHostFragment 26 | navController = navHostFragment.navController 27 | } 28 | 29 | override fun onBackPressed() { 30 | super.onBackPressed() 31 | if (navController.currentDestination?.id == R.id.loginFragment){ 32 | moveTaskToBack(true) 33 | }else{ 34 | super.onBackPressed() 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm 2 | 3 | import android.app.Application 4 | import dagger.hilt.android.HiltAndroidApp 5 | 6 | @HiltAndroidApp 7 | class MyApplication: Application() -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/model/Note.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.model 2 | 3 | import android.os.Parcelable 4 | import com.google.firebase.firestore.ServerTimestamp 5 | import kotlinx.parcelize.Parcelize 6 | import java.util.* 7 | 8 | @Parcelize 9 | data class Note( 10 | var id: String = "", 11 | var user_id: String = "", 12 | val title: String = "", 13 | val description: String = "", 14 | val tags: MutableList = arrayListOf(), 15 | val images: List = arrayListOf(), 16 | @ServerTimestamp 17 | val date: Date = Date(), 18 | ) : Parcelable 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/model/Task.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.model 2 | 3 | import android.os.Parcelable 4 | import com.google.firebase.firestore.ServerTimestamp 5 | import kotlinx.parcelize.Parcelize 6 | import java.util.* 7 | 8 | @Parcelize 9 | data class Task( 10 | var id: String = "", 11 | var user_id: String = "", 12 | var description: String = "", 13 | val date: String = "", 14 | ) : Parcelable 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/model/User.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.model 2 | 3 | 4 | data class User( 5 | var id: String = "", 6 | val first_name: String = "", 7 | val last_name: String = "", 8 | val job_title: String = "", 9 | val email: String = "", 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/repository/AuthRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.repository 2 | 3 | import com.example.firebasewithmvvm.data.model.Note 4 | import com.example.firebasewithmvvm.data.model.User 5 | import com.example.firebasewithmvvm.util.UiState 6 | 7 | interface AuthRepository { 8 | fun registerUser(email: String, password: String, user: User, result: (UiState) -> Unit) 9 | fun updateUserInfo(user: User, result: (UiState) -> Unit) 10 | fun loginUser(email: String, password: String, result: (UiState) -> Unit) 11 | fun forgotPassword(email: String, result: (UiState) -> Unit) 12 | fun logout(result: () -> Unit) 13 | fun storeSession(id: String, result: (User?) -> Unit) 14 | fun getSession(result: (User?) -> Unit) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/repository/AuthRepositoryImp.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.repository 2 | 3 | import android.content.SharedPreferences 4 | import com.example.firebasewithmvvm.data.model.User 5 | import com.example.firebasewithmvvm.util.FireStoreCollection 6 | import com.example.firebasewithmvvm.util.SharedPrefConstants 7 | import com.example.firebasewithmvvm.util.UiState 8 | import com.google.firebase.auth.* 9 | import com.google.firebase.firestore.FirebaseFirestore 10 | import com.google.gson.Gson 11 | 12 | class AuthRepositoryImp( 13 | val auth: FirebaseAuth, 14 | val database: FirebaseFirestore, 15 | val appPreferences: SharedPreferences, 16 | val gson: Gson 17 | ) : AuthRepository { 18 | 19 | override fun registerUser( 20 | email: String, 21 | password: String, 22 | user: User, result: (UiState) -> Unit 23 | ) { 24 | auth.createUserWithEmailAndPassword(email,password) 25 | .addOnCompleteListener { 26 | if (it.isSuccessful){ 27 | user.id = it.result.user?.uid ?: "" 28 | updateUserInfo(user) { state -> 29 | when(state){ 30 | is UiState.Success -> { 31 | storeSession(id = it.result.user?.uid ?: "") { 32 | if (it == null){ 33 | result.invoke(UiState.Failure("User register successfully but session failed to store")) 34 | }else{ 35 | result.invoke( 36 | UiState.Success("User register successfully!") 37 | ) 38 | } 39 | } 40 | } 41 | is UiState.Failure -> { 42 | result.invoke(UiState.Failure(state.error)) 43 | } 44 | } 45 | } 46 | }else{ 47 | try { 48 | throw it.exception ?: java.lang.Exception("Invalid authentication") 49 | } catch (e: FirebaseAuthWeakPasswordException) { 50 | result.invoke(UiState.Failure("Authentication failed, Password should be at least 6 characters")) 51 | } catch (e: FirebaseAuthInvalidCredentialsException) { 52 | result.invoke(UiState.Failure("Authentication failed, Invalid email entered")) 53 | } catch (e: FirebaseAuthUserCollisionException) { 54 | result.invoke(UiState.Failure("Authentication failed, Email already registered.")) 55 | } catch (e: Exception) { 56 | result.invoke(UiState.Failure(e.message)) 57 | } 58 | } 59 | } 60 | .addOnFailureListener { 61 | result.invoke( 62 | UiState.Failure( 63 | it.localizedMessage 64 | ) 65 | ) 66 | } 67 | } 68 | 69 | override fun updateUserInfo(user: User, result: (UiState) -> Unit) { 70 | val document = database.collection(FireStoreCollection.USER).document(user.id) 71 | document 72 | .set(user) 73 | .addOnSuccessListener { 74 | result.invoke( 75 | UiState.Success("User has been update successfully") 76 | ) 77 | } 78 | .addOnFailureListener { 79 | result.invoke( 80 | UiState.Failure( 81 | it.localizedMessage 82 | ) 83 | ) 84 | } 85 | } 86 | 87 | override fun loginUser( 88 | email: String, 89 | password: String, 90 | result: (UiState) -> Unit) { 91 | auth.signInWithEmailAndPassword(email,password) 92 | .addOnCompleteListener { task -> 93 | if (task.isSuccessful) { 94 | storeSession(id = task.result.user?.uid ?: ""){ 95 | if (it == null){ 96 | result.invoke(UiState.Failure("Failed to store local session")) 97 | }else{ 98 | result.invoke(UiState.Success("Login successfully!")) 99 | } 100 | } 101 | } 102 | }.addOnFailureListener { 103 | result.invoke(UiState.Failure("Authentication failed, Check email and password")) 104 | } 105 | } 106 | 107 | override fun forgotPassword(email: String, result: (UiState) -> Unit) { 108 | auth.sendPasswordResetEmail(email) 109 | .addOnCompleteListener { task -> 110 | if (task.isSuccessful) { 111 | result.invoke(UiState.Success("Email has been sent")) 112 | 113 | } else { 114 | result.invoke(UiState.Failure(task.exception?.message)) 115 | } 116 | }.addOnFailureListener { 117 | result.invoke(UiState.Failure("Authentication failed, Check email")) 118 | } 119 | } 120 | 121 | override fun logout(result: () -> Unit) { 122 | auth.signOut() 123 | appPreferences.edit().putString(SharedPrefConstants.USER_SESSION,null).apply() 124 | result.invoke() 125 | } 126 | 127 | override fun storeSession(id: String, result: (User?) -> Unit) { 128 | database.collection(FireStoreCollection.USER).document(id) 129 | .get() 130 | .addOnCompleteListener { 131 | if (it.isSuccessful){ 132 | val user = it.result.toObject(User::class.java) 133 | appPreferences.edit().putString(SharedPrefConstants.USER_SESSION,gson.toJson(user)).apply() 134 | result.invoke(user) 135 | }else{ 136 | result.invoke(null) 137 | } 138 | } 139 | .addOnFailureListener { 140 | result.invoke(null) 141 | } 142 | } 143 | 144 | override fun getSession(result: (User?) -> Unit) { 145 | val user_str = appPreferences.getString(SharedPrefConstants.USER_SESSION,null) 146 | if (user_str == null){ 147 | result.invoke(null) 148 | }else{ 149 | val user = gson.fromJson(user_str,User::class.java) 150 | result.invoke(user) 151 | } 152 | } 153 | 154 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/repository/NoteRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.repository 2 | 3 | import android.net.Uri 4 | import com.example.firebasewithmvvm.data.model.Note 5 | import com.example.firebasewithmvvm.data.model.User 6 | import com.example.firebasewithmvvm.util.UiState 7 | 8 | interface NoteRepository { 9 | fun getNotes(user: User?, result: (UiState>) -> Unit) 10 | fun addNote(note: Note, result: (UiState>) -> Unit) 11 | fun updateNote(note: Note, result: (UiState) -> Unit) 12 | fun deleteNote(note: Note, result: (UiState) -> Unit) 13 | suspend fun uploadSingleFile(fileUri: Uri, onResult: (UiState) -> Unit) 14 | suspend fun uploadMultipleFile(fileUri: List, onResult: (UiState>) -> Unit) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/repository/NoteRepositoryImp.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.repository 2 | 3 | import android.content.SharedPreferences 4 | import android.net.Uri 5 | import com.example.firebasewithmvvm.data.model.Note 6 | import com.example.firebasewithmvvm.data.model.User 7 | import com.example.firebasewithmvvm.util.FireStoreCollection 8 | import com.example.firebasewithmvvm.util.FireStoreDocumentField 9 | import com.example.firebasewithmvvm.util.FirebaseStorageConstants.NOTE_IMAGES 10 | import com.example.firebasewithmvvm.util.SharedPrefConstants 11 | import com.example.firebasewithmvvm.util.UiState 12 | import com.google.firebase.FirebaseException 13 | import com.google.firebase.firestore.FirebaseFirestore 14 | import com.google.firebase.firestore.FirebaseFirestoreException 15 | import com.google.firebase.firestore.Query 16 | import com.google.firebase.storage.StorageReference 17 | import kotlinx.coroutines.Dispatchers 18 | import kotlinx.coroutines.async 19 | import kotlinx.coroutines.awaitAll 20 | import kotlinx.coroutines.tasks.await 21 | import kotlinx.coroutines.withContext 22 | 23 | class NoteRepositoryImp( 24 | val database: FirebaseFirestore, 25 | val storageReference: StorageReference 26 | ) : NoteRepository { 27 | 28 | override fun getNotes(user: User?, result: (UiState>) -> Unit) { 29 | database.collection(FireStoreCollection.NOTE) 30 | .whereEqualTo(FireStoreDocumentField.USER_ID,user?.id) 31 | .orderBy(FireStoreDocumentField.DATE, Query.Direction.DESCENDING) 32 | .get() 33 | .addOnSuccessListener { 34 | val notes = arrayListOf() 35 | for (document in it) { 36 | val note = document.toObject(Note::class.java) 37 | notes.add(note) 38 | } 39 | result.invoke( 40 | UiState.Success(notes) 41 | ) 42 | } 43 | .addOnFailureListener { 44 | result.invoke( 45 | UiState.Failure( 46 | it.localizedMessage 47 | ) 48 | ) 49 | } 50 | } 51 | 52 | override fun addNote(note: Note, result: (UiState>) -> Unit) { 53 | val document = database.collection(FireStoreCollection.NOTE).document() 54 | note.id = document.id 55 | document 56 | .set(note) 57 | .addOnSuccessListener { 58 | result.invoke( 59 | UiState.Success(Pair(note,"Note has been created successfully")) 60 | ) 61 | } 62 | .addOnFailureListener { 63 | result.invoke( 64 | UiState.Failure( 65 | it.localizedMessage 66 | ) 67 | ) 68 | } 69 | } 70 | 71 | override fun updateNote(note: Note, result: (UiState) -> Unit) { 72 | val document = database.collection(FireStoreCollection.NOTE).document(note.id) 73 | document 74 | .set(note) 75 | .addOnSuccessListener { 76 | result.invoke( 77 | UiState.Success("Note has been update successfully") 78 | ) 79 | } 80 | .addOnFailureListener { 81 | result.invoke( 82 | UiState.Failure( 83 | it.localizedMessage 84 | ) 85 | ) 86 | } 87 | } 88 | 89 | override fun deleteNote(note: Note, result: (UiState) -> Unit) { 90 | database.collection(FireStoreCollection.NOTE).document(note.id) 91 | .delete() 92 | .addOnSuccessListener { 93 | result.invoke(UiState.Success("Note successfully deleted!")) 94 | } 95 | .addOnFailureListener { e -> 96 | result.invoke(UiState.Failure(e.message)) 97 | } 98 | } 99 | 100 | override suspend fun uploadSingleFile(fileUri: Uri, onResult: (UiState) -> Unit) { 101 | try { 102 | val uri: Uri = withContext(Dispatchers.IO) { 103 | storageReference 104 | .putFile(fileUri) 105 | .await() 106 | .storage 107 | .downloadUrl 108 | .await() 109 | } 110 | onResult.invoke(UiState.Success(uri)) 111 | } catch (e: FirebaseException){ 112 | onResult.invoke(UiState.Failure(e.message)) 113 | }catch (e: Exception){ 114 | onResult.invoke(UiState.Failure(e.message)) 115 | } 116 | } 117 | 118 | override suspend fun uploadMultipleFile( 119 | fileUri: List, 120 | onResult: (UiState>) -> Unit 121 | ) { 122 | try { 123 | val uri: List = withContext(Dispatchers.IO) { 124 | fileUri.map { image -> 125 | async { 126 | storageReference.child(NOTE_IMAGES).child(image.lastPathSegment ?: "${System.currentTimeMillis()}") 127 | .putFile(image) 128 | .await() 129 | .storage 130 | .downloadUrl 131 | .await() 132 | } 133 | }.awaitAll() 134 | } 135 | onResult.invoke(UiState.Success(uri)) 136 | } catch (e: FirebaseException){ 137 | onResult.invoke(UiState.Failure(e.message)) 138 | }catch (e: Exception){ 139 | onResult.invoke(UiState.Failure(e.message)) 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/repository/TaskRepository.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.repository 2 | 3 | import com.example.firebasewithmvvm.data.model.Task 4 | import com.example.firebasewithmvvm.data.model.User 5 | import com.example.firebasewithmvvm.util.UiState 6 | 7 | interface TaskRepository { 8 | fun addTask(task: Task, result: (UiState>) -> Unit) 9 | fun updateTask(task: Task, result: (UiState>) -> Unit) 10 | fun deleteTask(task: Task, result: (UiState>) -> Unit) 11 | fun getTasks(user: User?, result: (UiState>) -> Unit) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/data/repository/TaskRepositoryImp.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.data.repository 2 | 3 | import com.example.firebasewithmvvm.data.model.Note 4 | import com.example.firebasewithmvvm.data.model.Task 5 | import com.example.firebasewithmvvm.data.model.User 6 | import com.example.firebasewithmvvm.util.FireDatabase 7 | import com.example.firebasewithmvvm.util.UiState 8 | import com.google.firebase.database.FirebaseDatabase 9 | 10 | class TaskRepositoryImp( 11 | val database: FirebaseDatabase 12 | ) : TaskRepository { 13 | 14 | override fun addTask(task: Task, result: (UiState>) -> Unit) { 15 | val reference = database.reference.child(FireDatabase.TASK).push() 16 | val uniqueKey = reference.key ?: "invalid" 17 | task.id = uniqueKey 18 | reference 19 | .setValue(task) 20 | .addOnSuccessListener { 21 | result.invoke( 22 | UiState.Success(Pair(task,"Task has been created successfully")) 23 | ) 24 | } 25 | .addOnFailureListener { 26 | result.invoke( 27 | UiState.Failure( 28 | it.localizedMessage 29 | ) 30 | ) 31 | } 32 | } 33 | 34 | override fun updateTask(task: Task, result: (UiState>) -> Unit) { 35 | val reference = database.reference.child(FireDatabase.TASK).child(task.id) 36 | reference 37 | .setValue(task) 38 | .addOnSuccessListener { 39 | result.invoke( 40 | UiState.Success(Pair(task,"Task has been updated successfully")) 41 | ) 42 | } 43 | .addOnFailureListener { 44 | result.invoke( 45 | UiState.Failure( 46 | it.localizedMessage 47 | ) 48 | ) 49 | } 50 | } 51 | 52 | override fun getTasks(user: User?, result: (UiState>) -> Unit) { 53 | val reference = database.reference.child(FireDatabase.TASK).orderByChild("user_id").equalTo(user?.id) 54 | reference.get() 55 | .addOnSuccessListener { 56 | val tasks = arrayListOf() 57 | for (item in it.children){ 58 | val task = item.getValue(Task::class.java) 59 | tasks.add(task) 60 | } 61 | result.invoke(UiState.Success(tasks.filterNotNull())) 62 | } 63 | .addOnFailureListener { 64 | result.invoke( 65 | UiState.Failure( 66 | it.localizedMessage 67 | ) 68 | ) 69 | } 70 | } 71 | 72 | override fun deleteTask(task: Task, result: (UiState>) -> Unit) { 73 | val reference = database.reference.child(FireDatabase.TASK).child(task.id) 74 | reference.removeValue() 75 | .addOnSuccessListener { 76 | result.invoke( 77 | UiState.Success(Pair(task,"Task has been deleted successfully")) 78 | ) 79 | } 80 | .addOnFailureListener { 81 | result.invoke( 82 | UiState.Failure( 83 | it.localizedMessage 84 | ) 85 | ) 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.di 2 | 3 | import android.content.Context 4 | import android.content.SharedPreferences 5 | import com.example.firebasewithmvvm.util.SharedPrefConstants 6 | import com.google.gson.Gson 7 | import dagger.Module 8 | import dagger.Provides 9 | import dagger.hilt.InstallIn 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import dagger.hilt.components.SingletonComponent 12 | import javax.inject.Singleton 13 | 14 | @InstallIn(SingletonComponent::class) 15 | @Module 16 | object AppModule { 17 | 18 | @Provides 19 | @Singleton 20 | fun provideSharedPref(@ApplicationContext context: Context): SharedPreferences { 21 | return context.getSharedPreferences(SharedPrefConstants.LOCAL_SHARED_PREF,Context.MODE_PRIVATE) 22 | } 23 | 24 | @Provides 25 | @Singleton 26 | fun provideGson(): Gson { 27 | return Gson() 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/di/FirebaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.di 2 | 3 | import com.example.firebasewithmvvm.util.FirebaseStorageConstants 4 | import com.google.firebase.auth.FirebaseAuth 5 | import com.google.firebase.database.FirebaseDatabase 6 | import com.google.firebase.firestore.FirebaseFirestore 7 | import com.google.firebase.storage.FirebaseStorage 8 | import com.google.firebase.storage.StorageReference 9 | import dagger.Module 10 | import dagger.Provides 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @InstallIn(SingletonComponent::class) 16 | @Module 17 | object FirebaseModule { 18 | 19 | @Provides 20 | @Singleton 21 | fun provideFirebaseDatabaseInstance(): FirebaseDatabase{ 22 | return FirebaseDatabase.getInstance() 23 | } 24 | 25 | @Provides 26 | @Singleton 27 | fun provideFireStoreInstance(): FirebaseFirestore{ 28 | return FirebaseFirestore.getInstance() 29 | } 30 | 31 | @Provides 32 | @Singleton 33 | fun provideFirebaseAuthInstance(): FirebaseAuth{ 34 | return FirebaseAuth.getInstance() 35 | } 36 | 37 | @Singleton 38 | @Provides 39 | fun provideFirebaseStroageInstance(): StorageReference { 40 | return FirebaseStorage.getInstance().getReference(FirebaseStorageConstants.ROOT_DIRECTORY) 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.di 2 | 3 | import android.content.SharedPreferences 4 | import com.example.firebasewithmvvm.data.repository.* 5 | import com.google.firebase.auth.FirebaseAuth 6 | import com.google.firebase.database.FirebaseDatabase 7 | import com.google.firebase.firestore.FirebaseFirestore 8 | import com.google.firebase.storage.StorageReference 9 | import com.google.gson.Gson 10 | import dagger.Module 11 | import dagger.Provides 12 | import dagger.hilt.InstallIn 13 | import dagger.hilt.components.SingletonComponent 14 | import javax.inject.Singleton 15 | 16 | @InstallIn(SingletonComponent::class) 17 | @Module 18 | object RepositoryModule { 19 | 20 | @Provides 21 | @Singleton 22 | fun provideNoteRepository( 23 | database: FirebaseFirestore, 24 | storageReference: StorageReference 25 | ): NoteRepository{ 26 | return NoteRepositoryImp(database,storageReference) 27 | } 28 | 29 | @Provides 30 | @Singleton 31 | fun provideTaskRepository( 32 | database: FirebaseDatabase 33 | ): TaskRepository{ 34 | return TaskRepositoryImp(database) 35 | } 36 | 37 | @Provides 38 | @Singleton 39 | fun provideAutghRepository( 40 | database: FirebaseFirestore, 41 | auth: FirebaseAuth, 42 | appPreferences: SharedPreferences, 43 | gson: Gson 44 | ): AuthRepository { 45 | return AuthRepositoryImp(auth,database,appPreferences,gson) 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/auth/AuthViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.auth 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.example.firebasewithmvvm.data.model.User 7 | import com.example.firebasewithmvvm.data.repository.AuthRepository 8 | import com.example.firebasewithmvvm.util.UiState 9 | import dagger.hilt.android.lifecycle.HiltViewModel 10 | import javax.inject.Inject 11 | 12 | @HiltViewModel 13 | class AuthViewModel @Inject constructor( 14 | val repository: AuthRepository 15 | ): ViewModel() { 16 | 17 | private val _register = MutableLiveData>() 18 | val register: LiveData> 19 | get() = _register 20 | 21 | private val _login = MutableLiveData>() 22 | val login: LiveData> 23 | get() = _login 24 | 25 | private val _forgotPassword = MutableLiveData>() 26 | val forgotPassword: LiveData> 27 | get() = _forgotPassword 28 | 29 | 30 | fun register( 31 | email: String, 32 | password: String, 33 | user: User 34 | ) { 35 | _register.value = UiState.Loading 36 | repository.registerUser( 37 | email = email, 38 | password = password, 39 | user = user 40 | ) { _register.value = it } 41 | } 42 | 43 | fun login( 44 | email: String, 45 | password: String 46 | ) { 47 | _login.value = UiState.Loading 48 | repository.loginUser( 49 | email, 50 | password 51 | ){ 52 | _login.value = it 53 | } 54 | } 55 | 56 | fun forgotPassword(email: String) { 57 | _forgotPassword.value = UiState.Loading 58 | repository.forgotPassword(email){ 59 | _forgotPassword.value = it 60 | } 61 | } 62 | 63 | fun logout(result: () -> Unit){ 64 | repository.logout(result) 65 | } 66 | 67 | fun getSession(result: (User?) -> Unit){ 68 | repository.getSession(result) 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/auth/ForgotPasswordFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.auth 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.viewModels 9 | import androidx.navigation.fragment.findNavController 10 | import com.example.firebasewithmvvm.R 11 | import com.example.firebasewithmvvm.databinding.FragmentForgotPasswordBinding 12 | import com.example.firebasewithmvvm.databinding.FragmentLoginBinding 13 | import com.example.firebasewithmvvm.util.* 14 | import dagger.hilt.android.AndroidEntryPoint 15 | 16 | 17 | @AndroidEntryPoint 18 | class ForgotPasswordFragment : Fragment() { 19 | 20 | val TAG: String = "ForgotPasswordFragment" 21 | lateinit var binding: FragmentForgotPasswordBinding 22 | val viewModel: AuthViewModel by viewModels() 23 | 24 | override fun onCreateView( 25 | inflater: LayoutInflater, container: ViewGroup?, 26 | savedInstanceState: Bundle? 27 | ): View? { 28 | binding = FragmentForgotPasswordBinding.inflate(layoutInflater) 29 | return binding.root 30 | } 31 | 32 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 33 | super.onViewCreated(view, savedInstanceState) 34 | observer() 35 | binding.forgotPassBtn.setOnClickListener { 36 | if (validation()){ 37 | viewModel.forgotPassword(binding.emailEt.text.toString()) 38 | } 39 | } 40 | } 41 | 42 | private fun observer(){ 43 | viewModel.forgotPassword.observe(viewLifecycleOwner) { state -> 44 | when(state){ 45 | is UiState.Loading -> { 46 | binding.forgotPassBtn.setText("") 47 | binding.forgotPassProgress.show() 48 | } 49 | is UiState.Failure -> { 50 | binding.forgotPassBtn.setText("Send") 51 | binding.forgotPassProgress.hide() 52 | toast(state.error) 53 | } 54 | is UiState.Success -> { 55 | binding.forgotPassBtn.setText("Send") 56 | binding.forgotPassProgress.hide() 57 | toast(state.data) 58 | } 59 | } 60 | } 61 | } 62 | 63 | fun validation(): Boolean { 64 | var isValid = true 65 | 66 | if (binding.emailEt.text.isNullOrEmpty()){ 67 | isValid = false 68 | toast(getString(R.string.enter_email)) 69 | }else{ 70 | if (!binding.emailEt.text.toString().isValidEmail()){ 71 | isValid = false 72 | toast(getString(R.string.invalid_email)) 73 | } 74 | } 75 | 76 | return isValid 77 | } 78 | 79 | 80 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/auth/LoginFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.auth 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.util.Log 6 | import androidx.fragment.app.Fragment 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.fragment.app.viewModels 11 | import androidx.navigation.NavGraph 12 | import androidx.navigation.fragment.findNavController 13 | import com.example.firebasewithmvvm.R 14 | import com.example.firebasewithmvvm.databinding.FragmentLoginBinding 15 | import com.example.firebasewithmvvm.databinding.FragmentRegisterBinding 16 | import com.example.firebasewithmvvm.util.* 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | @AndroidEntryPoint 20 | class LoginFragment : Fragment() { 21 | 22 | val TAG: String = "RegisterFragment" 23 | lateinit var binding: FragmentLoginBinding 24 | val viewModel: AuthViewModel by viewModels() 25 | 26 | override fun onCreateView( 27 | inflater: LayoutInflater, container: ViewGroup?, 28 | savedInstanceState: Bundle? 29 | ): View? { 30 | binding = FragmentLoginBinding.inflate(layoutInflater) 31 | return binding.root 32 | } 33 | 34 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 35 | super.onViewCreated(view, savedInstanceState) 36 | observer() 37 | binding.loginBtn.setOnClickListener { 38 | if (validation()) { 39 | viewModel.login( 40 | email = binding.emailEt.text.toString(), 41 | password = binding.passEt.text.toString() 42 | ) 43 | } 44 | } 45 | 46 | binding.forgotPassLabel.setOnClickListener { 47 | findNavController().navigate(R.id.action_loginFragment_to_forgotPasswordFragment) 48 | } 49 | 50 | binding.registerLabel.setOnClickListener { 51 | findNavController().navigate(R.id.action_loginFragment_to_registerFragment) 52 | } 53 | } 54 | 55 | fun observer(){ 56 | viewModel.login.observe(viewLifecycleOwner) { state -> 57 | when(state){ 58 | is UiState.Loading -> { 59 | binding.loginBtn.setText("") 60 | binding.loginProgress.show() 61 | } 62 | is UiState.Failure -> { 63 | binding.loginBtn.setText("Login") 64 | binding.loginProgress.hide() 65 | toast(state.error) 66 | } 67 | is UiState.Success -> { 68 | binding.loginBtn.setText("Login") 69 | binding.loginProgress.hide() 70 | toast(state.data) 71 | findNavController().navigate(R.id.action_loginFragment_to_home_navigation) 72 | } 73 | } 74 | } 75 | } 76 | 77 | fun validation(): Boolean { 78 | var isValid = true 79 | 80 | if (binding.emailEt.text.isNullOrEmpty()){ 81 | isValid = false 82 | toast(getString(R.string.enter_email)) 83 | }else{ 84 | if (!binding.emailEt.text.toString().isValidEmail()){ 85 | isValid = false 86 | toast(getString(R.string.invalid_email)) 87 | } 88 | } 89 | if (binding.passEt.text.isNullOrEmpty()){ 90 | isValid = false 91 | toast(getString(R.string.enter_password)) 92 | }else{ 93 | if (binding.passEt.text.toString().length < 8){ 94 | isValid = false 95 | toast(getString(R.string.invalid_password)) 96 | } 97 | } 98 | return isValid 99 | } 100 | 101 | override fun onStart() { 102 | super.onStart() 103 | viewModel.getSession { user -> 104 | if (user != null){ 105 | findNavController().navigate(R.id.action_loginFragment_to_home_navigation) 106 | } 107 | } 108 | } 109 | 110 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/auth/RegisterFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.auth 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.viewModels 9 | import androidx.navigation.fragment.findNavController 10 | import com.example.firebasewithmvvm.R 11 | import com.example.firebasewithmvvm.data.model.User 12 | import com.example.firebasewithmvvm.databinding.FragmentRegisterBinding 13 | import com.example.firebasewithmvvm.util.* 14 | import dagger.hilt.android.AndroidEntryPoint 15 | 16 | @AndroidEntryPoint 17 | class RegisterFragment : Fragment() { 18 | 19 | val TAG: String = "RegisterFragment" 20 | lateinit var binding: FragmentRegisterBinding 21 | val viewModel: AuthViewModel by viewModels() 22 | 23 | override fun onCreateView( 24 | inflater: LayoutInflater, container: ViewGroup?, 25 | savedInstanceState: Bundle? 26 | ): View { 27 | binding = FragmentRegisterBinding.inflate(layoutInflater) 28 | return binding.root 29 | } 30 | 31 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 32 | super.onViewCreated(view, savedInstanceState) 33 | observer() 34 | binding.registerBtn.setOnClickListener { 35 | if (validation()){ 36 | viewModel.register( 37 | email = binding.emailEt.text.toString(), 38 | password = binding.passEt.text.toString(), 39 | user = getUserObj() 40 | ) 41 | } 42 | } 43 | } 44 | 45 | fun observer() { 46 | viewModel.register.observe(viewLifecycleOwner) { state -> 47 | when(state){ 48 | is UiState.Loading -> { 49 | binding.registerBtn.setText("") 50 | binding.registerProgress.show() 51 | } 52 | is UiState.Failure -> { 53 | binding.registerBtn.setText("Register") 54 | binding.registerProgress.hide() 55 | toast(state.error) 56 | } 57 | is UiState.Success -> { 58 | binding.registerBtn.setText("Register") 59 | binding.registerProgress.hide() 60 | toast(state.data) 61 | findNavController().navigate(R.id.action_registerFragment_to_home_navigation) 62 | } 63 | } 64 | } 65 | } 66 | 67 | fun getUserObj(): User { 68 | return User( 69 | id = "", 70 | first_name = binding.firstNameEt.text.toString(), 71 | last_name = binding.lastNameEt.text.toString(), 72 | job_title = binding.jobTitleEt.text.toString(), 73 | email = binding.emailEt.text.toString(), 74 | ) 75 | } 76 | 77 | fun validation(): Boolean { 78 | var isValid = true 79 | 80 | if (binding.firstNameEt.text.isNullOrEmpty()){ 81 | isValid = false 82 | toast(getString(R.string.enter_first_name)) 83 | } 84 | 85 | if (binding.lastNameEt.text.isNullOrEmpty()){ 86 | isValid = false 87 | toast(getString(R.string.enter_last_name)) 88 | } 89 | 90 | if (binding.jobTitleEt.text.isNullOrEmpty()){ 91 | isValid = false 92 | toast(getString(R.string.enter_job_title)) 93 | } 94 | 95 | if (binding.emailEt.text.isNullOrEmpty()){ 96 | isValid = false 97 | toast(getString(R.string.enter_email)) 98 | }else{ 99 | if (!binding.emailEt.text.toString().isValidEmail()){ 100 | isValid = false 101 | toast(getString(R.string.invalid_email)) 102 | } 103 | } 104 | if (binding.passEt.text.isNullOrEmpty()){ 105 | isValid = false 106 | toast(getString(R.string.enter_password)) 107 | }else{ 108 | if (binding.passEt.text.toString().length < 8){ 109 | isValid = false 110 | toast(getString(R.string.invalid_password)) 111 | } 112 | } 113 | return isValid 114 | } 115 | 116 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.home 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import android.widget.TextView 9 | import androidx.core.content.ContextCompat 10 | import androidx.fragment.app.viewModels 11 | import androidx.navigation.fragment.findNavController 12 | import androidx.viewpager2.widget.ViewPager2 13 | import com.example.firebasewithmvvm.R 14 | import com.example.firebasewithmvvm.databinding.FragmentHomeBinding 15 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel 16 | import com.example.firebasewithmvvm.util.HomeTabs 17 | import com.google.android.material.tabs.TabLayout 18 | import com.google.android.material.tabs.TabLayoutMediator 19 | import dagger.hilt.android.AndroidEntryPoint 20 | 21 | @AndroidEntryPoint 22 | class HomeFragment : Fragment() { 23 | 24 | val TAG: String = "HomeFragment" 25 | lateinit var binding: FragmentHomeBinding 26 | val authViewModel: AuthViewModel by viewModels() 27 | 28 | override fun onCreateView( 29 | inflater: LayoutInflater, container: ViewGroup?, 30 | savedInstanceState: Bundle? 31 | ): View { 32 | binding = FragmentHomeBinding.inflate(layoutInflater) 33 | return binding.root 34 | } 35 | 36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 37 | super.onViewCreated(view, savedInstanceState) 38 | 39 | binding.logout.setOnClickListener { 40 | authViewModel.logout { 41 | findNavController().navigate(R.id.action_homeFragment_to_loginFragment) 42 | } 43 | } 44 | 45 | binding.viewPager2.adapter = HomePagerAdapter(this) 46 | viewPager2SetupWithTabLayout( 47 | tabLayout = binding.tabLayout, 48 | viewPager2 = binding.viewPager2 49 | ) 50 | binding.tabLayout.onTabSelectionListener() 51 | } 52 | 53 | private fun viewPager2SetupWithTabLayout(tabLayout: TabLayout, viewPager2: ViewPager2){ 54 | TabLayoutMediator(tabLayout, viewPager2) { tab, position -> 55 | val view = LayoutInflater.from(requireContext()).inflate(R.layout.item_tab_layout, null) 56 | val textViewTitle: TextView = view.findViewById(R.id.tabTitle) 57 | when (position) { 58 | HomeTabs.NOTES.index -> { 59 | tab.customView = view 60 | textViewTitle.text = getString(R.string.notes) 61 | tab.onSelection(true) 62 | } 63 | HomeTabs.TASKS.index -> { 64 | tab.customView = view 65 | textViewTitle.text = getString(R.string.tasks) 66 | tab.onSelection(false) 67 | } 68 | } 69 | }.attach() 70 | } 71 | 72 | } 73 | 74 | inline fun TabLayout.onTabSelectionListener( 75 | crossinline onTabSelected: (TabLayout.Tab?) -> Unit = {}, 76 | crossinline onTabUnselected: (TabLayout.Tab?) -> Unit? = {}, 77 | ) { 78 | addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener { 79 | override fun onTabSelected(tab: TabLayout.Tab?) { 80 | tab?.onSelection(true) 81 | onTabSelected.invoke(tab) 82 | } 83 | 84 | override fun onTabUnselected(tab: TabLayout.Tab?) { 85 | tab?.onSelection(false) 86 | onTabUnselected.invoke(tab) 87 | } 88 | 89 | override fun onTabReselected(tab: TabLayout.Tab?) {} 90 | }) 91 | } 92 | 93 | fun TabLayout.Tab.onSelection(isSelected: Boolean = true) { 94 | val textViewTitle: TextView? = customView?.findViewById(R.id.tabTitle) 95 | textViewTitle?.let { textView -> 96 | textView.setTextColor( 97 | ContextCompat.getColor( 98 | textView.context, 99 | if (isSelected) R.color.tab_selected else R.color.tab_unselected 100 | ) 101 | ) 102 | } 103 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/home/HomePagerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.home 2 | 3 | import androidx.fragment.app.Fragment 4 | import androidx.viewpager2.adapter.FragmentStateAdapter 5 | import com.example.firebasewithmvvm.ui.note.NoteListingFragment 6 | import com.example.firebasewithmvvm.ui.task.TaskListingFragment 7 | import com.example.firebasewithmvvm.util.HomeTabs 8 | 9 | class HomePagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) { 10 | 11 | override fun getItemCount(): Int = HomeTabs.values().size 12 | 13 | override fun createFragment(position: Int): Fragment { 14 | return when (position) { 15 | HomeTabs.NOTES.index -> NoteListingFragment.newInstance(HomeTabs.NOTES.name) 16 | HomeTabs.TASKS.index -> TaskListingFragment.newInstance(HomeTabs.TASKS.name) 17 | else -> throw IllegalStateException("Fragment not found") 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/note/ImageListingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.note 2 | 3 | import android.net.Uri 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.example.firebasewithmvvm.databinding.ImageLayoutBinding 8 | 9 | class ImageListingAdapter( 10 | val onCancelClicked: ((Int, Uri) -> Unit)? = null, 11 | ) : RecyclerView.Adapter() { 12 | 13 | private var list: MutableList = arrayListOf() 14 | 15 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 16 | val itemView = ImageLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false) 17 | return MyViewHolder(itemView) 18 | } 19 | 20 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 21 | val item = list[position] 22 | holder.bind(item,position) 23 | } 24 | 25 | fun updateList(list: MutableList){ 26 | this.list = list 27 | notifyDataSetChanged() 28 | } 29 | 30 | fun removeItem(position: Int){ 31 | list.removeAt(position) 32 | notifyItemChanged(position) 33 | } 34 | 35 | override fun getItemCount(): Int { 36 | return list.size 37 | } 38 | 39 | inner class MyViewHolder(val binding: ImageLayoutBinding) : RecyclerView.ViewHolder(binding.root) { 40 | fun bind(item: Uri,position: Int) { 41 | binding.image.setImageURI(item) 42 | binding.cancel.setOnClickListener { 43 | onCancelClicked?.invoke(absoluteAdapterPosition, item) 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteDetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.note 2 | 3 | import android.app.Activity 4 | import android.net.Uri 5 | import android.os.Bundle 6 | import android.util.Log 7 | import androidx.fragment.app.Fragment 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import android.widget.EditText 12 | import androidx.activity.result.ActivityResult 13 | import androidx.activity.result.contract.ActivityResultContracts 14 | import androidx.core.net.toUri 15 | import androidx.core.widget.doAfterTextChanged 16 | import androidx.fragment.app.viewModels 17 | import androidx.navigation.fragment.findNavController 18 | import androidx.recyclerview.widget.LinearLayoutManager 19 | import com.example.firebasewithmvvm.R 20 | import com.example.firebasewithmvvm.data.model.Note 21 | import com.example.firebasewithmvvm.databinding.FragmentNoteDetailBinding 22 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel 23 | import com.example.firebasewithmvvm.util.* 24 | import com.github.dhaval2404.imagepicker.ImagePicker 25 | import com.google.android.material.button.MaterialButton 26 | import dagger.hilt.android.AndroidEntryPoint 27 | import java.text.SimpleDateFormat 28 | import java.util.* 29 | 30 | @AndroidEntryPoint 31 | class NoteDetailFragment : Fragment() { 32 | 33 | val TAG: String = "NoteDetailFragment" 34 | lateinit var binding: FragmentNoteDetailBinding 35 | val viewModel: NoteViewModel by viewModels() 36 | val authViewModel: AuthViewModel by viewModels() 37 | var objNote: Note? = null 38 | var tagsList: MutableList = arrayListOf() 39 | var imageUris: MutableList = arrayListOf() 40 | val adapter by lazy { 41 | ImageListingAdapter( 42 | onCancelClicked = { pos, item -> onRemoveImage(pos,item)} 43 | ) 44 | } 45 | 46 | private val startForProfileImageResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult -> 47 | val resultCode = result.resultCode 48 | val data = result.data 49 | if (resultCode == Activity.RESULT_OK) { 50 | val fileUri = data?.data!! 51 | imageUris.add(fileUri) 52 | adapter.updateList(imageUris) 53 | binding.progressBar.hide() 54 | } else if (resultCode == ImagePicker.RESULT_ERROR) { 55 | binding.progressBar.hide() 56 | toast(ImagePicker.getError(data)) 57 | } else { 58 | binding.progressBar.hide() 59 | Log.e(TAG,"Task Cancelled") 60 | } 61 | } 62 | 63 | override fun onCreateView( 64 | inflater: LayoutInflater, container: ViewGroup?, 65 | savedInstanceState: Bundle? 66 | ): View { 67 | if (this::binding.isInitialized){ 68 | return binding.root 69 | }else { 70 | binding = FragmentNoteDetailBinding.inflate(layoutInflater) 71 | return binding.root 72 | } 73 | } 74 | 75 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 76 | super.onViewCreated(view, savedInstanceState) 77 | updateUI() 78 | observer() 79 | } 80 | 81 | private fun observer() { 82 | viewModel.addNote.observe(viewLifecycleOwner) { state -> 83 | when (state) { 84 | is UiState.Loading -> { 85 | binding.progressBar.show() 86 | } 87 | is UiState.Failure -> { 88 | binding.progressBar.hide() 89 | toast(state.error) 90 | } 91 | is UiState.Success -> { 92 | binding.progressBar.hide() 93 | toast(state.data.second) 94 | objNote = state.data.first 95 | isMakeEnableUI(false) 96 | binding.done.hide() 97 | binding.delete.show() 98 | binding.edit.show() 99 | } 100 | } 101 | } 102 | viewModel.updateNote.observe(viewLifecycleOwner) { state -> 103 | when (state) { 104 | is UiState.Loading -> { 105 | binding.progressBar.show() 106 | } 107 | is UiState.Failure -> { 108 | binding.progressBar.hide() 109 | toast(state.error) 110 | } 111 | is UiState.Success -> { 112 | binding.progressBar.hide() 113 | toast(state.data) 114 | binding.done.hide() 115 | binding.edit.show() 116 | isMakeEnableUI(false) 117 | } 118 | } 119 | } 120 | 121 | viewModel.deleteNote.observe(viewLifecycleOwner) { state -> 122 | when (state) { 123 | is UiState.Loading -> { 124 | binding.progressBar.show() 125 | } 126 | is UiState.Failure -> { 127 | binding.progressBar.hide() 128 | toast(state.error) 129 | } 130 | is UiState.Success -> { 131 | binding.progressBar.hide() 132 | toast(state.data) 133 | findNavController().navigateUp() 134 | } 135 | } 136 | } 137 | } 138 | 139 | private fun updateUI() { 140 | val sdf = SimpleDateFormat("dd MMM yyyy . hh:mm a") 141 | objNote = arguments?.getParcelable("note") 142 | binding.tags.layoutParams.height = 40.dpToPx 143 | objNote?.let { note -> 144 | binding.title.setText(note.title) 145 | binding.date.setText(sdf.format(note.date)) 146 | tagsList = note.tags 147 | addTags(tagsList) 148 | binding.description.setText(note.description) 149 | binding.done.hide() 150 | binding.edit.show() 151 | binding.delete.show() 152 | isMakeEnableUI(false) 153 | } ?: run { 154 | binding.title.setText("") 155 | binding.date.setText(sdf.format(Date())) 156 | binding.description.setText("") 157 | binding.done.hide() 158 | binding.edit.hide() 159 | binding.delete.hide() 160 | isMakeEnableUI(true) 161 | } 162 | binding.images.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL,false) 163 | binding.images.adapter = adapter 164 | binding.images.itemAnimator = null 165 | imageUris = objNote?.images?.map { it.toUri() }?.toMutableList() ?: arrayListOf() 166 | adapter.updateList(imageUris) 167 | binding.addImageLl.setOnClickListener { 168 | binding.progressBar.show() 169 | ImagePicker.with(this) 170 | //.crop() 171 | .compress(1024) 172 | .galleryOnly() 173 | .createIntent { intent -> 174 | startForProfileImageResult.launch(intent) 175 | } 176 | } 177 | binding.back.setOnClickListener { 178 | findNavController().navigateUp() 179 | } 180 | binding.title.setOnClickListener { 181 | isMakeEnableUI(true) 182 | } 183 | binding.description.setOnClickListener { 184 | isMakeEnableUI(true) 185 | } 186 | binding.delete.setOnClickListener { 187 | objNote?.let { viewModel.deleteNote(it) } 188 | } 189 | binding.addTagLl.setOnClickListener { 190 | showAddTagDialog() 191 | } 192 | binding.edit.setOnClickListener { 193 | isMakeEnableUI(true) 194 | binding.done.show() 195 | binding.edit.hide() 196 | binding.title.requestFocus() 197 | } 198 | binding.done.setOnClickListener { 199 | if (validation()) { 200 | onDonePressed() 201 | } 202 | } 203 | binding.title.doAfterTextChanged { 204 | binding.done.show() 205 | binding.edit.hide() 206 | } 207 | binding.description.doAfterTextChanged { 208 | binding.done.show() 209 | binding.edit.hide() 210 | } 211 | } 212 | 213 | private fun onRemoveImage(pos: Int, item: Uri) { 214 | adapter.removeItem(pos) 215 | if (objNote != null){ 216 | binding.edit.performClick() 217 | } 218 | } 219 | 220 | private fun showAddTagDialog(){ 221 | val dialog = requireContext().createDialog(R.layout.add_tag_dialog, true) 222 | val button = dialog.findViewById(R.id.tag_dialog_add) 223 | val editText = dialog.findViewById(R.id.tag_dialog_et) 224 | button.setOnClickListener { 225 | if (editText.text.toString().isNullOrEmpty()) { 226 | toast(getString(R.string.error_tag_text)) 227 | } else { 228 | val text = editText.text.toString() 229 | tagsList.add(text) 230 | binding.tags.apply { 231 | addChip(text, true) { 232 | tagsList.forEachIndexed { index, tag -> 233 | if (text.equals(tag)) { 234 | tagsList.removeAt(index) 235 | binding.tags.removeViewAt(index) 236 | } 237 | } 238 | if (tagsList.size == 0){ 239 | layoutParams.height = 40.dpToPx 240 | } 241 | } 242 | layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT 243 | } 244 | binding.done.show() 245 | binding.edit.hide() 246 | dialog.dismiss() 247 | } 248 | } 249 | dialog.show() 250 | } 251 | 252 | private fun addTags(note: MutableList) { 253 | if (note.size > 0) { 254 | binding.tags.apply { 255 | removeAllViews() 256 | note.forEachIndexed { index, tag -> 257 | addChip(tag, true) { 258 | if (isEnabled) { 259 | note.removeAt(index) 260 | this.removeViewAt(index) 261 | } 262 | } 263 | } 264 | } 265 | } 266 | } 267 | 268 | private fun isMakeEnableUI(isDisable: Boolean = false) { 269 | binding.title.isEnabled = isDisable 270 | binding.date.isEnabled = isDisable 271 | binding.tags.isEnabled = isDisable 272 | binding.addTagLl.isEnabled = isDisable 273 | binding.description.isEnabled = isDisable 274 | } 275 | 276 | private fun validation(): Boolean { 277 | var isValid = true 278 | if (binding.title.text.toString().isNullOrEmpty()) { 279 | isValid = false 280 | toast(getString(R.string.error_title)) 281 | } 282 | if (binding.description.text.toString().isNullOrEmpty()) { 283 | isValid = false 284 | toast(getString(R.string.error_description)) 285 | } 286 | return isValid 287 | } 288 | 289 | private fun getNote(): Note { 290 | return Note( 291 | id = objNote?.id ?: "", 292 | title = binding.title.text.toString(), 293 | description = binding.description.text.toString(), 294 | tags = tagsList, 295 | images = getImageUrls(), 296 | date = Date() 297 | ).apply { authViewModel.getSession { this.user_id = it?.id ?: "" } } 298 | } 299 | 300 | private fun getImageUrls(): List { 301 | if (imageUris.isNotEmpty()){ 302 | return imageUris.map { it.toString() } 303 | }else{ 304 | return objNote?.images ?: arrayListOf() 305 | } 306 | } 307 | 308 | private fun onDonePressed() { 309 | if (imageUris.isNotEmpty()){ 310 | viewModel.onUploadMultipleFile(imageUris){ state -> 311 | when (state) { 312 | is UiState.Loading -> { 313 | binding.progressBar.show() 314 | } 315 | is UiState.Failure -> { 316 | binding.progressBar.hide() 317 | toast(state.error) 318 | } 319 | is UiState.Success -> { 320 | binding.progressBar.hide() 321 | if (objNote == null) { 322 | viewModel.addNote(getNote()) 323 | } else { 324 | viewModel.updateNote(getNote()) 325 | } 326 | } 327 | } 328 | } 329 | }else{ 330 | if (objNote == null) { 331 | viewModel.addNote(getNote()) 332 | } else { 333 | viewModel.updateNote(getNote()) 334 | } 335 | } 336 | } 337 | } 338 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteListingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.note 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.example.firebasewithmvvm.data.model.Note 7 | import com.example.firebasewithmvvm.databinding.ItemNoteLayoutBinding 8 | import com.example.firebasewithmvvm.util.addChip 9 | import com.example.firebasewithmvvm.util.hide 10 | import java.text.SimpleDateFormat 11 | 12 | class NoteListingAdapter( 13 | val onItemClicked: (Int, Note) -> Unit 14 | ) : RecyclerView.Adapter() { 15 | 16 | val sdf = SimpleDateFormat("dd MMM yyyy") 17 | private var list: MutableList = arrayListOf() 18 | 19 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 20 | val itemView = ItemNoteLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false) 21 | return MyViewHolder(itemView) 22 | } 23 | 24 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 25 | val item = list[position] 26 | holder.bind(item) 27 | } 28 | 29 | fun updateList(list: MutableList){ 30 | this.list = list 31 | notifyDataSetChanged() 32 | } 33 | 34 | fun removeItem(position: Int){ 35 | list.removeAt(position) 36 | notifyItemChanged(position) 37 | } 38 | 39 | override fun getItemCount(): Int { 40 | return list.size 41 | } 42 | 43 | inner class MyViewHolder(val binding: ItemNoteLayoutBinding) : RecyclerView.ViewHolder(binding.root) { 44 | fun bind(item: Note){ 45 | binding.title.setText(item.title) 46 | binding.date.setText(sdf.format(item.date)) 47 | binding.tags.apply { 48 | if (item.tags.isNullOrEmpty()){ 49 | hide() 50 | }else { 51 | removeAllViews() 52 | if (item.tags.size > 2) { 53 | item.tags.subList(0, 2).forEach { tag -> addChip(tag) } 54 | addChip("+${item.tags.size - 2}") 55 | } else { 56 | item.tags.forEach { tag -> addChip(tag) } 57 | } 58 | } 59 | } 60 | binding.desc.apply { 61 | if (item.description.length > 120){ 62 | text = "${item.description.substring(0,120)}..." 63 | }else{ 64 | text = item.description 65 | } 66 | } 67 | binding.itemLayout.setOnClickListener { onItemClicked.invoke(adapterPosition,item) } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteListingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.note 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.util.Log 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import androidx.fragment.app.Fragment 10 | import androidx.fragment.app.viewModels 11 | import androidx.navigation.NavGraph 12 | import androidx.navigation.findNavController 13 | import androidx.navigation.fragment.findNavController 14 | import androidx.recyclerview.widget.LinearLayoutManager 15 | import androidx.recyclerview.widget.StaggeredGridLayoutManager 16 | import com.example.firebasewithmvvm.R 17 | import com.example.firebasewithmvvm.databinding.FragmentNoteListingBinding 18 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel 19 | import com.example.firebasewithmvvm.util.UiState 20 | import com.example.firebasewithmvvm.util.hide 21 | import com.example.firebasewithmvvm.util.show 22 | import com.example.firebasewithmvvm.util.toast 23 | import dagger.hilt.android.AndroidEntryPoint 24 | 25 | 26 | private const val ARG_PARAM1 = "param1" 27 | 28 | @AndroidEntryPoint 29 | class NoteListingFragment : Fragment() { 30 | 31 | val TAG: String = "NoteListingFragment" 32 | var param1: String? = null 33 | lateinit var binding: FragmentNoteListingBinding 34 | val viewModel: NoteViewModel by viewModels() 35 | val authViewModel: AuthViewModel by viewModels() 36 | val adapter by lazy { 37 | NoteListingAdapter( 38 | onItemClicked = { pos, item -> 39 | findNavController().navigate(R.id.action_noteListingFragment_to_noteDetailFragment,Bundle().apply { 40 | putParcelable("note",item) 41 | }) 42 | } 43 | ) 44 | } 45 | 46 | override fun onCreate(savedInstanceState: Bundle?) { 47 | super.onCreate(savedInstanceState) 48 | arguments?.let { 49 | param1 = it.getString(ARG_PARAM1) 50 | } 51 | } 52 | 53 | override fun onCreateView( 54 | inflater: LayoutInflater, container: ViewGroup?, 55 | savedInstanceState: Bundle? 56 | ): View { 57 | if (this::binding.isInitialized){ 58 | return binding.root 59 | }else { 60 | binding = FragmentNoteListingBinding.inflate(layoutInflater) 61 | return binding.root 62 | } 63 | } 64 | 65 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 66 | super.onViewCreated(view, savedInstanceState) 67 | oberver() 68 | val staggeredGridLayoutManager = StaggeredGridLayoutManager(2, LinearLayoutManager.VERTICAL) 69 | binding.recyclerView.layoutManager = staggeredGridLayoutManager 70 | binding.recyclerView.adapter = adapter 71 | binding.button.setOnClickListener { 72 | findNavController().navigate(R.id.action_noteListingFragment_to_noteDetailFragment) 73 | } 74 | 75 | authViewModel.getSession { 76 | viewModel.getNotes(it) 77 | } 78 | } 79 | 80 | private fun oberver(){ 81 | viewModel.note.observe(viewLifecycleOwner) { state -> 82 | when(state){ 83 | is UiState.Loading -> { 84 | binding.progressBar.show() 85 | } 86 | is UiState.Failure -> { 87 | binding.progressBar.hide() 88 | toast(state.error) 89 | } 90 | is UiState.Success -> { 91 | binding.progressBar.hide() 92 | adapter.updateList(state.data.toMutableList()) 93 | } 94 | } 95 | } 96 | } 97 | 98 | companion object { 99 | @JvmStatic 100 | fun newInstance(param1: String) = 101 | NoteListingFragment().apply { 102 | arguments = Bundle().apply { 103 | putString(ARG_PARAM1, param1) 104 | } 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/note/NoteViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.note 2 | 3 | import android.net.Uri 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MutableLiveData 6 | import androidx.lifecycle.ViewModel 7 | import androidx.lifecycle.viewModelScope 8 | import com.example.firebasewithmvvm.data.model.Note 9 | import com.example.firebasewithmvvm.data.model.User 10 | import com.example.firebasewithmvvm.data.repository.NoteRepository 11 | import com.example.firebasewithmvvm.util.UiState 12 | import dagger.hilt.android.lifecycle.HiltViewModel 13 | import kotlinx.coroutines.launch 14 | import javax.inject.Inject 15 | 16 | @HiltViewModel 17 | class NoteViewModel @Inject constructor( 18 | val repository: NoteRepository 19 | ): ViewModel() { 20 | 21 | private val _notes = MutableLiveData>>() 22 | val note: LiveData>> 23 | get() = _notes 24 | 25 | private val _addNote = MutableLiveData>>() 26 | val addNote: LiveData>> 27 | get() = _addNote 28 | 29 | private val _updateNote = MutableLiveData>() 30 | val updateNote: LiveData> 31 | get() = _updateNote 32 | 33 | private val _deleteNote = MutableLiveData>() 34 | val deleteNote: LiveData> 35 | get() = _deleteNote 36 | 37 | fun getNotes(user: User?) { 38 | _notes.value = UiState.Loading 39 | repository.getNotes(user) { _notes.value = it } 40 | } 41 | 42 | fun addNote(note: Note){ 43 | _addNote.value = UiState.Loading 44 | repository.addNote(note) { _addNote.value = it } 45 | } 46 | 47 | fun updateNote(note: Note){ 48 | _updateNote.value = UiState.Loading 49 | repository.updateNote(note) { _updateNote.value = it } 50 | } 51 | 52 | fun deleteNote(note: Note){ 53 | _deleteNote.value = UiState.Loading 54 | repository.deleteNote(note) { _deleteNote.value = it } 55 | } 56 | 57 | fun onUploadSingleFile(fileUris: Uri, onResult: (UiState) -> Unit){ 58 | onResult.invoke(UiState.Loading) 59 | viewModelScope.launch { 60 | repository.uploadSingleFile(fileUris,onResult) 61 | } 62 | } 63 | 64 | fun onUploadMultipleFile(fileUris: List, onResult: (UiState>) -> Unit){ 65 | onResult.invoke(UiState.Loading) 66 | viewModelScope.launch { 67 | repository.uploadMultipleFile(fileUris,onResult) 68 | } 69 | } 70 | 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/task/CreateTaskFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.task 2 | 3 | import android.app.Dialog 4 | import android.content.DialogInterface 5 | import android.graphics.Color 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import androidx.fragment.app.viewModels 11 | import com.example.firebasewithmvvm.R 12 | import com.example.firebasewithmvvm.data.model.Task 13 | import com.example.firebasewithmvvm.databinding.FragmentCreateTaskBinding 14 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel 15 | import com.example.firebasewithmvvm.util.UiState 16 | import com.example.firebasewithmvvm.util.hide 17 | import com.example.firebasewithmvvm.util.show 18 | import com.example.firebasewithmvvm.util.toast 19 | import com.google.android.material.bottomsheet.BottomSheetDialog 20 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 21 | import dagger.hilt.android.AndroidEntryPoint 22 | import java.text.SimpleDateFormat 23 | import java.util.* 24 | 25 | @AndroidEntryPoint 26 | class CreateTaskFragment(private val task: Task? = null) : BottomSheetDialogFragment() { 27 | 28 | val TAG: String = "CreateTaskFragment" 29 | lateinit var binding: FragmentCreateTaskBinding 30 | val viewModel: TaskViewModel by viewModels() 31 | val authViewModel: AuthViewModel by viewModels() 32 | var closeFunction: ((Boolean) -> Unit)? = null 33 | var isSuccessAddTask: Boolean = false 34 | 35 | 36 | override fun onCreateView( 37 | inflater: LayoutInflater, container: ViewGroup?, 38 | savedInstanceState: Bundle? 39 | ): View { 40 | binding = FragmentCreateTaskBinding.inflate(layoutInflater) 41 | return binding.root 42 | } 43 | 44 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 45 | super.onViewCreated(view, savedInstanceState) 46 | 47 | task?.let { 48 | binding.taskEt.setText(it.description) 49 | } 50 | 51 | binding.cancel.setOnClickListener { 52 | this.dismiss() 53 | } 54 | 55 | binding.done.setOnClickListener { 56 | if (validation()) { 57 | if (task == null) { 58 | viewModel.addTask(getTask()) 59 | }else{ 60 | task.description = binding.taskEt.text.toString() 61 | viewModel.updateTask(task) 62 | } 63 | } 64 | } 65 | observer() 66 | } 67 | 68 | private fun observer(){ 69 | viewModel.addTask.observe(viewLifecycleOwner) { state -> 70 | when(state){ 71 | is UiState.Loading -> { 72 | binding.progressBar.show() 73 | } 74 | is UiState.Failure -> { 75 | binding.progressBar.hide() 76 | toast(state.error) 77 | } 78 | is UiState.Success -> { 79 | isSuccessAddTask = true 80 | binding.progressBar.hide() 81 | toast(state.data.second) 82 | this.dismiss() 83 | } 84 | } 85 | } 86 | 87 | viewModel.updateTask.observe(viewLifecycleOwner) { state -> 88 | when(state){ 89 | is UiState.Loading -> { 90 | binding.progressBar.show() 91 | } 92 | is UiState.Failure -> { 93 | binding.progressBar.hide() 94 | toast(state.error) 95 | } 96 | is UiState.Success -> { 97 | isSuccessAddTask = true 98 | binding.progressBar.hide() 99 | toast(state.data.second) 100 | this.dismiss() 101 | } 102 | } 103 | } 104 | } 105 | 106 | private fun validation(): Boolean { 107 | var isValid = true 108 | if (binding.taskEt.text.toString().isNullOrEmpty()) { 109 | isValid = false 110 | toast(getString(R.string.error_task_detail)) 111 | } 112 | return isValid 113 | } 114 | 115 | private fun getTask(): Task { 116 | val sdf = SimpleDateFormat("dd MMM yyyy . hh:mm a") 117 | return Task( 118 | id = "", 119 | description = binding.taskEt.text.toString(), 120 | date = sdf.format(Date()) 121 | ).apply { authViewModel.getSession { this.user_id = it?.id ?: "" } } 122 | } 123 | 124 | fun setDismissListener(function: ((Boolean) -> Unit)?) { 125 | closeFunction = function 126 | } 127 | 128 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 129 | val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog 130 | dialog.setOnShowListener { setupBottomSheet(it) } 131 | return dialog 132 | } 133 | 134 | private fun setupBottomSheet(dialogInterface: DialogInterface) { 135 | val bottomSheetDialog = dialogInterface as BottomSheetDialog 136 | val bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) ?: return 137 | bottomSheet.setBackgroundColor(Color.TRANSPARENT) 138 | } 139 | 140 | override fun onDismiss(dialog: DialogInterface) { 141 | super.onDismiss(dialog) 142 | closeFunction?.invoke(isSuccessAddTask) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/task/TaskListingAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.task 2 | 3 | import android.view.LayoutInflater 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import com.example.firebasewithmvvm.data.model.Task 7 | import com.example.firebasewithmvvm.databinding.TaskLayoutBinding 8 | 9 | class TaskListingAdapter( 10 | val onItemClicked: ((Int, Task) -> Unit)? = null, 11 | val onDeleteClicked: ((Int, Task) -> Unit)? = null, 12 | ) : RecyclerView.Adapter() { 13 | 14 | private var list: MutableList = arrayListOf() 15 | 16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { 17 | val itemView = TaskLayoutBinding.inflate(LayoutInflater.from(parent.context),parent,false) 18 | return MyViewHolder(itemView) 19 | } 20 | 21 | override fun onBindViewHolder(holder: MyViewHolder, position: Int) { 22 | val item = list[position] 23 | holder.bind(item,position) 24 | } 25 | 26 | fun updateList(list: MutableList){ 27 | this.list = list 28 | notifyDataSetChanged() 29 | } 30 | 31 | fun removeItem(position: Int){ 32 | list.removeAt(position) 33 | notifyItemChanged(position) 34 | } 35 | 36 | override fun getItemCount(): Int { 37 | return list.size 38 | } 39 | 40 | inner class MyViewHolder(val binding: TaskLayoutBinding) : RecyclerView.ViewHolder(binding.root) { 41 | fun bind(item: Task,position: Int) { 42 | binding.title.setText(item.description) 43 | binding.itemLayout.setOnClickListener { 44 | onItemClicked?.invoke(position,item) 45 | } 46 | binding.delete.setOnClickListener { 47 | onDeleteClicked?.invoke(position,item) 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/task/TaskListingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.task 2 | 3 | import android.os.Bundle 4 | import androidx.fragment.app.Fragment 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.fragment.app.viewModels 9 | import androidx.recyclerview.widget.LinearLayoutManager 10 | import com.example.firebasewithmvvm.data.model.Task 11 | import com.example.firebasewithmvvm.databinding.FragmentTaskListingBinding 12 | import com.example.firebasewithmvvm.ui.auth.AuthViewModel 13 | import com.example.firebasewithmvvm.util.UiState 14 | import com.example.firebasewithmvvm.util.hide 15 | import com.example.firebasewithmvvm.util.show 16 | import com.example.firebasewithmvvm.util.toast 17 | import dagger.hilt.android.AndroidEntryPoint 18 | 19 | private const val ARG_PARAM1 = "param1" 20 | 21 | @AndroidEntryPoint 22 | class TaskListingFragment : Fragment() { 23 | 24 | val TAG: String = "TaskListingFragment" 25 | private var param1: String? = null 26 | val viewModel: TaskViewModel by viewModels() 27 | val authViewModel: AuthViewModel by viewModels() 28 | lateinit var binding: FragmentTaskListingBinding 29 | var deleteItemPos = -1 30 | val adapter by lazy{ 31 | TaskListingAdapter( 32 | onItemClicked = { pos, item -> onTaskClicked(item)}, 33 | onDeleteClicked = { pos, item -> onDeleteClicked(pos,item) } 34 | ) 35 | } 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | arguments?.let { 40 | param1 = it.getString(ARG_PARAM1) 41 | } 42 | } 43 | 44 | override fun onCreateView( 45 | inflater: LayoutInflater, container: ViewGroup?, 46 | savedInstanceState: Bundle? 47 | ): View { 48 | if (this::binding.isInitialized){ 49 | return binding.root 50 | }else { 51 | binding = FragmentTaskListingBinding.inflate(layoutInflater) 52 | return binding.root 53 | } 54 | } 55 | 56 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 57 | super.onViewCreated(view, savedInstanceState) 58 | binding.addTaskButton.setOnClickListener { 59 | val createTaskFragmentSheet = CreateTaskFragment() 60 | createTaskFragmentSheet.setDismissListener { 61 | if (it) { 62 | authViewModel.getSession { 63 | viewModel.getTasks(it) 64 | } 65 | } 66 | } 67 | createTaskFragmentSheet.show(childFragmentManager,"create_task") 68 | } 69 | 70 | binding.taskListing.layoutManager = LinearLayoutManager(requireContext()) 71 | binding.taskListing.adapter = adapter 72 | 73 | authViewModel.getSession { 74 | viewModel.getTasks(it) 75 | } 76 | observer() 77 | } 78 | 79 | private fun observer(){ 80 | viewModel.tasks.observe(viewLifecycleOwner) { state -> 81 | when(state){ 82 | is UiState.Loading -> { 83 | binding.progressBar.show() 84 | } 85 | is UiState.Failure -> { 86 | binding.progressBar.hide() 87 | toast(state.error) 88 | } 89 | is UiState.Success -> { 90 | binding.progressBar.hide() 91 | adapter.updateList(state.data.toMutableList()) 92 | } 93 | } 94 | } 95 | viewModel.deleteTask.observe(viewLifecycleOwner) { state -> 96 | when(state){ 97 | is UiState.Loading -> { 98 | binding.progressBar.show() 99 | } 100 | is UiState.Failure -> { 101 | binding.progressBar.hide() 102 | toast(state.error) 103 | } 104 | is UiState.Success -> { 105 | binding.progressBar.hide() 106 | toast(state.data.second) 107 | adapter.removeItem(deleteItemPos) 108 | } 109 | } 110 | } 111 | } 112 | 113 | private fun onTaskClicked(task: Task){ 114 | val createTaskFragmentSheet = CreateTaskFragment(task) 115 | createTaskFragmentSheet.setDismissListener { 116 | if (it) { 117 | authViewModel.getSession { 118 | viewModel.getTasks(it) 119 | } 120 | } 121 | } 122 | createTaskFragmentSheet.show(childFragmentManager,"create_task") 123 | } 124 | 125 | private fun onDeleteClicked(pos: Int, item: Task) { 126 | deleteItemPos = pos 127 | viewModel.deleteTask(item) 128 | } 129 | 130 | companion object { 131 | @JvmStatic 132 | fun newInstance(param1: String) = 133 | TaskListingFragment().apply { 134 | arguments = Bundle().apply { 135 | putString(ARG_PARAM1, param1) 136 | } 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/ui/task/TaskViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.ui.task 2 | 3 | import androidx.lifecycle.LiveData 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import com.example.firebasewithmvvm.data.model.Task 7 | import com.example.firebasewithmvvm.data.model.User 8 | import com.example.firebasewithmvvm.data.repository.TaskRepository 9 | import com.example.firebasewithmvvm.util.UiState 10 | import com.google.android.gms.tasks.Tasks 11 | import dagger.hilt.android.lifecycle.HiltViewModel 12 | import javax.inject.Inject 13 | 14 | @HiltViewModel 15 | class TaskViewModel @Inject constructor( 16 | val repository: TaskRepository 17 | ): ViewModel() { 18 | 19 | private val _addTask = MutableLiveData>>() 20 | val addTask: LiveData>> 21 | get() = _addTask 22 | 23 | private val _updateTask = MutableLiveData>>() 24 | val updateTask: LiveData>> 25 | get() = _updateTask 26 | 27 | private val _deleteTask = MutableLiveData>>() 28 | val deleteTask: LiveData>> 29 | get() = _deleteTask 30 | 31 | 32 | private val _tasks = MutableLiveData>>() 33 | val tasks: LiveData>> 34 | get() = _tasks 35 | 36 | fun addTask(task: Task){ 37 | _addTask.value = UiState.Loading 38 | repository.addTask(task) { _addTask.value = it } 39 | } 40 | 41 | fun updateTask(task: Task){ 42 | _updateTask.value = UiState.Loading 43 | repository.updateTask(task) { _updateTask.value = it } 44 | } 45 | 46 | fun getTasks(user: User?) { 47 | _tasks.value = UiState.Loading 48 | repository.getTasks(user) { _tasks.value = it } 49 | } 50 | 51 | fun deleteTask(task: Task){ 52 | _deleteTask.value = UiState.Loading 53 | repository.deleteTask(task) { _deleteTask.value = it } 54 | } 55 | 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.util 2 | 3 | object FireStoreCollection{ 4 | val NOTE = "note" 5 | val USER = "user" 6 | } 7 | 8 | object FireDatabase{ 9 | val TASK = "task" 10 | } 11 | 12 | object FireStoreDocumentField { 13 | val DATE = "date" 14 | val USER_ID = "user_id" 15 | } 16 | 17 | object SharedPrefConstants { 18 | val LOCAL_SHARED_PREF = "local_shared_pref" 19 | val USER_SESSION = "user_session" 20 | } 21 | 22 | object FirebaseStorageConstants { 23 | val ROOT_DIRECTORY = "app" 24 | val NOTE_IMAGES = "note" 25 | } 26 | 27 | enum class HomeTabs(val index: Int, val key: String) { 28 | NOTES(0, "notes"), 29 | TASKS(1, "tasks"), 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.util 2 | 3 | import android.app.Dialog 4 | import android.content.Context 5 | import android.content.res.Resources 6 | import android.graphics.Color 7 | import android.graphics.drawable.ColorDrawable 8 | import android.view.* 9 | import android.widget.Toast 10 | import androidx.core.content.ContextCompat 11 | import androidx.fragment.app.Fragment 12 | import com.example.firebasewithmvvm.R 13 | import com.google.android.material.chip.Chip 14 | import com.google.android.material.chip.ChipGroup 15 | 16 | 17 | fun View.hide(){ 18 | visibility = View.GONE 19 | } 20 | 21 | fun View.show(){ 22 | visibility = View.VISIBLE 23 | } 24 | 25 | fun View.disable(){ 26 | isEnabled = false 27 | } 28 | 29 | fun View.enabled(){ 30 | isEnabled = true 31 | } 32 | 33 | fun Fragment.toast(msg: String?){ 34 | Toast.makeText(requireContext(),msg,Toast.LENGTH_LONG).show() 35 | } 36 | 37 | fun ChipGroup.addChip( 38 | text: String, 39 | isTouchTargeSize: Boolean = false, 40 | closeIconListener: View.OnClickListener? = null 41 | ) { 42 | val chip: Chip = LayoutInflater.from(context).inflate(R.layout.item_chip,null,false) as Chip 43 | chip.text = if (text.length > 9) text.substring(0,9) + "..." else text 44 | chip.isClickable = false 45 | chip.setEnsureMinTouchTargetSize(isTouchTargeSize) 46 | if (closeIconListener != null){ 47 | chip.closeIcon = ContextCompat.getDrawable(context, com.google.android.material.R.drawable.ic_mtrl_chip_close_circle) 48 | chip.isCloseIconVisible = true 49 | chip.setOnCloseIconClickListener(closeIconListener) 50 | } 51 | addView(chip) 52 | } 53 | 54 | fun Context.createDialog(layout: Int, cancelable: Boolean): Dialog { 55 | val dialog = Dialog(this, android.R.style.Theme_Dialog) 56 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE) 57 | dialog.setContentView(layout) 58 | dialog.window?.setGravity(Gravity.CENTER) 59 | dialog.window?.setLayout( 60 | WindowManager.LayoutParams.MATCH_PARENT, 61 | WindowManager.LayoutParams.WRAP_CONTENT 62 | ) 63 | dialog.window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 64 | dialog.setCancelable(cancelable) 65 | return dialog 66 | } 67 | 68 | val Int.dpToPx: Int 69 | get() = (this * Resources.getSystem().displayMetrics.density).toInt() 70 | 71 | val Int.pxToDp: Int 72 | get() = (this / Resources.getSystem().displayMetrics.density).toInt() 73 | 74 | fun String.isValidEmail() = 75 | isNotEmpty() && android.util.Patterns.EMAIL_ADDRESS.matcher(this).matches() 76 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/firebasewithmvvm/util/UiState.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm.util 2 | 3 | sealed class UiState { 4 | object Loading: UiState() 5 | data class Success(val data: T): UiState() 6 | data class Failure(val error: String?): UiState() 7 | } -------------------------------------------------------------------------------- /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/black_rect_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/black_white_rect_bg.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_add_circle_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_back_ios_24.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_cancel_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_check_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_delete_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_edit_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_exit_to_app_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /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/font/titillium_web_black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_black.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/titillium_web_bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_bold.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/titillium_web_light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_light.ttf -------------------------------------------------------------------------------- /app/src/main/res/font/titillium_web_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/font/titillium_web_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/add_tag_dialog.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 29 | 30 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_create_task.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 27 | 28 | 38 | 39 | 51 | 52 | 62 | 63 | 64 | 65 | 81 | 82 | 92 | 93 | 99 | 100 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_forgot_password.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 33 | 34 | 46 | 47 | 66 | 67 | 76 | 77 | 87 | 88 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 19 | 20 | 28 | 29 | 39 | 40 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_login.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 21 | 22 | 33 | 34 | 46 | 47 | 66 | 67 | 80 | 81 | 95 | 96 | 108 | 109 | 123 | 124 | 133 | 134 | 144 | 145 | 155 | 156 | 157 | 158 | 159 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_note_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 19 | 20 | 31 | 32 | 43 | 44 | 55 | 56 | 66 | 67 | 75 | 76 | 86 | 87 | 92 | 93 | 105 | 106 | 116 | 117 | 127 | 128 | 136 | 137 | 146 | 147 | 156 | 157 | 158 | 159 | 169 | 170 | 178 | 179 | 188 | 189 | 198 | 199 | 200 | 201 | 210 | 211 | 219 | 220 | 232 | 233 | 234 | 235 | 236 | 237 | 247 | 248 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_note_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 31 | 32 | 47 | 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_register.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 32 | 33 | 45 | 46 | 65 | 66 | 78 | 79 | 98 | 99 | 111 | 112 | 131 | 132 | 144 | 145 | 164 | 165 | 178 | 179 | 193 | 194 | 195 | 204 | 205 | 215 | 216 | 226 | 227 | 228 | 229 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_task_listing.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 20 | 21 | 36 | 37 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/res/layout/image_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_chip.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_note_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 18 | 19 | 28 | 29 | 39 | 40 | 48 | 49 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_tab_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/layout/task_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 19 | 20 | 31 | 32 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/navigation/app_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 16 | 17 | 18 | 23 | 26 | 29 | 32 | 33 | 34 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/navigation/home_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 18 | 19 | 20 | 23 | 24 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | 9 | #FFFFFFFF 10 | 11 | #FF000000 12 | #B3000000 13 | #80000000 14 | #4D000000 15 | #1A000000 16 | 17 | #FAFBFF 18 | 19 | #FF000000 20 | #4D000000 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Note Taking 3 | 4 | Hello blank fragment 5 | In this chapter, you’re going to learn about the different types of subjects in RxJava, see how to work with each one and why you might choose one over another based on some common use cases. 6 | You’ve gotten a handle on what an Observable is, how to create one, how to subscribe to it, and how to dispose of things when you’re done. Observables are a fundamental part of RxJava, but a common need when developing apps is to manually add new values onto an Observable at runtime that will then be emitted to subscribers. What you want is something that can act as both an Observable and as an observer. And that something is called a subject. 7 | Description missing 8 | Title missing 9 | Enter text 10 | Enter first name 11 | Enter last name 12 | Enter job title 13 | Enter email address 14 | Invalid email entered 15 | Enter password 16 | Your password must be at least 8 characters 17 | Logout 18 | Notes 19 | Tasks 20 | Notes 21 | Tasks 22 | Home 23 | Create Task 24 | Create 25 | Done 26 | Task detail is missing 27 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 20 | 21 | 24 | 25 | 28 | 29 | 32 | 33 | 37 | 38 | 42 | 43 | 49 | 50 | 54 | 55 | 59 | 60 | 63 | 64 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/firebasewithmvvm/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.firebasewithmvvm 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 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | dependencies { 4 | classpath 'com.google.gms:google-services:4.3.3' 5 | classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.5' 6 | } 7 | } 8 | 9 | plugins { 10 | id 'com.android.application' version '7.1.0' apply false 11 | id 'com.android.library' version '7.1.0' apply false 12 | id 'org.jetbrains.kotlin.android' version '1.6.21' apply false 13 | } 14 | 15 | task clean(type: Delete) { 16 | delete rootProject.buildDir 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 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 28 11:54:50 PKT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.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 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /screenshots/note-taking-detail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-detail.png -------------------------------------------------------------------------------- /screenshots/note-taking-forgotpassword.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-forgotpassword.png -------------------------------------------------------------------------------- /screenshots/note-taking-listing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-listing.png -------------------------------------------------------------------------------- /screenshots/note-taking-login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-login.png -------------------------------------------------------------------------------- /screenshots/note-taking-register.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Realtime-Coding/FirebaseWithMVVM/52a372fe766ff826fe4e807f321bbf2353b41c62/screenshots/note-taking-register.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | maven { url "https://jitpack.io" } 7 | } 8 | } 9 | dependencyResolutionManagement { 10 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 11 | repositories { 12 | google() 13 | mavenCentral() 14 | maven { url "https://jitpack.io" } 15 | } 16 | } 17 | rootProject.name = "FirebaseWithMVVM" 18 | include ':app' 19 | --------------------------------------------------------------------------------