Build Online User Presence In Kotlin Firebase Real Time DB

HOW TO BUILD ONLINE USER PRESENCE SYSTEM IN ANDROID/Kotlin USING REALTIME DATABASE


Please Subscribe Youtube| Like Facebook | Follow Twitter

For Java follow this article

Introduction

In this article we will learn how to build online user presence system using firebase real time database in our android app.

Our Example

In our example we have a list of user showing their online status that are connected in our android app through firebase realtime database. Before connecting to our database of online users, app user first needs to register using Firebase Authentication. Then that information will be saved our database. After registration user can see list of other online/offline users.

Online/Offline status is implemented using firebase db reference “.info/connected” which is updated every time the Firebase Realtime Database client’s connection state changes. So we will attach listener to it whenever user connection become active (online) or inactive (offline), we will then update online status value accordingly.

Requirements:

  1. Android Project/App in which Online User Presence  to be implemented
  2. Firebase Project

Note: First you need to create Firebase Project and then connect your app to it. You can follow this article to set it up. Also you need to have knowledge of about Firebase Authentication and Firebase Realtime database, therefore it is recommended to follow this article for realtime db and this article for firebase authentication.

Steps

Follow below steps

1) Integrate Firebase Authentication and Realtime Database in your app

2) Code Online Presence System

3) Run and test your app

1) Integrate Firebase Authentication and Realtime Database in your app

First add firebase-auth , firebase-ui-auth and firebase realtime database library at app level build.gradle file

implementation 'com.google.firebase:firebase-database:19.3.0'
implementation 'com.google.firebase:firebase-auth:19.3.1'
implementation 'com.firebaseui:firebase-ui-auth:6.2.0'

Add FirebaseTheme inside res->values->styles.xml

<style name="FirebaseTheme" parent="Theme.AppCompat.Light.DarkActionBar"> </style>

2) Code Online Presence System

Our main activity screen contain button “ONLINE USERS”, on button click we will check user registration using firebase auth. If user is new then we will popup firebase auth registration and save user info in our db. Else if user is already registered then it will directly go to next activity which displays online status of registered users list.

fun onlineUser(view: View?) {
	openOnlineUserList()
}

private fun openOnlineUserList() {
	if (checkSigInStatus()) {
		startActivity(Intent(this, OnlineUserActivity::class.java))
	} else {
		authenticate()
	}
}

var authUser: FirebaseUser? = null
private fun checkSigInStatus(): Boolean {
	authUser = FirebaseAuth.getInstance().currentUser
	return authUser != null
}

fun authenticate() {
	// Choose authentication providers
	val providers = Arrays.asList(
			EmailBuilder().build(),
			GoogleBuilder().build()
	)
	// Create and launch sign-in intent
	startActivityForResult(
			AuthUI.getInstance()
					.createSignInIntentBuilder()
					.setAvailableProviders(providers)
					.setTheme(R.style.FirebaseTheme)
					.build(),
			RC_SIGN_IN)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
	super.onActivityResult(requestCode, resultCode, data)
	if (requestCode == RC_SIGN_IN) {
		if (resultCode == Activity.RESULT_OK && FirebaseAuth.getInstance().currentUser != null) {
			openOnlineUserList()
		} else {
			Toast.makeText(this, "Sign in Failed. Please Sign in To Continue", Toast.LENGTH_SHORT).show()
		}
	}
}

companion object {
	private const val RC_SIGN_IN = 1
}

Below is xml code for main activity and onlineuser activity respectively

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Online Users"
        android:gravity="center"
        android:onClick="onlineUser"
        />

</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".OnlineUserActivity">

    <ListView
        android:id="@+id/userListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

Now on OnlineUserActivity we will first add the current user to users node in our db if previously not added and then change its online status to online. Then we have attached listener to db ref “.info/connected” which will be called whenever user online status is changed. When user goes offline normally it will take 0-3 minutes to make changes effect. This is achieved using onDisconnect() method, where we have set online status value to offline on disconnect.

After adding user to users node and setting its online status we have called populateUserList() method,  which will read all connected users online status on users node and display list on our listview. We have attached listener to users node so whenever if any user’s online status is changed then its updated value will be reflected in our users’ listview.

