Expert: Directory App MVVM Jetpack (Video Call with Webrtc & Firebase Realtime DB) in Android using Kotlin- Part-4
Published: 2022-06-10 10:59:02

Overview

In this article, I will create a Directory android application using Webrtc Video Calling App in which I will integrate HMS Core kits such as HMS Account, AuthService, Identity Kit, Firebase Auth and Firebase Realtime DB .

App will make use of android MVVM clean architecture using Jetpack components such as DataBinding, AndroidViewModel, Observer, LiveData and much more.

In this article we are going to implement DataBinding using Observable pattern.

FirebaseAuth Service Introduction

Firebase security applies Google’s internal expertise to easily build app sign-ins. Develop simple, multi-platform sign-in with Firebase Authentication. Build Fast For Any Device.

Firebase Authentication provides backend services, easy-to-use SDKs, and ready-made UI libraries to authenticate users to your app.

Firebase Realtime Database Service Introduction

Firebase Realtime Database lets you build rich, collaborative applications by allowing secure access to the database directly from client-side code.

WebRTC Service Introduction

WebRTC is a free and open-source project providing web browsers and mobile applications with real-time communication via application programming interfaces.

Prerequisite

Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
HMS Core APK 4.0.0.300 or later
Android Studio
AppGallery Account
App Gallery Integration process

1. Sign In and Create or Choose a project on AppGallery Connect portal.

2. Navigate to Project settings and download the configuration file.

3. Navigate to General Information, and then provide Data Storage location.

App Development

Add Required Dependencies:

Launch Android studio and create a new project. Once the project is ready.
Add following dependency for HMS Kits
```
//HMS Kits
implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
implementation 'com.huawei.hms:hwid:5.3.0.302'
implementation 'com.huawei.hms:identity:5.3.0.300'Copy codeCopy code
//Google Firebase    
implementation platform('com.google.firebase:firebase-bom:28.4.1')
    implementation 'com.google.firebase:firebase-analytics'
    implementation 'com.google.firebase:firebase-auth'
    implementation 'com.google.firebase:firebase-database'
    implementation 'com.google.android.gms:play-services-auth:19.2.0'
    implementation 'com.airbnb.android:lottie:4.1.0'
    implementation 'com.mikhaellopez:circularimageview:4.3.0'
    implementation 'com.kaopiz:kprogresshud:1.2.0'
    implementation 'com.google.android.gms:play-services-ads:20.4.0'
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Navigate to the Gradle scripts folder and open build.gradle (project: app)

repositories {
        google()
        jcenter()
        maven {url 'https://developer.huawei.com/repo/'}
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.0.1"
        classpath 'com.huawei.agconnect:agcp:1.4.2.300'
```
Code Implementation

Created following package model, event, viewmodel.
ViewModel: The ViewModel makes it easy to update data changes on the UI.Create a package named viewmodel in your main folder.Then create a new file and name it LoginViewModel.kt along with their FactoryViewModelProviders.

