Firebase Realtime Database BlogPost App Examples
Learn Firebase Realtime database using these step by step full BlogPosts App examples.
Kotlin Firebase Realtime Database BlogPosts App
This is a full blog posts app allowing you to learn Firebase Realtime Database and use it to create a fully functional app.
Here is the screenshot of what is created:
Data Model
The database has four "root" nodes:
Database Rules
Below are some samples rules that limit access and validate data:
{
"rules": {
// User profiles are only readable/writable by the user who owns it
"users": {
"$UID": {
".read": "auth.uid == $UID",
".write": "auth.uid == $UID"
}
},
// Posts can be read by anyone but only written by logged-in users.
"posts": {
".read": true,
".write": "auth.uid != null",
"$POSTID": {
// UID must match logged in user and is fixed once set
"uid": {
".validate": "(data.exists() && data.val() == newData.val()) || newData.val() == auth.uid"
},
// User can only update own stars
"stars": {
"$UID": {
".validate": "auth.uid == $UID"
}
}
}
},
// User posts can be read by anyone but only written by the user that owns it,
// and with a matching UID
"user-posts": {
".read": true,
"$UID": {
"$POSTID": {
".write": "auth.uid == $UID",
".validate": "data.exists() || newData.child('uid').val() == auth.uid"
}
}
},
// Comments can be read by anyone but only written by a logged in user
"post-comments": {
".read": true,
".write": "auth.uid != null",
"$POSTID": {
"$COMMENTID": {
// UID must match logged in user and is fixed once set
"uid": {
".validate": "(data.exists() && data.val() == newData.val()) || newData.val() == auth.uid"
}
}
}
}
}
}
Let's start:
Step 1. Dependencies
We need to add some dependencies in our app/build.gradle
file as shown below:
(a). build.gradle
Our app-level
build.gradle
.
We Prepare our dependencies as shown below. You may use later versions.
We will enable view binding by setting the viewBinding
property to true.
We then declare our app dependencies under the dependencies
closure. We will need the following 14 dependencies:
- Our
Appcompat
library. - Our
Recyclerview
library. - Our
Material
library. - Our
Navigation-fragment-ktx
library. - Our
Navigation-ui-ktx
library. - Our
Firebase-bom
library. - Our
Com.google.firebase
library. - Our
Com.google.firebase
library. - Our
Com.google.firebase
library. - Our
Com.google.firebase
library. - Our
Firebase-ui-database
library. - Our
Core-runtime
library.
Here is our full app/build.gradle
:
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'com.google.gms.google-services'
}
check.dependsOn 'assembleDebugAndroidTest'
android {
compileSdkVersion 33
defaultConfig {
applicationId "com.google.firebase.quickstart.database"
minSdkVersion 19
targetSdkVersion 33
versionCode 1
versionName "1.0"
multiDexEnabled true
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
}
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
buildFeatures {
viewBinding = true
}
}
dependencies {
implementation project(":internal:lintchecks")
implementation project(":internal:chooserx")
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'com.google.android.material:material:1.6.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.2'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.2'
// Import the Firebase BoM (see: https://firebase.google.com/docs/android/learn-more#bom)
implementation platform('com.google.firebase:firebase-bom:30.5.0')
// Firebase Realtime Database (Java)
implementation 'com.google.firebase:firebase-database'
// Firebase Realtime Database (Kotlin)
implementation 'com.google.firebase:firebase-database-ktx'
// Firebase Authentication (Java)
implementation 'com.google.firebase:firebase-auth'
// Firebase Authentication (Kotlin)
implementation 'com.google.firebase:firebase-auth-ktx'
implementation 'com.firebaseui:firebase-ui-database:8.0.2'
// Needed to fix a dependency conflict with FirebaseUI'
implementation 'androidx.arch.core:core-runtime:2.1.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.test:rules:1.4.0'
androidTestImplementation 'androidx.test:runner:1.4.0'
}
Step 2. Create Firebase App
You will need to create or setup a Firebase app first. This link explains how to do so or follow these steps:
- Add Firebase to your Android Project.
- Log in to the Firebase Console.
- Go to Auth tab and enable Email/Password authentication.
- Download and Run the sample on Android device or emulator.
Step 3. Our Android Manifest
(a). AndroidManifest.xml
Our
AndroidManifest
file.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.firebase.quickstart.database">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".EntryChoiceActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".java.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" />
<activity
android:name=".kotlin.MainActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar" />
</application>
</manifest>
Step 4. Create Navigation Rules
Create a directory known as navigation
inside your res
directory and add the following navigation rules:
(a). nav_graph_kotlin.xml
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph_kotlin"
app:startDestination="@id/SignInFragment">
<fragment
android:id="@+id/SignInFragment"
android:name="com.google.firebase.quickstart.database.kotlin.SignInFragment"
android:label="@string/title_sign_in"
tools:layout="@layout/fragment_sign_in">
<action
android:id="@+id/action_SignInFragment_to_MainFragment"
app:popUpTo="@id/MainFragment"
app:destination="@id/MainFragment" />
</fragment>
<fragment
android:id="@+id/MainFragment"
android:name="com.google.firebase.quickstart.database.kotlin.MainFragment"
android:label="@string/app_name"
tools:layout="@layout/fragment_main">
<action
android:id="@+id/action_MainFragment_to_NewPostFragment"
app:destination="@id/NewPostFragment" />
<action
android:id="@+id/action_MainFragment_to_SignInFragment"
app:popUpTo="@id/SignInFragment"
app:destination="@id/SignInFragment" />
<action
android:id="@+id/action_MainFragment_to_PostDetailFragment"
app:destination="@id/PostDetailFragment" >
<argument android:name="post_key" app:nullable="false" app:argType="string" android:defaultValue=""/>
</action>
</fragment>
<fragment
android:id="@+id/PostDetailFragment"
android:name="com.google.firebase.quickstart.database.kotlin.PostDetailFragment"
android:label="@string/app_name"
tools:layout="@layout/fragment_post_detail">
</fragment>
<fragment
android:id="@+id/NewPostFragment"
android:name="com.google.firebase.quickstart.database.kotlin.NewPostFragment"
android:label="@string/app_name"
tools:layout="@layout/fragment_new_post">
<action
android:id="@+id/action_NewPostFragment_to_MainFragment"
app:destination="@id/MainFragment"
app:popUpTo="@id/MainFragment">
</action>
</fragment>
</navigation>
Step 5. Create Menus
Create a directory known as menu
inside your res
directory and add the following menu files:
(a). menu_main.xml
Inside your /res/menu/
directory create a menu resource file named menu_main.xml
and add menu items as shown below:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<item
android:id="@+id/action_logout"
android:title="@string/menu_logout"
android:visible="true"
app:showAsAction="never" />
</menu>
Step 6. Design Layouts
In Android we design our UI interfaces using XML. So let's create the following layouts:
(a). item_post.xml
Our
item_post
layout.
Inside your /res/layout/
directory create an xml layout file named item_post.xml
and add the following:
com.google.android.material.card.MaterialCardView
androidx.constraintlayout.widget.ConstraintLayout
ImageView
TextView
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
tools:viewBindingIgnore="true">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp">
<include
android:id="@+id/postAuthorLayout"
layout="@layout/include_post_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:src="@drawable/ic_toggle_star_outline_24"
app:layout_constraintBottom_toBottomOf="@+id/postAuthorLayout"
app:layout_constraintEnd_toStartOf="@+id/postNumStars"
app:layout_constraintTop_toTopOf="@+id/postAuthorLayout" />
<TextView
android:id="@+id/postNumStars"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="@+id/star"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/star"
tools:text="7" />
<include
layout="@layout/include_post_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postAuthorLayout" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>
(b). item_comment.xml
Our
item_comment
layout.
Inside your /res/layout/
directory create an xml layout file named item_comment.xml
and add these 3 UI widgets and ViewGroups:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="8dp"
android:paddingBottom="8dp"
tools:viewBindingIgnore="true">
<ImageView
android:id="@+id/commentPhoto"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_centerVertical="true"
android:src="@drawable/ic_action_account_circle_40"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/commentAuthor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:textStyle="bold"
app:layout_constraintStart_toEndOf="@+id/commentPhoto"
app:layout_constraintTop_toTopOf="parent"
tools:text="John Doe" />
<TextView
android:id="@+id/commentBody"
style="@style/TextAppearance.AppCompat.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="@+id/commentAuthor"
app:layout_constraintTop_toBottomOf="@+id/commentAuthor"
tools:text="This is the comment text.." />
</androidx.constraintlayout.widget.ConstraintLayout>
(c). include_post_text.xml
Our
include_post_text
layout.
Design your XML layout using the following 2 UI widgets and ViewGroups:
androidx.constraintlayout.widget.ConstraintLayout
TextView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/postTitle"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textStyle="bold"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="My First Post" />
<TextView
android:id="@+id/postBody"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postTitle"
tools:text="@string/lorem" />
</androidx.constraintlayout.widget.ConstraintLayout>
(d). include_post_author.xml
Our
include_post_author
layout.
androidx.constraintlayout.widget.ConstraintLayout
ImageView
TextView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:id="@+id/postAuthorPhoto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_action_account_circle_40"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/postAuthor"
style="@style/Base.TextAppearance.AppCompat.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:gravity="center_vertical"
app:layout_constraintBottom_toBottomOf="@+id/postAuthorPhoto"
app:layout_constraintStart_toEndOf="@+id/postAuthorPhoto"
app:layout_constraintTop_toTopOf="@+id/postAuthorPhoto"
tools:text="[email protected]" />
</androidx.constraintlayout.widget.ConstraintLayout>
(e). fragment_sign_in.xml
Our
fragment_sign_in
layout.
We design our Sign-in screen:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:indeterminate="true"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/firebase_lockup_400"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/progressBar" />
<EditText
android:id="@+id/fieldEmail"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:ellipsize="end"
android:hint="@string/hint_email"
android:inputType="textEmailAddress"
android:maxLines="1"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintEnd_toStartOf="@+id/fieldPassword"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/icon" />
<EditText
android:id="@+id/fieldPassword"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:hint="@string/hint_password"
android:inputType="textPassword"
android:maxLines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/fieldEmail"
app:layout_constraintTop_toTopOf="@+id/fieldEmail" />
<Button
android:id="@+id/buttonSignIn"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/sign_in"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintEnd_toStartOf="@+id/buttonSignUp"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fieldEmail" />
<Button
android:id="@+id/buttonSignUp"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:text="@string/sign_up"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/buttonSignIn"
app:layout_constraintTop_toTopOf="@+id/buttonSignIn" />
</androidx.constraintlayout.widget.ConstraintLayout>
(f). fragment_post_detail.xml
Our
fragment_post_detail
layout.
This will be our Blog Post Detail content screen. Create an xml layout file named fragment_post_detail.xml
and add the following 4 UI widgets and ViewGroups:
androidx.constraintlayout.widget.ConstraintLayout
EditText
com.google.android.material.button.MaterialButton
androidx.recyclerview.widget.RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingBottom="@dimen/activity_vertical_margin">
<include
android:id="@+id/postAuthorLayout"
layout="@layout/include_post_author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<include
android:id="@+id/postTextLayout"
layout="@layout/include_post_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postAuthorLayout" />
<EditText
android:id="@+id/fieldCommentText"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Write a comment..."
android:maxLines="1"
app:layout_constraintEnd_toStartOf="@+id/buttonPostComment"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="8"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/postTextLayout" />
<com.google.android.material.button.MaterialButton
android:id="@+id/buttonPostComment"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="Post"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_weight="2"
app:layout_constraintStart_toEndOf="@+id/fieldCommentText"
app:layout_constraintTop_toTopOf="@+id/fieldCommentText" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerPostComments"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/buttonPostComment"
tools:listitem="@layout/item_comment" />
</androidx.constraintlayout.widget.ConstraintLayout>
(g). fragment_new_post.xml
Our
fragment_new_post
layout.
Create an xml layout file named fragment_new_post.xml
and add the following 3 UI widgets and ViewGroups:
androidx.constraintlayout.widget.ConstraintLayout
EditText
com.google.android.material.floatingactionbutton.FloatingActionButton
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/fieldTitle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:hint="Title"
android:maxLines="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<EditText
android:id="@+id/fieldBody"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:hint="Write your post..."
android:inputType="textMultiLine"
android:maxLines="10"
android:scrollHorizontally="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fieldTitle"
/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fabSubmitPost"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:src="@drawable/ic_navigation_check_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
(h). fragment_main.xml
Our
fragment_main
layout.
Create an xml layout file named fragment_main.xml
and add the following:
androidx.constraintlayout.widget.ConstraintLayout
com.google.android.material.tabs.TabLayout
androidx.viewpager2.widget.ViewPager2
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tabs" />
</androidx.constraintlayout.widget.ConstraintLayout>
(i). fragment_all_posts.xml
Our
fragment_all_posts
layout.
Create an xml layout file named fragment_all_posts.xml
and add these:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:viewBindingIgnore="true">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/messagesList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:padding="5dp"
android:scrollbars="vertical"
tools:listitem="@layout/item_post" />
</FrameLayout>
(j). activity_main.xml
Our
activity_main
layout.
Design your main activity layout using the following 6 UI widgets and ViewGroups:
androidx.coordinatorlayout.widget.CoordinatorLayout
com.google.android.material.appbar.AppBarLayout
androidx.appcompat.widget.Toolbar
com.google.android.material.floatingactionbutton.FloatingActionButton
FrameLayout
fragment
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
app:srcCompat="@drawable/ic_image_edit" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<fragment
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</FrameLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
Step 7. Write Code
Finally we need to write our code as follows:
(a). Comment.kt
Our
Comment
class.
Create a Kotlin file named Comment.kt
.
Here is the full code:
package replace_with_your_package_name
import com.google.firebase.database.IgnoreExtraProperties
@IgnoreExtraProperties
data class Comment(
var uid: String? = "",
var author: String? = "",
var text: String? = ""
)
(b). Post.kt
Our
Post
class.
Create a Kotlin file named Post.kt
.
We will be creating the following methods:
toMap(): Map<String, Any?>
.
(a). Our toMap()
function
Write the toMap()
function as follows:
fun toMap(): Map<String, Any?> {
return mapOf(
"uid" to uid,
"author" to author,
"title" to title,
"body" to body,
"starCount" to starCount,
"stars" to stars
)
}
Here is the full code:
package replace_with_your_package_name
import com.google.firebase.database.Exclude
import com.google.firebase.database.IgnoreExtraProperties
import java.util.HashMap
@IgnoreExtraProperties
data class Post(
var uid: String? = "",
var author: String? = "",
var title: String? = "",
var body: String? = "",
var starCount: Int = 0,
var stars: MutableMap<String, Boolean> = HashMap()
) {
@Exclude
fun toMap(): Map<String, Any?> {
return mapOf(
"uid" to uid,
"author" to author,
"title" to title,
"body" to body,
"starCount" to starCount,
"stars" to stars
)
}
}
(c). User.kt
Our
User
class.
Create a Kotlin file named User.kt
.
Here is the full code:
package replace_with_your_package_name
import com.google.firebase.database.IgnoreExtraProperties
@IgnoreExtraProperties
data class User(
var username: String? = "",
var email: String? = ""
)
(d). PostViewHolder.kt
Our
PostViewHolder
class.
Create a Kotlin file named PostViewHolder.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
RecyclerView
from theandroidx.recyclerview.widget
package.View
from theandroid.view
package.ImageView
from theandroid.widget
package.TextView
from theandroid.widget
package.
Next create a class that derives from RecyclerView.ViewHolder(itemView)
and add its contents as follows:
We will be creating the following methods:
bindToPost(post: Post, starClickListener: View.OnClickListener)
.setLikedState(parameter)
- Our function will take aBoolean
object as a parameter.
(a). Our setLikedState()
function
Write the setLikedState()
function as follows:
fun setLikedState(liked: Boolean) {
if (liked) {
star.setImageResource(R.drawable.ic_toggle_star_24)
} else {
star.setImageResource(R.drawable.ic_toggle_star_outline_24)
}
}
(b). Our bindToPost()
function
Write the bindToPost()
function as follows:
fun bindToPost(post: Post, starClickListener: View.OnClickListener) {
postTitle.text = post.title
postAuthor.text = post.author
postNumStars.text = post.starCount.toString()
postBody.text = post.body
star.setOnClickListener(starClickListener)
}
Here is the full code:
package replace_with_your_package_name
import androidx.recyclerview.widget.RecyclerView
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.kotlin.models.Post
class PostViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val postTitle: TextView = itemView.findViewById(R.id.postTitle)
private val postAuthor: TextView = itemView.findViewById(R.id.postAuthor)
private val postNumStars: TextView = itemView.findViewById(R.id.postNumStars)
private val postBody: TextView = itemView.findViewById(R.id.postBody)
private val star: ImageView = itemView.findViewById(R.id.star)
fun bindToPost(post: Post, starClickListener: View.OnClickListener) {
postTitle.text = post.title
postAuthor.text = post.author
postNumStars.text = post.starCount.toString()
postBody.text = post.body
star.setOnClickListener(starClickListener)
}
fun setLikedState(liked: Boolean) {
if (liked) {
star.setImageResource(R.drawable.ic_toggle_star_24)
} else {
star.setImageResource(R.drawable.ic_toggle_star_outline_24)
}
}
}
(e). CommentViewHolder.kt
Our
CommentViewHolder
class.
Create a Kotlin file named CommentViewHolder.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
View
from theandroid.view
package.TextView
from theandroid.widget
package.RecyclerView
from theandroidx.recyclerview.widget
package.
Next create a class that derives from RecyclerView.ViewHolder(itemView)
and add its contents as follows:
We will be creating the following methods:
bind(parameter)
- Pass to this method aComment
object as a parameter.
(a). Our bind()
function
Write the bind()
function as follows:
fun bind(comment: Comment) {
itemView.findViewById<TextView>(R.id.commentAuthor).text = comment.author
itemView.findViewById<TextView>(R.id.commentBody).text = comment.text
}
Here is the full code:
package replace_with_your_package_name
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.kotlin.models.Comment
class CommentViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bind(comment: Comment) {
itemView.findViewById<TextView>(R.id.commentAuthor).text = comment.author
itemView.findViewById<TextView>(R.id.commentBody).text = comment.text
}
}
(f). RecentPostsFragment.kt
Our
RecentPostsFragment
class.
Create a Kotlin file named RecentPostsFragment.kt
.
Next create a class that derives from PostListFragment
and add its contents as follows:
We will be overriding the following functions:
getQuery(databaseReference: DatabaseReference): Query
.
Here is the full code:
package replace_with_your_package_name
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.Query
class RecentPostsFragment : PostListFragment() {
override fun getQuery(databaseReference: DatabaseReference): Query {
// [START recent_posts_query]
// Last 100 posts, these are automatically the 100 most recent
// due to sorting by push() keys.
return databaseReference.child("posts")
.limitToFirst(100)
// [END recent_posts_query]
}
}
(g). PostListFragment.kt
Our
PostListFragment
class.
Create a Kotlin file named PostListFragment.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.Log
from theandroid.util
package.LayoutInflater
from theandroid.view
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.bundleOf
from theandroidx.core.os
package.Fragment
from theandroidx.fragment.app
package.findNavController
from theandroidx.navigation
package.LinearLayoutManager
from theandroidx.recyclerview.widget
package.RecyclerView
from theandroidx.recyclerview.widget
package.
Next create a class that derives from Fragment
and add its contents as follows:
We will be overriding the following functions:
onActivityCreated(savedInstanceState: Bundle?)
.onCreateViewHolder(viewGroup: ViewGroup, i: Int): PostViewHolder
.onBindViewHolder(viewHolder: PostViewHolder, position: Int, model: Post)
.doTransaction(mutableData: MutableData): Transaction.Result
.onStart()
.onStop()
.
We will be creating the following methods:
onStarClicked(parameter)
- Our function will take aDatabaseReference
object as a parameter.
(a). Our onStarClicked()
function
Write the onStarClicked()
function as follows:
private fun onStarClicked(postRef: DatabaseReference) {
postRef.runTransaction(object : Transaction.Handler {
override fun doTransaction(mutableData: MutableData): Transaction.Result {
val p = mutableData.getValue(Post::class.java)
?: return Transaction.success(mutableData)
if (p.stars.containsKey(uid)) {
// Unstar the post and remove self from stars
p.starCount = p.starCount - 1
p.stars.remove(uid)
} else {
// Star the post and add self to stars
p.starCount = p.starCount + 1
p.stars[uid] = true
}
// Set value and report transaction success
mutableData.value = p
return Transaction.success(mutableData)
}
override fun onComplete(
databaseError: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
}
})
}
(b). Our getQuery()
function
Write the getQuery()
function as follows:
abstract fun getQuery(databaseReference: DatabaseReference): Query
companion object {
private const val TAG = "PostListFragment"
}
Here is the full code:
package replace_with_your_package_name
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.firebase.ui.database.FirebaseRecyclerAdapter
import com.firebase.ui.database.FirebaseRecyclerOptions
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.MutableData
import com.google.firebase.database.Query
import com.google.firebase.database.Transaction
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.kotlin.PostDetailFragment
import com.google.firebase.quickstart.database.kotlin.models.Post
import com.google.firebase.quickstart.database.kotlin.viewholder.PostViewHolder
abstract class PostListFragment : Fragment() {
// [START define_database_reference]
private lateinit var database: DatabaseReference
// [END define_database_reference]
private lateinit var recycler: RecyclerView
private lateinit var manager: LinearLayoutManager
private var adapter: FirebaseRecyclerAdapter<Post, PostViewHolder>? = null
val uid: String
get() = Firebase.auth.currentUser!!.uid
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
super.onCreateView(inflater, container, savedInstanceState)
val rootView = inflater.inflate(R.layout.fragment_all_posts, container, false)
// [START create_database_reference]
database = Firebase.database.reference
// [END create_database_reference]
recycler = rootView.findViewById(R.id.messagesList)
recycler.setHasFixedSize(true)
return rootView
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
// Set up Layout Manager, reverse layout
manager = LinearLayoutManager(activity)
manager.reverseLayout = true
manager.stackFromEnd = true
recycler.layoutManager = manager
// Set up FirebaseRecyclerAdapter with the Query
val postsQuery = getQuery(database)
val options = FirebaseRecyclerOptions.Builder<Post>()
.setQuery(postsQuery, Post::class.java)
.build()
adapter = object : FirebaseRecyclerAdapter<Post, PostViewHolder>(options) {
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): PostViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
return PostViewHolder(inflater.inflate(R.layout.item_post, viewGroup, false))
}
override fun onBindViewHolder(viewHolder: PostViewHolder, position: Int, model: Post) {
val postRef = getRef(position)
// Set click listener for the whole post view
val postKey = postRef.key
viewHolder.itemView.setOnClickListener {
// Launch PostDetailFragment
val navController = requireActivity().findNavController(R.id.nav_host_fragment)
val args = bundleOf(PostDetailFragment.EXTRA_POST_KEY to postKey)
navController.navigate(R.id.action_MainFragment_to_PostDetailFragment, args)
}
// Determine if the current user has liked this post and set UI accordingly
viewHolder.setLikedState(model.stars.containsKey(uid))
// Bind Post to ViewHolder, setting OnClickListener for the star button
viewHolder.bindToPost(model) {
// Need to write to both places the post is stored
val globalPostRef = database.child("posts").child(postRef.key!!)
val userPostRef = database.child("user-posts").child(model.uid!!).child(postRef.key!!)
// Run two transactions
onStarClicked(globalPostRef)
onStarClicked(userPostRef)
}
}
}
recycler.adapter = adapter
}
// [START post_stars_transaction]
private fun onStarClicked(postRef: DatabaseReference) {
postRef.runTransaction(object : Transaction.Handler {
override fun doTransaction(mutableData: MutableData): Transaction.Result {
val p = mutableData.getValue(Post::class.java)
?: return Transaction.success(mutableData)
if (p.stars.containsKey(uid)) {
// Unstar the post and remove self from stars
p.starCount = p.starCount - 1
p.stars.remove(uid)
} else {
// Star the post and add self to stars
p.starCount = p.starCount + 1
p.stars[uid] = true
}
// Set value and report transaction success
mutableData.value = p
return Transaction.success(mutableData)
}
override fun onComplete(
databaseError: DatabaseError?,
committed: Boolean,
currentData: DataSnapshot?
) {
// Transaction completed
Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
}
})
}
// [END post_stars_transaction]
override fun onStart() {
super.onStart()
adapter?.startListening()
}
override fun onStop() {
super.onStop()
adapter?.stopListening()
}
abstract fun getQuery(databaseReference: DatabaseReference): Query
companion object {
private const val TAG = "PostListFragment"
}
}
(h). MyTopPostsFragment.kt
Our
MyTopPostsFragment
class.
Create a Kotlin file named MyTopPostsFragment.kt
.
Next create a class that derives from PostListFragment
and add its contents as follows:
We will be overriding the following functions:
getQuery(databaseReference: DatabaseReference): Query
.
Here is the full code:
package replace_with_your_package_name
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.Query
class MyTopPostsFragment : PostListFragment() {
override fun getQuery(databaseReference: DatabaseReference): Query {
// My top posts by number of stars
val myUserId = uid
return databaseReference.child("user-posts").child(myUserId)
.orderByChild("starCount")
}
}
(i). MyPostsFragment.kt
Our
MyPostsFragment
class.
Create a Kotlin file named MyPostsFragment.kt
.
Next create a class that derives from PostListFragment
and add its contents as follows:
We will be overriding the following functions:
getQuery(databaseReference: DatabaseReference): Query
.
Here is the full code:
package replace_with_your_package_name
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.Query
class MyPostsFragment : PostListFragment() {
override fun getQuery(databaseReference: DatabaseReference): Query {
// All my posts
return databaseReference.child("user-posts")
.child(uid)
}
}
(j). EntryChoiceActivity.kt
Our
EntryChoiceActivity
class.
Create a Kotlin file named EntryChoiceActivity.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Intent
from theandroid.content
package.
Next create a class that derives from BaseEntryChoiceActivity
and add its contents as follows:
We will be overriding the following functions:
getChoices(): List<Choice>
.
Here is the full code:
package replace_with_your_package_name
import android.content.Intent
import com.firebase.example.internal.BaseEntryChoiceActivity
import com.firebase.example.internal.Choice
class EntryChoiceActivity : BaseEntryChoiceActivity() {
override fun getChoices(): List<Choice> {
return listOf(
Choice(
"Java",
"Run the Firebase Realtime Database quickstart written in Java.",
Intent(this, com.google.firebase.quickstart.database.java.MainActivity::class.java)),
Choice(
"Kotlin",
"Run the Firebase Realtime Database quickstart written in Kotlin.",
Intent(this, com.google.firebase.quickstart.database.kotlin.MainActivity::class.java))
)
}
}
(k). SignInFragment.kt
Our
SignInFragment
class.
Create a Kotlin file named SignInFragment.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.TextUtils
from theandroid.text
package.Log
from theandroid.util
package.LayoutInflater
from theandroid.view
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.Toast
from theandroid.widget
package.findNavController
from theandroidx.navigation.fragment
package.
Next create a class that derives from BaseFragment
and add its contents as follows:
We will be overriding the following functions:
onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
.onViewCreated(view: View, savedInstanceState: Bundle?)
.onStart()
.onDestroy()
.
We will be creating the following methods:
signIn()
.signUp()
.onAuthSuccess(parameter)
- Our function will take aFirebaseUser
object as a parameter.usernameFromEmail(parameter)
- Pass to this method aString
object as a parameter.validateForm(): Boolean
.writeNewUser(userId: String, name: String, email: String?)
.
(a). Our usernameFromEmail()
function
Write the usernameFromEmail()
function as follows:
private fun usernameFromEmail(email: String): String {
return if (email.contains("@")) {
email.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
} else {
email
}
}
(b). Our validateForm()
function
Write the validateForm()
function as follows:
private fun validateForm(): Boolean {
var result = true
if (TextUtils.isEmpty(binding.fieldEmail.text.toString())) {
binding.fieldEmail.error = "Required"
result = false
} else {
binding.fieldEmail.error = null
}
if (TextUtils.isEmpty(binding.fieldPassword.text.toString())) {
binding.fieldPassword.error = "Required"
result = false
} else {
binding.fieldPassword.error = null
}
return result
}
(c). Our writeNewUser()
function
Write the writeNewUser()
function as follows:
private fun writeNewUser(userId: String, name: String, email: String?) {
val user = User(name, email)
database.child("users").child(userId).setValue(user)
}
(d). Our signIn()
function
Write the signIn()
function as follows:
private fun signIn() {
Log.d(TAG, "signIn")
if (!validateForm()) {
return
}
showProgressBar()
val email = binding.fieldEmail.text.toString()
val password = binding.fieldPassword.text.toString()
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()) { task ->
Log.d(TAG, "signIn:onComplete:" + task.isSuccessful)
hideProgressBar()
if (task.isSuccessful) {
onAuthSuccess(task.result?.user!!)
} else {
Toast.makeText(context, "Sign In Failed",
Toast.LENGTH_SHORT).show()
}
}
}
(e). Our onAuthSuccess()
function
Write the onAuthSuccess()
function as follows:
private fun onAuthSuccess(user: FirebaseUser) {
val username = usernameFromEmail(user.email!!)
// Write new user
writeNewUser(user.uid, username, user.email)
// Go to MainFragment
findNavController().navigate(R.id.action_SignInFragment_to_MainFragment)
}
(f). Our signUp()
function
Write the signUp()
function as follows:
private fun signUp() {
Log.d(TAG, "signUp")
if (!validateForm()) {
return
}
showProgressBar()
val email = binding.fieldEmail.text.toString()
val password = binding.fieldPassword.text.toString()
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()) { task ->
Log.d(TAG, "createUser:onComplete:" + task.isSuccessful)
hideProgressBar()
if (task.isSuccessful) {
onAuthSuccess(task.result?.user!!)
} else {
Toast.makeText(context, "Sign Up Failed",
Toast.LENGTH_SHORT).show()
}
}
}
Here is the full code:
package replace_with_your_package_name
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.navigation.fragment.findNavController
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.auth.ktx.auth
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ktx.database
import com.google.firebase.ktx.Firebase
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.databinding.FragmentSignInBinding
import com.google.firebase.quickstart.database.kotlin.models.User
class SignInFragment : BaseFragment() {
private var _binding: FragmentSignInBinding? = null
private val binding get() = _binding!!
private lateinit var database: DatabaseReference
private lateinit var auth: FirebaseAuth
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentSignInBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
database = Firebase.database.reference
auth = Firebase.auth
setProgressBar(R.id.progressBar)
// Click listeners
with(binding) {
buttonSignIn.setOnClickListener { signIn() }
buttonSignUp.setOnClickListener { signUp() }
}
}
override fun onStart() {
super.onStart()
// Check auth on Fragment start
auth.currentUser?.let {
onAuthSuccess(it)
}
}
private fun signIn() {
Log.d(TAG, "signIn")
if (!validateForm()) {
return
}
showProgressBar()
val email = binding.fieldEmail.text.toString()
val password = binding.fieldPassword.text.toString()
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()) { task ->
Log.d(TAG, "signIn:onComplete:" + task.isSuccessful)
hideProgressBar()
if (task.isSuccessful) {
onAuthSuccess(task.result?.user!!)
} else {
Toast.makeText(context, "Sign In Failed",
Toast.LENGTH_SHORT).show()
}
}
}
private fun signUp() {
Log.d(TAG, "signUp")
if (!validateForm()) {
return
}
showProgressBar()
val email = binding.fieldEmail.text.toString()
val password = binding.fieldPassword.text.toString()
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener(requireActivity()) { task ->
Log.d(TAG, "createUser:onComplete:" + task.isSuccessful)
hideProgressBar()
if (task.isSuccessful) {
onAuthSuccess(task.result?.user!!)
} else {
Toast.makeText(context, "Sign Up Failed",
Toast.LENGTH_SHORT).show()
}
}
}
private fun onAuthSuccess(user: FirebaseUser) {
val username = usernameFromEmail(user.email!!)
// Write new user
writeNewUser(user.uid, username, user.email)
// Go to MainFragment
findNavController().navigate(R.id.action_SignInFragment_to_MainFragment)
}
private fun usernameFromEmail(email: String): String {
return if (email.contains("@")) {
email.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
} else {
email
}
}
private fun validateForm(): Boolean {
var result = true
if (TextUtils.isEmpty(binding.fieldEmail.text.toString())) {
binding.fieldEmail.error = "Required"
result = false
} else {
binding.fieldEmail.error = null
}
if (TextUtils.isEmpty(binding.fieldPassword.text.toString())) {
binding.fieldPassword.error = "Required"
result = false
} else {
binding.fieldPassword.error = null
}
return result
}
private fun writeNewUser(userId: String, name: String, email: String?) {
val user = User(name, email)
database.child("users").child(userId).setValue(user)
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
companion object {
private const val TAG = "SignInFragment"
}
}
(m). PostDetailFragment.kt
Our
PostDetailFragment
class.
Create a Kotlin file named PostDetailFragment.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Context
from theandroid.content
package.Bundle
from theandroid.os
package.Log
from theandroid.util
package.LayoutInflater
from theandroid.view
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.Toast
from theandroid.widget
package.LinearLayoutManager
from theandroidx.recyclerview.widget
package.RecyclerView
from theandroidx.recyclerview.widget
package.
Next create a class that derives from BaseFragment
and add its contents as follows:
We will be overriding the following functions:
onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View
.onViewCreated(view: View, savedInstanceState: Bundle?)
.onStart()
.onDataChange(dataSnapshot: DataSnapshot)
.onCancelled(databaseError: DatabaseError)
.onStop()
.onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?)
.onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?)
.onChildRemoved(dataSnapshot: DataSnapshot)
.onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?)
.onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder
.onBindViewHolder(holder: CommentViewHolder, position: Int)
.onDestroy()
.
We will be creating the following methods:
postComment()
.cleanupListener()
.
(a). Our postComment()
function
Write the postComment()
function as follows:
private fun postComment() {
val uid = uid
Firebase.database.reference.child("users").child(uid)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get user information
val user = dataSnapshot.getValue<User>() ?: return
val authorName = user.username
// Create new comment object
val commentText = binding.fieldCommentText.text.toString()
val comment = Comment(uid, authorName, commentText)
// Push the comment, it will appear in the list
commentsReference.push().setValue(comment)
// Clear the field
binding.fieldCommentText.text = null
}
override fun onCancelled(databaseError: DatabaseError) {
}
})
}
(b). Our cleanupListener()
function
Write the cleanupListener()
function as follows:
Here is the full code:
package replace_with_your_package_name
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.database.ChildEventListener
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.databinding.FragmentPostDetailBinding
import com.google.firebase.quickstart.database.kotlin.models.Comment
import com.google.firebase.quickstart.database.kotlin.models.Post
import com.google.firebase.quickstart.database.kotlin.models.User
import com.google.firebase.quickstart.database.kotlin.viewholder.CommentViewHolder
import java.lang.IllegalArgumentException
import java.util.ArrayList
class PostDetailFragment : BaseFragment() {
private lateinit var postKey: String
private lateinit var postReference: DatabaseReference
private lateinit var commentsReference: DatabaseReference
private var postListener: ValueEventListener? = null
private var adapter: CommentAdapter? = null
private var _binding: FragmentPostDetailBinding? = null
private val binding get() = _binding!!
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
_binding = FragmentPostDetailBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Get post key from arguments
postKey = requireArguments().getString(EXTRA_POST_KEY)
?: throw IllegalArgumentException("Must pass EXTRA_POST_KEY")
// Initialize Database
postReference = Firebase.database.reference
.child("posts").child(postKey)
commentsReference = Firebase.database.reference
.child("post-comments").child(postKey)
// Initialize Views
with(binding) {
buttonPostComment.setOnClickListener { postComment() }
recyclerPostComments.layoutManager = LinearLayoutManager(context)
}
}
override fun onStart() {
super.onStart()
// Add value event listener to the post
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get Post object and use the values to update the UI
val post = dataSnapshot.getValue<Post>()
post?.let {
binding.postAuthorLayout.postAuthor.text = it.author
with(binding.postTextLayout) {
postTitle.text = it.title
postBody.text = it.body
}
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
Toast.makeText(context, "Failed to load post.",
Toast.LENGTH_SHORT).show()
}
}
postReference.addValueEventListener(postListener)
// Keep copy of post listener so we can remove it when app stops
this.postListener = postListener
// Listen for comments
adapter = CommentAdapter(requireContext(), commentsReference)
binding.recyclerPostComments.adapter = adapter
}
override fun onStop() {
super.onStop()
// Remove post value event listener
postListener?.let {
postReference.removeEventListener(it)
}
// Clean up comments listener
adapter?.cleanupListener()
}
private fun postComment() {
val uid = uid
Firebase.database.reference.child("users").child(uid)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get user information
val user = dataSnapshot.getValue<User>() ?: return
val authorName = user.username
// Create new comment object
val commentText = binding.fieldCommentText.text.toString()
val comment = Comment(uid, authorName, commentText)
// Push the comment, it will appear in the list
commentsReference.push().setValue(comment)
// Clear the field
binding.fieldCommentText.text = null
}
override fun onCancelled(databaseError: DatabaseError) {
}
})
}
private class CommentAdapter(
private val context: Context,
private val databaseReference: DatabaseReference
) : RecyclerView.Adapter<CommentViewHolder>() {
private val childEventListener: ChildEventListener?
private val commentIds = ArrayList<String>()
private val comments = ArrayList<Comment>()
init {
// Create child event listener
val childEventListener = object : ChildEventListener {
override fun onChildAdded(dataSnapshot: DataSnapshot, previousChildName: String?) {
Log.d(TAG, "onChildAdded:" + dataSnapshot.key!!)
// A new comment has been added, add it to the displayed list
val comment = dataSnapshot.getValue<Comment>()
// Update RecyclerView
commentIds.add(dataSnapshot.key!!)
comments.add(comment!!)
notifyItemInserted(comments.size - 1)
}
override fun onChildChanged(dataSnapshot: DataSnapshot, previousChildName: String?) {
Log.d(TAG, "onChildChanged: ${dataSnapshot.key}")
// A comment has changed, use the key to determine if we are displaying this
// comment and if so displayed the changed comment.
val newComment = dataSnapshot.getValue<Comment>()
val commentKey = dataSnapshot.key
val commentIndex = commentIds.indexOf(commentKey)
if (commentIndex > -1 && newComment != null) {
// Replace with the new data
comments[commentIndex] = newComment
// Update the RecyclerView
notifyItemChanged(commentIndex)
} else {
Log.w(TAG, "onChildChanged:unknown_child: $commentKey")
}
}
override fun onChildRemoved(dataSnapshot: DataSnapshot) {
Log.d(TAG, "onChildRemoved:" + dataSnapshot.key!!)
// A comment has changed, use the key to determine if we are displaying this
// comment and if so remove it.
val commentKey = dataSnapshot.key
val commentIndex = commentIds.indexOf(commentKey)
if (commentIndex > -1) {
// Remove data from the list
commentIds.removeAt(commentIndex)
comments.removeAt(commentIndex)
// Update the RecyclerView
notifyItemRemoved(commentIndex)
} else {
Log.w(TAG, "onChildRemoved:unknown_child:" + commentKey!!)
}
}
override fun onChildMoved(dataSnapshot: DataSnapshot, previousChildName: String?) {
Log.d(TAG, "onChildMoved:" + dataSnapshot.key!!)
// A comment has changed position, use the key to determine if we are
// displaying this comment and if so move it.
val movedComment = dataSnapshot.getValue<Comment>()
val commentKey = dataSnapshot.key
// ...
}
override fun onCancelled(databaseError: DatabaseError) {
Log.w(TAG, "postComments:onCancelled", databaseError.toException())
Toast.makeText(context, "Failed to load comments.",
Toast.LENGTH_SHORT).show()
}
}
databaseReference.addChildEventListener(childEventListener)
// Store reference to listener so it can be removed on app stop
this.childEventListener = childEventListener
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {
val inflater = LayoutInflater.from(context)
val view = inflater.inflate(R.layout.item_comment, parent, false)
return CommentViewHolder(view)
}
override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
holder.bind(comments[position])
}
override fun getItemCount(): Int = comments.size
fun cleanupListener() {
childEventListener?.let {
databaseReference.removeEventListener(it)
}
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
companion object {
private const val TAG = "PostDetailFragment"
const val EXTRA_POST_KEY = "post_key"
}
}
(n). NewPostFragment.kt
Our
NewPostFragment
class.
Create a Kotlin file named NewPostFragment.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.TextUtils
from theandroid.text
package.Log
from theandroid.util
package.LayoutInflater
from theandroid.view
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.Toast
from theandroid.widget
package.findNavController
from theandroidx.navigation.fragment
package.
Next create a class that derives from BaseFragment
and add its contents as follows:
We will be overriding the following functions:
onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
.onViewCreated(view: View, savedInstanceState: Bundle?)
.onDataChange(dataSnapshot: DataSnapshot)
.onCancelled(databaseError: DatabaseError)
.onDestroy()
.
We will be creating the following methods:
submitPost()
.setEditingEnabled(parameter)
- We pass aBoolean
object as a parameter.writeNewPost(userId: String, username: String, title: String, body: String)
.
(a). Our writeNewPost()
function
Write the writeNewPost()
function as follows:
private fun writeNewPost(userId: String, username: String, title: String, body: String) {
// Create new post at /user-posts/$userid/$postid and at
// /posts/$postid simultaneously
val key = database.child("posts").push().key
if (key == null) {
Log.w(TAG, "Couldn't get push key for posts")
return
}
val post = Post(userId, username, title, body)
val postValues = post.toMap()
val childUpdates = hashMapOf<String, Any>(
"/posts/$key" to postValues,
"/user-posts/$userId/$key" to postValues
)
database.updateChildren(childUpdates)
}
(b). Our submitPost()
function
Write the submitPost()
function as follows:
private fun submitPost() {
val title = binding.fieldTitle.text.toString()
val body = binding.fieldBody.text.toString()
// Title is required
if (TextUtils.isEmpty(title)) {
binding.fieldTitle.error = REQUIRED
return
}
// Body is required
if (TextUtils.isEmpty(body)) {
binding.fieldBody.error = REQUIRED
return
}
// Disable button so there are no multi-posts
setEditingEnabled(false)
Toast.makeText(context, "Posting...", Toast.LENGTH_SHORT).show()
val userId = uid
database.child("users").child(userId).addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get user value
val user = dataSnapshot.getValue<User>()
if (user == null) {
// User is null, error out
Log.e(TAG, "User $userId is unexpectedly null")
Toast.makeText(context,
"Error: could not fetch user.",
Toast.LENGTH_SHORT).show()
} else {
// Write new post
writeNewPost(userId, user.username.toString(), title, body)
}
setEditingEnabled(true)
findNavController().navigate(R.id.action_NewPostFragment_to_MainFragment)
}
override fun onCancelled(databaseError: DatabaseError) {
Log.w(TAG, "getUser:onCancelled", databaseError.toException())
setEditingEnabled(true)
}
})
}
(c). Our setEditingEnabled()
function
Write the setEditingEnabled()
function as follows:
private fun setEditingEnabled(enabled: Boolean) {
with(binding) {
fieldTitle.isEnabled = enabled
fieldBody.isEnabled = enabled
if (enabled) {
fabSubmitPost.show()
} else {
fabSubmitPost.hide()
}
}
}
Here is the full code:
package replace_with_your_package_name
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.navigation.fragment.findNavController
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import com.google.firebase.database.ktx.database
import com.google.firebase.database.ktx.getValue
import com.google.firebase.ktx.Firebase
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.databinding.FragmentNewPostBinding
import com.google.firebase.quickstart.database.kotlin.models.Post
import com.google.firebase.quickstart.database.kotlin.models.User
class NewPostFragment : BaseFragment() {
private var _binding: FragmentNewPostBinding? = null
private val binding get() = _binding!!
private lateinit var database: DatabaseReference
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentNewPostBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
database = Firebase.database.reference
binding.fabSubmitPost.setOnClickListener { submitPost() }
}
private fun submitPost() {
val title = binding.fieldTitle.text.toString()
val body = binding.fieldBody.text.toString()
// Title is required
if (TextUtils.isEmpty(title)) {
binding.fieldTitle.error = REQUIRED
return
}
// Body is required
if (TextUtils.isEmpty(body)) {
binding.fieldBody.error = REQUIRED
return
}
// Disable button so there are no multi-posts
setEditingEnabled(false)
Toast.makeText(context, "Posting...", Toast.LENGTH_SHORT).show()
val userId = uid
database.child("users").child(userId).addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get user value
val user = dataSnapshot.getValue<User>()
if (user == null) {
// User is null, error out
Log.e(TAG, "User $userId is unexpectedly null")
Toast.makeText(context,
"Error: could not fetch user.",
Toast.LENGTH_SHORT).show()
} else {
// Write new post
writeNewPost(userId, user.username.toString(), title, body)
}
setEditingEnabled(true)
findNavController().navigate(R.id.action_NewPostFragment_to_MainFragment)
}
override fun onCancelled(databaseError: DatabaseError) {
Log.w(TAG, "getUser:onCancelled", databaseError.toException())
setEditingEnabled(true)
}
})
}
private fun setEditingEnabled(enabled: Boolean) {
with(binding) {
fieldTitle.isEnabled = enabled
fieldBody.isEnabled = enabled
if (enabled) {
fabSubmitPost.show()
} else {
fabSubmitPost.hide()
}
}
}
private fun writeNewPost(userId: String, username: String, title: String, body: String) {
// Create new post at /user-posts/$userid/$postid and at
// /posts/$postid simultaneously
val key = database.child("posts").push().key
if (key == null) {
Log.w(TAG, "Couldn't get push key for posts")
return
}
val post = Post(userId, username, title, body)
val postValues = post.toMap()
val childUpdates = hashMapOf<String, Any>(
"/posts/$key" to postValues,
"/user-posts/$userId/$key" to postValues
)
database.updateChildren(childUpdates)
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
companion object {
private const val TAG = "NewPostFragment"
private const val REQUIRED = "Required"
}
}
(o). MainFragment.kt
Our
MainFragment
class.
Create a Kotlin file named MainFragment.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.LayoutInflater
from theandroid.view
package.Menu
from theandroid.view
package.MenuInflater
from theandroid.view
package.MenuItem
from theandroid.view
package.View
from theandroid.view
package.ViewGroup
from theandroid.view
package.Fragment
from theandroidx.fragment.app
package.findNavController
from theandroidx.navigation.fragment
package.FragmentStateAdapter
from theandroidx.viewpager2.adapter
package.
Next create a class that derives from Fragment
and add its contents as follows:
We will be overriding the following functions:
onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View?
.onViewCreated(view: View, savedInstanceState: Bundle?)
.onCreateOptionsMenu(menu: Menu, inflater: MenuInflater)
.onOptionsItemSelected(item: MenuItem): Boolean
.onDestroy()
.
Here is the full code:
package replace_with_your_package_name
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.google.android.material.tabs.TabLayoutMediator
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.databinding.FragmentMainBinding
import com.google.firebase.quickstart.database.kotlin.listfragments.MyPostsFragment
import com.google.firebase.quickstart.database.kotlin.listfragments.MyTopPostsFragment
import com.google.firebase.quickstart.database.kotlin.listfragments.RecentPostsFragment
class MainFragment : Fragment() {
private var _binding: FragmentMainBinding? = null
private val binding get() = _binding!!
private lateinit var pagerAdapter: FragmentStateAdapter
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
_binding = FragmentMainBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setHasOptionsMenu(true)
// Create the adapter that will return a fragment for each section
pagerAdapter = object : FragmentStateAdapter(parentFragmentManager, viewLifecycleOwner.lifecycle) {
private val fragments = arrayOf<Fragment>(
RecentPostsFragment(),
MyPostsFragment(),
MyTopPostsFragment())
override fun createFragment(position: Int) = fragments[position]
override fun getItemCount() = fragments.size
}
// Set up the ViewPager with the sections adapter.
with(binding) {
container.adapter = pagerAdapter
TabLayoutMediator(tabs, container) { tab, position ->
tab.text = when(position) {
0 -> getString(R.string.heading_recent)
1 -> getString(R.string.heading_my_posts)
else -> getString(R.string.heading_my_top_posts)
}
}.attach()
}
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.menu_main, menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return if (item.itemId == R.id.action_logout) {
Firebase.auth.signOut()
findNavController().navigate(R.id.action_MainFragment_to_SignInFragment)
true
} else {
super.onOptionsItemSelected(item)
}
}
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
(p). MainActivity.kt
Our
MainActivity
class.
Create a Kotlin file named MainActivity.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
Bundle
from theandroid.os
package.AppCompatActivity
from theandroidx.appcompat.app
package.isGone
from theandroidx.core.view
package.isVisible
from theandroidx.core.view
package.findNavController
from theandroidx.navigation
package.
Next create a class that derives from AppCompatActivity
and add its contents as follows:
We will be overriding the following functions:
onCreate(savedInstanceState: Bundle?)
.
Here is the full code:
package replace_with_your_package_name
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.navigation.findNavController
import com.google.firebase.quickstart.database.R
import com.google.firebase.quickstart.database.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val toolbar = binding.toolbar
setSupportActionBar(toolbar)
val fab = binding.fab
val navController = findNavController(R.id.nav_host_fragment)
navController.setGraph(R.navigation.nav_graph_kotlin)
navController.addOnDestinationChangedListener { _, destination, _ ->
if (destination.id == R.id.MainFragment) {
fab.isVisible = true
fab.setOnClickListener {
navController.navigate(R.id.action_MainFragment_to_NewPostFragment)
}
} else {
fab.isGone = true
}
}
}
}
(q). BaseFragment.kt
Our
BaseFragment
class.
Create a Kotlin file named BaseFragment.kt
.
We will then add imports from android SDK and other packages. Here are some of the imports we will use in this class:
View
from theandroid.view
package.ProgressBar
from theandroid.widget
package.Fragment
from theandroidx.fragment.app
package.
Next create a class that derives from Fragment
and add its contents as follows:
We will be creating the following methods:
setProgressBar(parameter)
- This function will take aInt
object as a parameter.showProgressBar()
.hideProgressBar()
.
(a). Our hideProgressBar()
function
Write the hideProgressBar()
function as follows:
(b). Our setProgressBar()
function
Write the setProgressBar()
function as follows:
(c). Our showProgressBar()
function
Write the showProgressBar()
function as follows:
Here is the full code:
package replace_with_your_package_name
import android.view.View
import android.widget.ProgressBar
import androidx.fragment.app.Fragment
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
open class BaseFragment : Fragment() {
private var progressBar: ProgressBar? = null
val uid: String
get() = Firebase.auth.currentUser!!.uid
fun setProgressBar(resId: Int) {
progressBar = view?.findViewById(resId)
}
fun showProgressBar() {
progressBar?.visibility = View.VISIBLE
}
fun hideProgressBar() {
progressBar?.visibility = View.INVISIBLE
}
}
Reference
Below are the reference links:
No. | Link |
---|---|
2. | Read more here. |
3. | Follow code author here. |