private fun addToUserList(user: FirebaseUser?) {

	usersListRef!!.child(user!!.uid).setValue(User(user.displayName, "Online"))
	onlineStatus = db!!.getReference("users/" + user.uid + "/onlineStatus")
	connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected")
	connectedRef!!.addValueEventListener(object : ValueEventListener {
		override fun onDataChange(snapshot: DataSnapshot) {
			val connected = snapshot.getValue(Boolean::class.java)!!
			if (connected) {
				onlineStatus!!.onDisconnect().setValue("offline")
				onlineStatus!!.setValue("Online")
			} else {
				onlineStatus!!.setValue("offline")
			}
		}

		override fun onCancelled(error: DatabaseError) {}
	})
}

private fun populateUserList() {

	userListValueEventListener = object : ValueEventListener {
		override fun onDataChange(dataSnapshot: DataSnapshot) {
			userListItems!!.clear()
			//first check datasnap shot exist
			//then add all users except current/self user
			if (dataSnapshot.exists()) {
				for (ds in dataSnapshot.children) {
					if (ds.exists() && ds.key != user!!.uid) {
						val name = ds.child("name").getValue(String::class.java)!!
						val onlineStatus = ds.child("onlineStatus").getValue(String::class.java)!!
						userListItems!!.add("$name status : $onlineStatus")
					}
				}
			}
			adapter = ArrayAdapter(this@OnlineUserActivity,
					android.R.layout.simple_list_item_1, android.R.id.text1, userListItems)
			userListView.adapter = adapter
			adapter!!.notifyDataSetChanged()
		}

		override fun onCancelled(databaseError: DatabaseError) {}
	}
	usersListRef!!.addValueEventListener(userListValueEventListener!!)
}

Below is User.kt file code

class User {
    var name: String? = null
    var onlineStatus: String? = null

    constructor() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    constructor(name: String?, onlineStatus: String?) {
        this.name = name
        this.onlineStatus = onlineStatus
    }
}

Whole Code

project level build.gradle file