MainActivity.kt:
```
package com.hms.directory
class MainActivity : AppCompatActivity(), ActivityNavigation {
    private lateinit var viewModel: LoginViewModel
    private lateinit var dataBinding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        val viewModel: LoginViewModel by lazy {
            val activity = requireNotNull(this) {}
            ViewModelProviders.of(this, LoginViewModelFactory(activity.application))
                .get(LoginViewModel::class.java)
        }
        dataBinding.loginViewModel = viewModel
        dataBinding.lifecycleOwner = this
        viewModel.startActivityForResultEvent.setEventReceiver(this, this)
    }
    public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        viewModel.onResultFromActivity(requestCode, data)
        super.onActivityResult(requestCode, resultCode, data)
    }
}
```
LoginViewModel.kt:
```
package com.hms.directory.viewmodel
@SuppressLint("StaticFieldLeak")
class LoginViewModel(application: Application) : AndroidViewModel(application), Observable {
    private val context = getApplication<Application>().applicationContext
    private var mAuthManager: AccountAuthService? = null
    private var mAuthParam: AccountAuthParams? = null
    val startActivityForResultEvent = LiveMessageEvent<ActivityNavigation>()
    fun login() {
        val intent = Intent(context, OrderActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        context.startActivity(intent)
       /* mAuthParam = AccountAuthParamsHelper(AccountAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
            .setIdToken()
            .setAccessToken()
            .createParams()
        mAuthManager = AccountAuthManager.getService(Activity(), mAuthParam)
        startActivityForResultEvent.sendEvent {
            startActivityForResult(
                mAuthManager?.signInIntent,
                HMS_SIGN_IN
            )
        }*/
    }
    fun onResultFromActivity(requestCode: Int, data: Intent?) {
        when (requestCode) {
            HMS_SIGN_IN -> {
                val authAccountTask = AccountAuthManager.parseAuthResultFromIntent(data)
                onCompleteLogin(authAccountTask)
            }
        }
    }
    private fun onCompleteLogin(doneTask: Task<AuthAccount>) {
        if (doneTask.isSuccessful) {
            val authAccount = doneTask.result
            Log.d("LoginViewModel", "SigIn Success")
            context.startActivity(Intent(context, ContactListActivity::class.java))
        } else {
            Log.d("LoginViewModel", "SigIn Error")
        }
    }
    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }
    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }
}
```
ContactActivity.kt:
```
public class ContactListActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_contact_list);
        // Load contacts from file
        Contacts.loadData(this);
        // Set up recycler view and fill it with all the contacts
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.contact_list);
        recyclerView.setAdapter(new ContactListAdapter(this, Contacts.LIST));
    }
```
LoginFireBaseActivity.java
```
package com.hms.directory.app.call;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.auth.AuthCredential;
import com.google.firebase.auth.AuthResult;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import com.google.firebase.auth.GoogleAuthProvider;
import com.google.firebase.database.FirebaseDatabase;
import com.hms.corrierapp.R;
import com.hms.directory.app.call.models.User;
import org.jetbrains.annotations.NotNull;
public class LoginActivity extends AppCompatActivity {
    GoogleSignInClient mGoogleSignInClient;
    int RC_SIGN_IN = 11;
    FirebaseAuth mAuth;
    FirebaseDatabase database;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login_goole);
        mAuth = FirebaseAuth.getInstance();
        if (mAuth.getCurrentUser() != null) {
            goToNextActivity();
        }
        database = FirebaseDatabase.getInstance();
        GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
                .requestIdToken("1016048264402-439a9aamtpiajbgqeqg24qkum2bb7fmh.apps.googleusercontent.com")
                .requestEmail()
                .build();
        mGoogleSignInClient = GoogleSignIn.getClient(this, gso);
        findViewById(R.id.loginBtn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = mGoogleSignInClient.getSignInIntent();
                startActivityForResult(intent, RC_SIGN_IN);
                //startActivity(new Intent(LoginActivity.this, MainActivity.class));
            }
        });
    }
    void goToNextActivity() {
        startActivity(new Intent(LoginActivity.this, MainActivity.class));
        finish();
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable @org.jetbrains.annotations.Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == RC_SIGN_IN) {
            Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
            GoogleSignInAccount account = task.getResult();
            authWithGoogle(account.getIdToken());
        }
    }
    void authWithGoogle(String idToken) {
        AuthCredential credential = GoogleAuthProvider.getCredential(idToken, null);
        mAuth.signInWithCredential(credential)
                .addOnCompleteListener(new OnCompleteListener<AuthResult>() {
                    @Override
                    public void onComplete(@NonNull @NotNull Task<AuthResult> task) {
                        if (task.isSuccessful()) {
                            FirebaseUser user = mAuth.getCurrentUser();
                            User firebaseUser = new User(user.getUid(), user.getDisplayName(), user.getPhotoUrl().toString(), "Unknown", 500);
                            database.getReference()
                                    .child("profiles")
                                    .child(user.getUid())
                                    .setValue(firebaseUser).addOnCompleteListener(new OnCompleteListener<Void>() {
                                @Override
                                public void onComplete(@NonNull @NotNull Task<Void> task) {
                                    if (task.isSuccessful()) {
                                        startActivity(new Intent(LoginActivity.this, MainActivity.class));
                                        finishAffinity();
                                    } else {
                                        Toast.makeText(LoginActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
                                    }
                                }
                            });
                            //Log.e("profile", user.getPhotoUrl().toString());
                        } else {
                            Log.e("err", task.getException().getLocalizedMessage());
                        }
                    }
                });
    }
}
```
CallConnectingActivity.java
```
public class ConnectingActivity extends AppCompatActivity {
    ActivityConnectingBinding binding;
    FirebaseAuth auth;
    FirebaseDatabase database;
    boolean isOkay = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityConnectingBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        auth = FirebaseAuth.getInstance();
        database = FirebaseDatabase.getInstance();
        String profile = getIntent().getStringExtra("profile");
        Glide.with(this)
                .load(profile)
                .into(binding.profile);
        String username = auth.getUid();
        database.getReference().child("users")
                .orderByChild("status")
                .equalTo(0).limitToFirst(1)
                .addListenerForSingleValueEvent(new ValueEventListener() {
                    @Override
                    public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
                        if (snapshot.getChildrenCount() > 0) {
                            isOkay = true;
                            // Room Available
                            for (DataSnapshot childSnap : snapshot.getChildren()) {
                                database.getReference()
                                        .child("users")
                                        .child(childSnap.getKey())
                                        .child("incoming")
                                        .setValue(username);
                                database.getReference()
                                        .child("users")
                                        .child(childSnap.getKey())
                                        .child("status")
                                        .setValue(1);
                                Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
                                String incoming = childSnap.child("incoming").getValue(String.class);
                                String createdBy = childSnap.child("createdBy").getValue(String.class);
                                boolean isAvailable = childSnap.child("isAvailable").getValue(Boolean.class);
                                intent.putExtra("username", username);
                                intent.putExtra("incoming", incoming);
                                intent.putExtra("createdBy", createdBy);
                                intent.putExtra("isAvailable", isAvailable);
                                startActivity(intent);
                                finish();
                            }
                        } else {
                            // Not Available
                            HashMap<String, Object> room = new HashMap<>();
                            room.put("incoming", username);
                            room.put("createdBy", username);
                            room.put("isAvailable", true);
                            room.put("status", 0);
                            database.getReference()
                                    .child("users")
                                    .child(username)
                                    .setValue(room).addOnSuccessListener(new OnSuccessListener<Void>() {
                                @Override
                                public void onSuccess(Void unused) {
                                    database.getReference()
                                            .child("users")
                                            .child(username).addValueEventListener(new ValueEventListener() {
                                        @Override
                                        public void onDataChange(@NonNull @NotNull DataSnapshot snapshot) {
                                            if (snapshot.child("status").exists()) {
                                                if (snapshot.child("status").getValue(Integer.class) == 1) {
                                                    if (isOkay)
                                                        return;
                                                    isOkay = true;
                                                    Intent intent = new Intent(ConnectingActivity.this, CallActivity.class);
                                                    String incoming = snapshot.child("incoming").getValue(String.class);
                                                    String createdBy = snapshot.child("createdBy").getValue(String.class);
                                                    boolean isAvailable = snapshot.child("isAvailable").getValue(Boolean.class);
                                                    intent.putExtra("username", username);
                                                    intent.putExtra("incoming", incoming);
                                                    intent.putExtra("createdBy", createdBy);
                                                    intent.putExtra("isAvailable", isAvailable);
                                                    startActivity(intent);
                                                    finish();
                                                }
                                            }
                                        }
                                        @Override
                                        public void onCancelled(@NonNull @NotNull DatabaseError error) {
                                        }
                                    });
                                }
                            });
                        }
                    }
                    @Override
                    public void onCancelled(@NonNull @NotNull DatabaseError error) {
                    }
                });
    }
}
```
Xml layout DataBinding
To include data binding in the UI, enclose all content with <layout></layout>.

The ViewModel is introduced to the layout in the <data></data> section, as shown. Ensure that the type value points to the specific folder that has the required ViewModel.

App Build Result
​​​​​​​​
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/aj4xckjcs21wxzz59a4g.gif)

Tips and Tricks

Identity Kit displays the HUAWEI ID registration or sign-in page first. The user can use the functions provided by Identity Kit only after signing in using a registered HUAWEI ID.

Conclusion

In this article, we have learned how to integrate Huawei Identity Kit and Firebase Realtime DB using Webrtc Video Call in Android application. After completely read this article user can easily implement Huawei ID in the Directory App android application using Kotlin.

Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.

References

HMS Docs:

https://developer.huawei.com/consumer/en/doc/development/HMSCore-Guides/introduction-0000001050048870