buildscript {
    ext.kotlin_version = '1.4.0-rc'
    repositories {
        jcenter()
        google()
        maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.5.3'
        // Add this line
        classpath 'com.google.gms:google-services:4.3.3'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app level build.gradle file

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-android'
// Add this line
apply plugin: 'com.google.gms.google-services'
android {
    compileSdkVersion 28
    buildToolsVersion "29.0.3"
    defaultConfig {
        applicationId "com.programtown.example"
        minSdkVersion 17
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'androidx.appcompat:appcompat:1.0.2'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'

    implementation 'com.google.firebase:firebase-database:19.3.0'
    implementation 'com.google.firebase:firebase-auth:19.3.1'
    implementation 'com.firebaseui:firebase-ui-auth:6.2.0'
    implementation "androidx.core:core-ktx:1.3.1"
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
repositories {
    maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
    mavenCentral()
}

Style.xml

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

    <style name="FirebaseTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    </style>

</resources>

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
    tools:context=".MainActivity"
    android:orientation="vertical"
    >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Online Users"
        android:gravity="center"
        android:onClick="onlineUser"
        />

</RelativeLayout>

activity_online_user.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
    tools:context=".OnlineUserActivity">

    <ListView
        android:id="@+id/userListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
</LinearLayout>

MainActivity.kt

package com.programtown.example

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.firebase.ui.auth.AuthUI
import com.firebase.ui.auth.AuthUI.IdpConfig.EmailBuilder
import com.firebase.ui.auth.AuthUI.IdpConfig.GoogleBuilder
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import java.util.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    fun onlineUser(view: View?) {
        openOnlineUserList()
    }

    private fun openOnlineUserList() {
        if (checkSigInStatus()) {
            startActivity(Intent(this, OnlineUserActivity::class.java))
        } else {
            authenticate()
        }
    }

    var authUser: FirebaseUser? = null
    private fun checkSigInStatus(): Boolean {
        authUser = FirebaseAuth.getInstance().currentUser
        return authUser != null
    }

    fun authenticate() {
        // Choose authentication providers
        val providers = Arrays.asList(
                EmailBuilder().build(),
                GoogleBuilder().build()
        )
        // Create and launch sign-in intent
        startActivityForResult(
                AuthUI.getInstance()
                        .createSignInIntentBuilder()
                        .setAvailableProviders(providers)
                        .setTheme(R.style.FirebaseTheme)
                        .build(),
                RC_SIGN_IN)
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (requestCode == RC_SIGN_IN) {
            if (resultCode == Activity.RESULT_OK && FirebaseAuth.getInstance().currentUser != null) {
                openOnlineUserList()
            } else {
                Toast.makeText(this, "Sign in Failed. Please Sign in To Continue", Toast.LENGTH_SHORT).show()
            }
        }
    }

    companion object {
        private const val RC_SIGN_IN = 1
    }
}

OnlineUserActivity.kt

package com.programtown.example

import android.os.Bundle
import android.view.View
import android.widget.ArrayAdapter
import android.widget.ListView
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import com.google.firebase.database.*
import kotlinx.android.synthetic.main.activity_online_user.*
import java.util.*

class OnlineUserActivity : AppCompatActivity() {

    var user: FirebaseUser? = null
    var db: FirebaseDatabase? = null
    var usersListRef: DatabaseReference? = null
    var onlineStatus: DatabaseReference? = null
    var connectedRef: DatabaseReference? = null
    var userListValueEventListener: ValueEventListener? = null

    var userListItems: ArrayList<String>? = null
    var adapter: ArrayAdapter<String>? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_online_user)

        userListItems = ArrayList()
        db = FirebaseDatabase.getInstance()
        usersListRef = db!!.getReference("users")
        user = FirebaseAuth.getInstance().currentUser
        addToUserList(user)
        populateUserList()
    }

    private fun addToUserList(user: FirebaseUser?) {

        usersListRef!!.child(user!!.uid).setValue(User(user.displayName, "Online"))
        onlineStatus = db!!.getReference("users/" + user.uid + "/onlineStatus")
        connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected")
        connectedRef!!.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                val connected = snapshot.getValue(Boolean::class.java)!!
                if (connected) {
                    onlineStatus!!.onDisconnect().setValue("offline")
                    onlineStatus!!.setValue("Online")
                } else {
                    onlineStatus!!.setValue("offline")
                }
            }

            override fun onCancelled(error: DatabaseError) {}
        })
    }

    private fun populateUserList() {

        userListValueEventListener = object : ValueEventListener {
            override fun onDataChange(dataSnapshot: DataSnapshot) {
                userListItems!!.clear()
                //first check datasnap shot exist
                //then add all users except current/self user
                if (dataSnapshot.exists()) {
                    for (ds in dataSnapshot.children) {
                        if (ds.exists() && ds.key != user!!.uid) {
                            val name = ds.child("name").getValue(String::class.java)!!
                            val onlineStatus = ds.child("onlineStatus").getValue(String::class.java)!!
                            userListItems!!.add("$name status : $onlineStatus")
                        }
                    }
                }
                adapter = ArrayAdapter(this@OnlineUserActivity,
                        android.R.layout.simple_list_item_1, android.R.id.text1, userListItems)
                userListView.adapter = adapter
                adapter!!.notifyDataSetChanged()
            }

            override fun onCancelled(databaseError: DatabaseError) {}
        }
        usersListRef!!.addValueEventListener(userListValueEventListener!!)
    }

    override fun onDestroy() {
        super.onDestroy()
        usersListRef!!.removeEventListener(userListValueEventListener!!)
    }
}

User.kt

package com.programtown.example

class User {
    var name: String? = null
    var onlineStatus: String? = null

    constructor() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    constructor(name: String?, onlineStatus: String?) {
        this.name = name
        this.onlineStatus = onlineStatus
    }
}

3) Run and test your app

Conclusion

So in this post we have learned how to display/show list of online/offline users in android using firebase realtime database.

Please Subscribe Youtube| Like Facebook | Follow Twitter


Leave a Reply

Your email address will not be published. Required fields are marked *