Author

Surya

Browsing

I this post, will learn about firebase cloud messaging in android. I will show you a full demonstration with sample app. Let see how we setup firebase cloud messaging in our app

Overview
  • Firebase Cloud Messaging is a cross-platform messaging solution that lets you reliably deliver messages free of cost.
  • It just allows you to notify a client that a new email, data or other data is available to sync.
  • You can send notification messages that are displayed to the user.
  • Notification messaging is act as instant messaging. A message can transfer a payload of up to 4KB to the client.
  • You can send a message to a single device, group of the device, or to devices subscribed to topic.
Implementation step of firebase cloud messaging in your app
  • Create an android project
  • Create a firebase project on firebase console
  • Connect your app to firebase
  • Setup your android app to use the cloud messaging libraries
  • Receive the code when the app is background and foreground.
Setup your android app to use the cloud messaging libraries

Navigate your project, In project-level build.gradle file make sure to include Google maven.

build.gradle (project-level)
    // add these line
    classpath 'com.google.gms:google-services:4.2.0'
app/build.gradle
  //  Add these line
  implementation 'com.google.firebase:firebase-messaging:20.1.0'
  implementation 'android.arch.work:work-runtime:1.0.1'
Add this line on top of app/build.gradle
apply plugin: 'com.google.gms.google-services'
Add internet uses permission
  <uses-permission android:name="android.permission.INTERNET" />
Set Notification custom icon as default in manifest
    <! – Add these line -->
    <! – [START] -->
    <! – Set custom default icon -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_icon"
        android:resource="@drawable/ic_notification" />
    <! – Set color used with incoming notification messages. This is used when no color is set for the incoming notification message.-->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_color"
        android:resource="@color/colorAccent" />
    <! –  Set fcm default channel-->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_channel_id"
        android:value="@string/default_notification_channel_id" />
    <! – [END] -->

Let’s create a service class named is CloudMessagingService

In the src folder create a service class named CloudMessagingService which extends FirebaseMessagingService. We have to override two methods which are onNewToken() and onMessageReceived().

Let’s override onNewToken methods.

In this methods make own server request here using your HTTP client

  @Override
  public void onNewToken(String token) {
    super.onNewToken(token);
    Log.d(TAG, "Refreshed token: " + token);
    // make a own server request here using your http client
  }
Now override onMessageReceived method

Let’s override onMessageReceived method, in this method we received RemoteMessgage instance that contains all message details.

Message Type

Firebase cloud messing allow you to send two types of messages.

  • Notification Messages
    • It displays the notification. It’s managed by firebase SDK, FCM automatically to the end-user device on behalf of the client app.
    • Notification messages contain a predefined set of user-visible keys.
  • Data Messages
    • Data messages contain only your user defines custom key-value pair.
    • Handled by the app itself. It contains key-value pairs, based on this you can do something(such as make some event, store some data, sync some data ).

The maximum payload of both messages type is 4 KB, except when sending messaging from firebase console, which enforces a 1024 character limit.

Check Notification Messages

Check remote message contains a notification payload or not.

 // if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
      Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }
Handle DATA messages

Check remote message contains a data payload. If data payload contains more than 10 seconds job then we should run log run job using WorkManager. If the task is within 10 min then we should handle it.

  //  if remote message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
      Log.d(TAG, "Message data payload: " + remoteMessage.getData());
      Map<String, String> data = remoteMessage.getData();

      String jobType = data.get("type");
     /* Check the message contains data If needs to be processed by long running job
         so check if data needs to be processed by long running job */

      if (jobType.equalsIgnoreCase(JobType.LONG.name())) {
        // For long-running tasks (10 seconds or more) use WorkManager.
        scheduleLongRunningJob();
      } else {
        // Handle message within 10 seconds
        handleNow(data);
      }
    }
Create and show notification containing the received FCM message.
private void sendNotification(String title, String messageBody) {
    Intent intent = new Intent(this, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
        PendingIntent.FLAG_ONE_SHOT);

    String channelId = getString(R.string.default_notification_channel_id);
    Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    NotificationCompat.Builder notificationBuilder =
        new NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(messageBody)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent);

    NotificationManager notificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    // Since android Oreo notification channel is needed.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      // Channel human readable title
      NotificationChannel channel = new NotificationChannel(channelId,
          "Cloud Messaging Service",
          NotificationManager.IMPORTANCE_DEFAULT);
      notificationManager.createNotificationChannel(channel);
    }

    notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
  }
Finally, your CloudMessagingService class looks like this.
package com.firebasecloudmessaging;

import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkManager;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import java.util.Map;

public class CloudMessagingService extends FirebaseMessagingService {
  private static final String TAG = "CloudMessagingService";

  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {
    super.onMessageReceived(remoteMessage);

    // log the getting message from firebase
    Log.d(TAG, "From: " + remoteMessage.getFrom());

    //  if remote message contains a data payload.
    if (remoteMessage.getData().size() > 0) {
      Log.d(TAG, "Message data payload: " + remoteMessage.getData());
      Map<String, String> data = remoteMessage.getData();

      String jobType = data.get("type");
     /* Check the message contains data If needs to be processed by long running job
         so check if data needs to be processed by long running job */

      if (jobType.equalsIgnoreCase(JobType.LONG.name())) {
        // For long-running tasks (10 seconds or more) use WorkManager.
        scheduleLongRunningJob();
      } else {
        // Handle message within 10 seconds
        handleNow(data);
      }
    }

    // if message contains a notification payload.
    if (remoteMessage.getNotification() != null) {
      Log.d(TAG, "Message Notification Body: " + remoteMessage.getNotification().getBody());
    }
  }

  @Override
  public void onNewToken(String token) {
    super.onNewToken(token);
    Log.d(TAG, "Refreshed token: " + token);
    sendRegistrationToServer(token);
  }

  /**
   * Persist token on third-party servers using your Retrofit APIs client.
   * Modify this method to associate the user's FCM InstanceID token with any server-side account
   *
   * @param token The new token.
   */
  private void sendRegistrationToServer(String token) {
    // make a own server request here using your http client

  }

  private void handleNow(Map<String, String> data) {
    if (data.containsKey("title") && data.containsKey("message")) {
      sendNotification(data.get("title"), data.get("message"));
    }
  }

  /**
   * Schedule async work using WorkManager mostly these are one type job.
   */
  private void scheduleLongRunningJob() {
    OneTimeWorkRequest work = new OneTimeWorkRequest.Builder(DataSyncWorker.class)
        .build();
    WorkManager.getInstance().beginWith(work).enqueue();
  }

  /**
   * Create and show notification containing the received FCM message.
   *
   * @param messageBody FCM message body received.
   */
  private void sendNotification(String title, String messageBody) {
    Intent intent = new Intent(this, MainActivity.class);
    intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
    PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent,
        PendingIntent.FLAG_ONE_SHOT);

    String channelId = getString(R.string.default_notification_channel_id);
    Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
    NotificationCompat.Builder notificationBuilder =
        new NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ic_notification)
            .setContentTitle(title)
            .setContentText(messageBody)
            .setAutoCancel(true)
            .setSound(defaultSoundUri)
            .setContentIntent(pendingIntent);

    NotificationManager notificationManager =
        (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);

    // Since android Oreo notification channel is needed.
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
      // Channel human readable title
      NotificationChannel channel = new NotificationChannel(channelId,
          "Cloud Messaging Service",
          NotificationManager.IMPORTANCE_DEFAULT);
      notificationManager.createNotificationChannel(channel);
    }

    notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
  }
}
Modify main activity layout file

This file I’m adding a button for logging firebase token.

<?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"
    tools:context=".MainActivity"
    >


  <Button
      android:id="@+id/logTokenButton"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Log Token"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
</androidx.constraintlayout.widget.ConstraintLayout>
Meanwhile, open the MainActivity, and manage followings things
  • Handle possible data accompanying notification message
  • Log and show the Get new Instance ID token
package com.firebasecloudmessaging;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.iid.FirebaseInstanceId;
import com.google.firebase.iid.InstanceIdResult;

public class MainActivity extends AppCompatActivity {
  private static final String TAG = "MainActivity";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    // If a notification message is tapped, any data accompanying the notification
    // message is available in the intent extras.
    // Handle possible data accompanying notification message.
    if (getIntent().getExtras() != null) {
      for (String key : getIntent().getExtras().keySet()) {
        Object value = getIntent().getExtras().get(key);
        Log.d(TAG, "Key: " + key + " Value: " + value);
        // navigate the app based on param
      }
    }

    // Log and show the  Get new Instance ID token
    Button loginButton = findViewById(R.id.logTokenButton);
    loginButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        FirebaseInstanceId.getInstance().getInstanceId().addOnCompleteListener(
            new OnCompleteListener<InstanceIdResult>() {
              @Override public void onComplete(@NonNull Task<InstanceIdResult> task) {
                if (!task.isSuccessful()) {
                  Log.e(TAG, "getInstanceId failed", task.getException());
                  return;
                }

                // Get new Instance ID token
                String token = task.getResult().getToken();

                Log.d(TAG, token);
                Toast.makeText(MainActivity.this, token, Toast.LENGTH_SHORT).show();
              }
            });
      }
    });
  }
}
Conclusion

All done, now run the app and see your app is run and up. you able to see the token on toast and logcat as well. Now send the messaging from android another device. Let’s see your notification is visible on the screen.

In this post, we learned how to implement Firebase Cloud Messaging Android our application. I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Happy coding 🙂

In this blog, I will show how to auto read OTP in android with the help of SMS User Consent API. In our previous blog, we have learned Automatic SMS Verification Android using SMS Retriever API. But it has some difficulty for example

  • SMS should start with <#> tag,
  • SMS should end with application hash code, etc.

In this article, I’m going to show you the implementation auto-read OTP using SMS User Consent API. It is complements of SMS Retriever API. Using SMS User Consent API we can achieve auto read OTP by allowing an app to prompt the user to grant access to the content of a single SMS message. When the user gives consent, the app can access the entire message to automatically complete SMS verification.

auto read otp android user consent 1
auto read otp android user consent 2
auto read otp android user consent 3

Image source google developers website.

  • Start – The first thing you have to start is listening before sending the message or OTP to the server.
  • Prompt – When the user’s device receives the SMS message containing a one-time code, Google Play services display the contents of the message to the user and asks for consent to make that text available to your app.
  • Read Message – If the user consents, the entire SMS message is made available to your app.
Message Criteria

Google play services imposed the followings SMS criteria

  • It contains a one-time password– The should message contains a 4–10 character alphanumeric string with at least one number.
  • Contacts – The message was sent by a phone number that’s not in the user’s contacts.
  • Timing – The API will look for the One Time Code for a maximum time of 5 minutes.
1. Let’s create a new project in Android Studio

Open to an android studio and create a new project. That project we will implement auto read OTP features. So I’m adding few strings in strings.xml file that we will use this project.

<resources>
  <string name="app_name">SMS verification</string>
  <string name="send_otp">Send OTP</string>
  <string name="generate_otp">Generate OTP</string>
  <string name="received_message">Received Message</string>
  <string name="verify_otp">Verify OTP</string>
</resources>

We should add below dependencies in app-level build.gradle in your project.

    // add these lines in your app build.gradle
    implementation 'com.google.android.gms:play-services-auth:17.0.0'
    implementation 'com.google.android.gms:play-services-auth-api-phone:17.1.0'
3. Listen to the incoming messages

The next step is to listen to incoming messaging. we can start listening for incoming messages using below method. We can add sender phone number, SMS User Consent API will only trigger on messages from this number. I’m adding null here

  private void startSmsUserConsent() {
    SmsRetrieverClient client = SmsRetriever.getClient(this);
    //We can add sender phone number or leave it blank
    // I'm adding null here
    client.startSmsUserConsent(null).addOnSuccessListener(new OnSuccessListener<Void>() {
      @Override
      public void onSuccess(Void aVoid) {
        Toast.makeText(getApplicationContext(), "On Success", Toast.LENGTH_LONG).show();
      }
    }).addOnFailureListener(new OnFailureListener() {
      @Override
      public void onFailure(@NonNull Exception e) {
        Toast.makeText(getApplicationContext(), "On OnFailure", Toast.LENGTH_LONG).show();
      }
    });
  }
4. Create a BroadcastReceiver named is SmsBroadcastReceiver

In SmsBroadcastReceiverListener we will catch SMS and set back using SmsBroadcastReceiverListener callback methods.

package com.smsverification;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.common.api.CommonStatusCodes;
import com.google.android.gms.common.api.Status;

public class SmsBroadcastReceiver extends BroadcastReceiver{

  SmsBroadcastReceiverListener smsBroadcastReceiverListener;

  @Override
  public void onReceive(Context context, Intent intent) {
    if (intent.getAction() == SmsRetriever.SMS_RETRIEVED_ACTION) {
      Bundle extras = intent.getExtras();
      Status smsRetrieverStatus = (Status) extras.get(SmsRetriever.EXTRA_STATUS);
      switch (smsRetrieverStatus.getStatusCode()) {
        case CommonStatusCodes.SUCCESS:
          Intent messageIntent = extras.getParcelable(SmsRetriever.EXTRA_CONSENT_INTENT);
          smsBroadcastReceiverListener.onSuccess(messageIntent);
          break;
        case CommonStatusCodes.TIMEOUT:
          smsBroadcastReceiverListener.onFailure();
          break;
      }
    }
  }

  public interface SmsBroadcastReceiverListener {
    void onSuccess(Intent intent);

    void onFailure();
  }
}

When the broadcast receiver catch any message that contains OTP, Google paly services display is contents( BottomSheet ) for asking permission. If the user allows then full messing is available to your app. So let’s register broadcast receiver.

private void registerBroadcastReceiver() {
    smsBroadcastReceiver = new SmsBroadcastReceiver();
    smsBroadcastReceiver.smsBroadcastReceiverListener =
        new SmsBroadcastReceiver.SmsBroadcastReceiverListener() {
          @Override
          public void onSuccess(Intent intent) {
            startActivityForResult(intent, REQ_USER_CONSENT);
          }

          @Override
          public void onFailure() {

          }
        };
    IntentFilter intentFilter = new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION);
    registerReceiver(smsBroadcastReceiver, intentFilter);
  }

  @Override
  protected void onStart() {
    super.onStart();
    registerBroadcastReceiver();
  }

  @Override
  protected void onStop() {
    super.onStop();
    unregisterReceiver(smsBroadcastReceiver);
  }
6. Receive message in onActivityResult()

If the user consents, the entire SMS message is made available to your app. we will catch messaging in

 @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQ_USER_CONSENT) {
      if ((resultCode == RESULT_OK) && (data != null)) {
        //That gives all message to us.
        // We need to get the code from inside with regex
        String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
        textViewMessage.setText(
            String.format("%s - %s", getString(R.string.received_message), message));

        getOtpFromMessage(message);
      }
    }
  }
7. Extract OTP from messaging.

Suppose our messaging is “Your OTP is 123456. Please do not share OTP with others “. Now you have to extract OTP from methods using below method.

  private void getOtpFromMessage(String message) {
    // This will match any 6 digit number in the message
    Pattern pattern = Pattern.compile("(|^)\\d{6}");
    Matcher matcher = pattern.matcher(message);
    if (matcher.find()) {
      otpText.setText(matcher.group(0));
    }
  }
8. I’m modifying main activity layout for demonstration

For creating a fully functional sample app, I’m adding TextView for showing messaging and edit text for showing OTP.

<?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"
    tools:context=".MainActivity"
    >

  <Button
      android:id="@+id/button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/verify_otp"
      android:textAllCaps="false"
      android:textSize="18sp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <TextView
      android:id="@+id/textViewMessage"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="16dp"
      android:layout_marginTop="24dp"
      android:layout_marginEnd="16dp"
      android:gravity="center"
      android:textSize="16sp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/button"
      tools:text="@string/received_message"
      />
  <ImageView
      android:id="@+id/imageView"
      android:layout_width="150dp"
      android:layout_height="150dp"
      android:layout_marginTop="32dp"
      app:layout_constraintBottom_toTopOf="@+id/editTextOTP"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:srcCompat="@drawable/ic_otp"
      />
  <EditText
      android:id="@+id/editTextOTP"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginStart="16dp"
      android:layout_marginEnd="16dp"
      android:layout_marginBottom="16dp"
      android:ems="10"
      android:gravity="center"
      android:hint="OTP"
      android:inputType="number"
      app:layout_constraintBottom_toTopOf="@+id/button"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
9. We almost done, put all java code together in activity files.
package com.smsverification;

import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.auth.api.phone.SmsRetriever;
import com.google.android.gms.auth.api.phone.SmsRetrieverClient;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class MainActivity extends AppCompatActivity {
  private static final int REQ_USER_CONSENT = 200;
  SmsBroadcastReceiver smsBroadcastReceiver;
  Button verifyOTP;
  TextView textViewMessage;
  EditText otpText;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    // find view by ids
    verifyOTP = findViewById(R.id.button);
    textViewMessage = findViewById(R.id.textViewMessage);
    otpText = findViewById(R.id.editTextOTP);
    startSmsUserConsent();
  }

  private void startSmsUserConsent() {
    SmsRetrieverClient client = SmsRetriever.getClient(this);
    //We can add sender phone number or leave it blank
    // I'm adding null here
    client.startSmsUserConsent(null).addOnSuccessListener(new OnSuccessListener<Void>() {
      @Override
      public void onSuccess(Void aVoid) {
        Toast.makeText(getApplicationContext(), "On Success", Toast.LENGTH_LONG).show();
      }
    }).addOnFailureListener(new OnFailureListener() {
      @Override
      public void onFailure(@NonNull Exception e) {
        Toast.makeText(getApplicationContext(), "On OnFailure", Toast.LENGTH_LONG).show();
      }
    });
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == REQ_USER_CONSENT) {
      if ((resultCode == RESULT_OK) && (data != null)) {
        //That gives all message to us.
        // We need to get the code from inside with regex
        String message = data.getStringExtra(SmsRetriever.EXTRA_SMS_MESSAGE);
        Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show();
        textViewMessage.setText(
            String.format("%s - %s", getString(R.string.received_message), message));

        getOtpFromMessage(message);
      }
    }
  }

  private void getOtpFromMessage(String message) {
    // This will match any 6 digit number in the message
    Pattern pattern = Pattern.compile("(|^)\\d{6}");
    Matcher matcher = pattern.matcher(message);
    if (matcher.find()) {
      otpText.setText(matcher.group(0));
    }
  }

  private void registerBroadcastReceiver() {
    smsBroadcastReceiver = new SmsBroadcastReceiver();
    smsBroadcastReceiver.smsBroadcastReceiverListener =
        new SmsBroadcastReceiver.SmsBroadcastReceiverListener() {
          @Override
          public void onSuccess(Intent intent) {
            startActivityForResult(intent, REQ_USER_CONSENT);
          }

          @Override
          public void onFailure() {

          }
        };
    IntentFilter intentFilter = new IntentFilter(SmsRetriever.SMS_RETRIEVED_ACTION);
    registerReceiver(smsBroadcastReceiver, intentFilter);
  }

  @Override
  protected void onStart() {
    super.onStart();
    registerBroadcastReceiver();
  }

  @Override
  protected void onStop() {
    super.onStop();
    unregisterReceiver(smsBroadcastReceiver);
  }
}

Congrats, Let’s use the SMS content API is your application

Conclusion

In this post, we learned how to auto-read OTP android using SMS content API in our application. So let’s recap the step of implementation, basically, three major steps Start Listening messaging, show prompt and Read messaging.

 I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Get Solution Code

Keep Learning 🙂

In this blog, I’m going to show you adding Google Sign In using firebase in your android app. It very easy to do just follow below step. So let’s get started.

Step of Implementation Google Authentication
  • Create an android project
  • Setup and add project on firebase console.
  • Enable Google Sign In
  • Add SHA and 256 keys to the app on firebase console. (Required step for Google Sign In )
  • Add authentication library in app-level build.gradle
  • Setup Google client
1. Create an android project

Let’s move to android studio and create a new project. In this sample application, I’m taking a package name is com.googlesigninfirebase for demonstration of Google Sign in using firebase. let’s go to next step.

2. Setup new firebase project on firebase console.

Let’s open the firebase console and add a project. For adding a project on firebase follow my previous post. Adding Firebase to Android App Manually

3. Enable Google Sign In

Select Authentication from the left panel. Go to the Sign In method and you can select Google and enable the Google SignIn. In sort ( Authentication -> Sign In Method -> Google -> Enable ).

4. Add SHA and 256 keys to the app on firebase console

This is the required step for google sign In. Now go to the android studio project. To get the signature of the app clicks the gradle on right-hand side. Now select app -> task -> android and select singingReport.

You just need to copy and paste in project setting in firebase. You have to set both keys SHA1 and SHA-256. Now come back to android studio follow next step.

Add authentication library in app-level build.gradle
 implementation 'com.firebaseui:firebase-ui-auth:4.3.1'
 // Google Sign In SDK (only required for Google Sign In)
 implementation 'com.google.android.gms:play-services-auth:17.0.0'
Create a new activity along with layout named is LoginActivity

Open layout file and button for demonstration

<?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"
    tools:context=".LoginActivity"
    >

  <com.google.android.gms.common.SignInButton
      android:id="@+id/sign_in_button"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginStart="8dp"
      android:layout_marginTop="8dp"
      android:layout_marginEnd="8dp"
      android:layout_marginBottom="8dp"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
Open LoginActvitiy and paste following code
package com.googlesigninfirebase;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
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.common.SignInButton;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
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;

public class LoginActivity extends AppCompatActivity {
  private static final String TAG = "LoginActivity";
  private static final int RC_SIGN_IN = 1001;

  GoogleSignInClient googleSignInClient;

  private FirebaseAuth firebaseAuth;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);

    SignInButton signInButton = findViewById(R.id.sign_in_button);
    signInButton.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        // Launch Sign In
        signInToGoogle();
      }
    });

    // Configure Google Client
    configureGoogleClient();
  }

  private void configureGoogleClient() {
    // Configure Google Sign In
    GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
        // for the requestIdToken, this is in the values.xml file that
        // is generated from your google-services.json 
        .requestIdToken(getString(R.string.default_web_client_id))
        .requestEmail()
        .build();

    // Build a GoogleSignInClient with the options specified by gso.
    googleSignInClient = GoogleSignIn.getClient(this, gso);

    // Set the dimensions of the sign-in button.
    SignInButton signInButton = findViewById(R.id.sign_in_button);
    signInButton.setSize(SignInButton.SIZE_WIDE);

    // Initialize Firebase Auth
    firebaseAuth = FirebaseAuth.getInstance();
  }

  @Override
  public void onStart() {
    super.onStart();

    // Check if user is signed in (non-null) and update UI accordingly.
    FirebaseUser currentUser = firebaseAuth.getCurrentUser();

    if (currentUser != null) {
      Log.d(TAG, "Currently Signed in: " + currentUser.getEmail());
      showToastMessage("Currently Logged in: " + currentUser.getEmail());
    }
  }

  public void signInToGoogle() {
    Intent signInIntent = googleSignInClient.getSignInIntent();
    startActivityForResult(signInIntent, RC_SIGN_IN);
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    // Result returned from launching the Intent from GoogleSignInApi.getSignInIntent(...);
    if (requestCode == RC_SIGN_IN) {
      Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
      try {
        // Google Sign In was successful, authenticate with Firebase
        GoogleSignInAccount account = task.getResult(ApiException.class);
        showToastMessage("Google Sign in Succeeded");
        firebaseAuthWithGoogle(account);
      } catch (ApiException e) {
        // Google Sign In failed, update UI appropriately
        Log.w(TAG, "Google sign in failed", e);
        showToastMessage("Google Sign in Failed " + e);
      }
    }
  }

  private void firebaseAuthWithGoogle(GoogleSignInAccount account) {
    Log.d(TAG, "firebaseAuthWithGoogle:" + account.getId());

    AuthCredential credential = GoogleAuthProvider.getCredential(account.getIdToken(), null);
    firebaseAuth.signInWithCredential(credential)
        .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
          @Override
          public void onComplete(@NonNull Task<AuthResult> task) {
            if (task.isSuccessful()) {
              // Sign in success, update UI with the signed-in user's information
              FirebaseUser user = firebaseAuth.getCurrentUser();

              Log.d(TAG, "signInWithCredential:success: currentUser: " + user.getEmail());

              showToastMessage("Firebase Authentication Succeeded ");
              launchMainActivity(user);
            } else {
              // If sign in fails, display a message to the user.

              Log.w(TAG, "signInWithCredential:failure", task.getException());

              showToastMessage("Firebase Authentication failed:" + task.getException());
            }
          }
        });
  }

  private void showToastMessage(String message) {
    Toast.makeText(LoginActivity.this, message, Toast.LENGTH_LONG).show();
  }

  private void launchMainActivity(FirebaseUser user) {
    if (user != null) {
      MainActivity.startActivity(this, user.getDisplayName());
      finish();
    }
  }
}
Open MainActvity layout add logout and revoke access button
<?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"
    tools:context=".MainActivity"
    >


  <Button
      android:id="@+id/buttonLogout"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginBottom="16dp"
      android:text="@string/logout"
      android:textAllCaps="false"
      app:layout_constraintBottom_toTopOf="@+id/buttonDisconnect"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />
  <Button
      android:id="@+id/buttonDisconnect"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@string/revoke_access"
      android:textAllCaps="false"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <TextView
      android:id="@+id/textViewWelcome"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginBottom="32dp"
      android:text="@string/welcome"
      android:gravity="center"
      app:layout_constraintBottom_toTopOf="@+id/buttonLogout"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />
</androidx.constraintlayout.widget.ConstraintLayout>

Go to main activity and add logout code

package com.googlesigninfirebase;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.google.android.gms.auth.api.signin.GoogleSignIn;
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.auth.FirebaseAuth;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  private static final String TAG = "MainActivity";
  private static final String ARG_NAME = "username";

  public static void startActivity(Context context, String username) {
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra(ARG_NAME, username);
    context.startActivity(intent);
  }

  FirebaseAuth firebaseAuth;
  GoogleSignInClient googleSignInClient;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    TextView textView = findViewById(R.id.textViewWelcome);
    if (getIntent().hasExtra(ARG_NAME)) {
      textView.setText(String.format("Welcome - %s", getIntent().getStringExtra(ARG_NAME)));
    }
    findViewById(R.id.buttonLogout).setOnClickListener(this);
    findViewById(R.id.buttonDisconnect).setOnClickListener(this);

    googleSignInClient = GoogleSignIn.getClient(this, GoogleSignInOptions.DEFAULT_SIGN_IN);
    firebaseAuth = FirebaseAuth.getInstance();
  }

  @Override
  public void onClick(View view) {
    switch (view.getId()) {
      case R.id.buttonLogout:
        signOut();
        break;
      case R.id.buttonDisconnect:
        revokeAccess();
        break;
    }
  }

  private void signOut() {
    // Firebase sign out
    firebaseAuth.signOut();

    // Google sign out
    googleSignInClient.signOut().addOnCompleteListener(this,
        new OnCompleteListener<Void>() {
          @Override
          public void onComplete(@NonNull Task<Void> task) {
            // Google Sign In failed, update UI appropriately
            Log.w(TAG, "Signed out of google");
          }
        });
  }

  private void revokeAccess() {
    // Firebase sign out
    firebaseAuth.signOut();

    // Google revoke access
    googleSignInClient.revokeAccess().addOnCompleteListener(this,
        new OnCompleteListener<Void>() {
          @Override
          public void onComplete(@NonNull Task<Void> task) {
            // Google Sign In failed, update UI appropriately
            Log.w(TAG, "Revoked Access");
          }
        });
  }
}

Conclusion

With the help of this android app tutorial, We have learned Google Sign In using Firebase android. I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Welcome guys, In this post I’ll show you sign in with email with FirebaseUI. Firebase provides many ways for authentication. Here, I’ll explain FirebaseUI to login using email and password. so Let’s get started.

Why should use FirebaseUI for Authentication?

FirebaseUI is an open-source library, you can also customize it because of open source. It eliminates boilerplate code and promotes android best practices with user experiences and security. FirebaseUI is all enabled single tap to enable and returning users also.

Connect your app to firebase

Let’s open an android studio and create a new android project with the empty activity template. I’m taking the package name here com.firebaseauthenticationuiexample . Once the project created, connect your app to the firebase. Please follow this article for creating a project on firebase console.

Add a new classpath in project-level build.gradle

Open the project-level build.gradle and add the below line.

//Add this line
classpath 'com.google.gms:google-services:4.3.2'
Add new plugin in app-level build.gradle

Open the app-level build.gradle and google services plugin by adding this line

// add this line
apply plugin: 'com.google.gms.google-services'
Add firebase UI auth dependency in same app level gradle file

Firebase UI auth library needs to perform a firebase auth with email.

implementation 'com.firebaseui:firebase-ui-auth:4.3.1'

After adding sync the project and make sure everything is working fine.

Enable email and password authentication on firebase console.

Go to the firebase console and enable email password authentication. So go to the Authentication option in the left panel. Once you open on authentication go to Sign In method and enable email password.

firebase login with email and password android
Create a LoginActvitiy

I’m creating a new activity with a layout file. Here I’m adding a button for initiating login just for demonstration purposes. Let’s go to the layout file and add SignIn button just like below

<?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"
    tools:context=".LoginActivity"
    >
  <Button
      android:id="@+id/buttonSignIn"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="SignIn with Email"
      android:textAllCaps="false"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
Let’s go to LoginActivity.

In the LoginActivity, before initiating sign-in make sure your user is already login for not in previous session. you can check something like this.

    FirebaseAuth auth = FirebaseAuth.getInstance();
    if (auth.getCurrentUser() != null) {
      // already signed in
    } else {
      // not signed in 
    }

Create and launch sign-in intent

If the user is not signed-in then by the initiated sign-in process by creating auth intent. This you can specify service URL and include the link inside, or customized theme as well. You need to create and launch sign-in intent just like this.

    // Choose authentication providers
    List<AuthUI.IdpConfig> providers = Arrays.asList(
        new AuthUI.IdpConfig.EmailBuilder().build());

    // Create and launch sign-in intent
    startActivityForResult(
        AuthUI.getInstance()
            .createSignInIntentBuilder()
            .setAvailableProviders(providers)
            .build(),
        RC_SIGN_IN);

Check Response code on Activity Result

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    // RC_SIGN_IN is the request code you passed into
    if (requestCode == 1234) {

      // Successfully signed in
      if (resultCode == RESULT_OK) {
        // Successfully signed in
        FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
        Toast.makeText(getApplicationContext(), "Successfully signed in", Toast.LENGTH_SHORT)
            .show();
        launchMainActivity(user);
      }
    } else {
      // Sign in failed. If response is null the user canceled the sign-in flow using the back button. Otherwise check
      // response.getError().getErrorCode() and handle the error.
      Toast.makeText(getApplicationContext(), "Unable to Sign in", Toast.LENGTH_SHORT).show();
    }
  }
Finally, the login activity source code look like this.
package com.firebaseauthenticationuiexample;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import com.firebase.ui.auth.AuthUI;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseUser;
import java.util.Arrays;
import java.util.List;

public class LoginActivity extends AppCompatActivity {
  private static final int RC_SIGN_IN = 1234;

  public static void startActivity(Context context) {
    Intent intent = new Intent(context, LoginActivity.class);
    context.startActivity(intent);
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_login);

    // find view
    Button buttonSignIn = findViewById(R.id.buttonSignIn);
    // Set onclick listener
    buttonSignIn.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        FirebaseAuth auth = FirebaseAuth.getInstance();
        if (auth.getCurrentUser() != null) {
          Toast.makeText(getApplicationContext(), "User already signed in, must sign out first",
              Toast.LENGTH_SHORT).show();
          // already signed in
        } else {
          // Choose authentication providers
          List<AuthUI.IdpConfig> providers = Arrays.asList(
              new AuthUI.IdpConfig.EmailBuilder().build());

          // Create and launch sign-in intent
          startActivityForResult(
              AuthUI.getInstance()
                  .createSignInIntentBuilder()
                  .setAvailableProviders(providers)
                  .build(),
              RC_SIGN_IN);
        }
      }
    });
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    // RC_SIGN_IN is the request code you passed into
    if (requestCode == 1234) {

      // Successfully signed in
      if (resultCode == RESULT_OK) {
        // Successfully signed in
        FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
        Toast.makeText(getApplicationContext(), "Successfully signed in", Toast.LENGTH_SHORT)
            .show();
        launchMainActivity(user);
      }
    } else {
      // Sign in failed. If response is null the user canceled the sign-in flow using the back button. Otherwise check
      // response.getError().getErrorCode() and handle the error.
      Toast.makeText(getApplicationContext(), "Unable to Sign in", Toast.LENGTH_SHORT).show();
    }
  }

  private void launchMainActivity(FirebaseUser user) {
    if (user != null) {
      MainActivity.startActivity(this, user.getDisplayName());
      finish();
    }
  }
}
Implement logout in MainActivity

Your login stuff is done. Now I’m going to explain the logout part. open the main activity layout file add one button for perform logout.

<?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"
    tools:context=".MainActivity"
    >

  <Button
      android:id="@+id/buttonLogout"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Logout"
      android:textAllCaps="false"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"

      />
  <TextView
      android:id="@+id/textView"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginBottom="16dp"
      android:text="Welcome"
      android:textColor="@color/colorAccent"
      android:textSize="20sp"
      app:layout_constraintBottom_toTopOf="@+id/buttonLogout"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      />
</androidx.constraintlayout.widget.ConstraintLayout>
Let’s open the main activity source file

Doing signout firebase auth provider gives callback onComplete methods. just like below.

package com.firebaseauthenticationuiexample;

import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import com.firebase.ui.auth.AuthUI;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;

public class MainActivity extends AppCompatActivity {
  private static final String ARG_NAME = "username";

  public static void startActivity(Context context, String username) {
    Intent intent = new Intent(context, MainActivity.class);
    intent.putExtra(ARG_NAME, username);
    context.startActivity(intent);
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    TextView textView = findViewById(R.id.textView);
    if (getIntent().hasExtra(ARG_NAME)) {
      textView.setText(String.format("Welcome - %s", getIntent().getStringExtra(ARG_NAME)));
    }

    Button logout = findViewById(R.id.buttonLogout);
    logout.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        AuthUI.getInstance()
            .signOut(MainActivity.this)
            .addOnCompleteListener(new OnCompleteListener<Void>() {
              public void onComplete(@NonNull Task<Void> task) {
                // user is now signed out
                Toast.makeText(getApplicationContext(), "User has signed out!", Toast.LENGTH_SHORT)
                    .show();
                launchLoginActivity();
              }
            });
      }
    });
  }

  private void launchLoginActivity() {
    LoginActivity.startActivity(MainActivity.this);
    finish();
  }
}

With the help of this android app tutorial, We have learned firebaseui for authentication with email I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Keep in touch

If you want to keep in touch and get an email when I write new blog posts, follow me on facebook or subscribe usIt only takes about 10 seconds to register. 

Still, if you have any queries please put your comment below.

In this article, I’ll show you how you adding firebase to android app. We’ll discuss the possible options and recommended ways of it. Apart from this, I will tell you preliminary information for Firebase integration. For doing that, we’ll create a sample app and will add the firebase into it. So let’s started

Prerequisite

The following preliminary information that required for Firebase

  • One Google account
  • One real device or emulator for running the sample app
  • The latest Android Studio version (1.5 or later)
  • The app must use gradle 4.1 or later
There are two options for adding firebase into your project.
  • Firebase Assistant in Android Studio
    • Prebuild tool available in Android Studio
    • Using Assistant you can connect existing project or create new ones for you.
    • Assistant is automatically added and install all necessary dependencies
  • Add Firebase to your app via a firebase console manually.
    • This way you have to do all operations manually

Note – Google recommended manual way to add firebase in your app.

Create a project in Firebase console

Let’s open web browse and open firebase console. Click on add project -> enter the project name -> If you need analytics then check this option otherwise you can uncheck it. Finally, click on create a project.

Go to add firebase into android platform

Open this project and click on android icon, Then below window pop up. The big thing here is the package name. This package name has to match android app package name.

The next step is to download google-services.json file

You have to download this file and put into your app

Put this json file into app folder in your app

It’s a really important step. So let’s open your app folder -> navigate app folder – paste inside the app google-services.json. After adding it click on next.

Create a project in Android Studio with the same package name.

Let’s move to Android Studio create a new project with package name com.sampleapp. Now paste the google-services.json file in your app folder. just like below screenshot.

Add firebase SDK
Modify project-level build.gradle in Android Studio and add SDK classpath
  dependencies {
    // Add this line
    classpath 'com.google.gms:google-services:4.3.2'
  }
Open the app-level.gradle file. (<project>/<app-module>/build.gradle)
// Add this line
apply plugin: 'com.google.gms.google-services'

After modifying these files sync the project. Now your firebase setup is used. Firebase provides so many libraries that we’ll learn in the coming tutorial.

Learn our firebase android tutorial series

In this post, I’ll show you how to implement the navigation architecture component in your app in Android. For demonstration will create a sample app and will integrate the navigation component in this app. This sample app contains the bottom navigation bar and toolbar menu with navigation components. So let’s get started.

Open the Android Studio and create a new project with Androidx.

Let’s move to Android Studio to create a new project with default template.

Navigate the project build.gradle and add below classpath

Let’s navigate project level build.gradle and below line.

  def nav_version = "2.1.0"
  classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
Apply Navigation Safeargs Plugin.

Now open the app level project build.gradle and this line on top of the file. This plug is helped us a lot in navigation, action and sending argument as well.

apply plugin: "androidx.navigation.safeargs"
Add below dependencies
  def nav_version = "1.0.0"
  def material_version = "1.2.0-alpha03"
  implementation "android.arch.navigation:navigation-fragment:$nav_version"
  implementation "android.arch.navigation:navigation-ui:$nav_version"
  implementation "com.google.android.material:material:$material_version"

Check out the latest version of navigation library here

Now add dimens inside the dimens.xml

Let’s add below dimen in that resource file.

<?xml version="1.0" encoding="utf-8"?>
<resources>
  <dimen name="padding_large">16dp</dimen>
  <dimen name="text_large">22sp</dimen>
  <dimen name="margin_large">16dp</dimen>
</resources>
Open String.xml and update below entries.
<resources>
  <string name="app_name">Navigation Example</string>
  <string name="hello_blank_fragment">Hello blank fragment</string>
  <string name="home">Home</string>
  <string name="cart">Cart</string>
  <string name="shop">Shop</string>
  <string name="about_us">About Us</string>
  <string name="notification">Notifications</string>
  <string name="view_profile">View Profile</string>
  <string name="welcome_navigation_example_application">Welcome \nNavigation Example Application</string>
  <string name="notification_fragment">Notification Fragment</string>
  <string name="cart_fragment">Cart Fragment</string>
  <string name="cart_detail">Cart Detail</string>
  <string name="view_product_with_arguments">View Product with arguments</string>
  <string name="profile_fragment">Profile</string>
  <string name="product_detail">Product Details</string>
  <string name="string_count">Total Products Count</string>
  <string name="name_lable">Product Name -</string>
</resources>
Create a menu resource file toolbar_menu.xml

For showing menu in the toolbar will create menu file named is toolbar_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
	xmlns:app="http://schemas.android.com/apk/res-auto">
	<item
		android:id="@+id/cart_destination"
		android:icon="@drawable/ic_shopping_cart"
		android:title="@string/cart"
		app:showAsAction="ifRoom" />
</menu>
Create res menu file for Bottom Navigation named is bottom_nav_menu.xml

For showing bottom navigation in main activity, I’m creating another menu for that.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
	<item
		android:id="@+id/home_destination"
		android:icon="@drawable/ic_home"
		android:title="@string/home" />
	<item
		android:id="@+id/notification_destination"
		android:icon="@drawable/ic_notifications"
		android:title="@string/notification" />
</menu>
Open main activity layout file and add below code

In main activity layout file contains NavHostFragment and BottomNaviagtion in portrait mode.

<?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"
    tools:context=".MainActivity"
    >

  <fragment
      android:id="@+id/nav_host_fragment"
      android:name="androidx.navigation.fragment.NavHostFragment"
      android:layout_width="0dp"
      android:layout_height="0dp"
      android:layout_weight="1"
      app:defaultNavHost="true"
      app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      app:navGraph="@navigation/nav_graph"
      />

  <com.google.android.material.bottomnavigation.BottomNavigationView
      android:id="@+id/bottom_nav"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:backgroundTint="@color/colorAccent"
      app:layout_constraintBottom_toBottomOf="parent"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      android:background="@color/colorPrimary"
      app:itemIconTint="@color/white"
      app:itemTextColor="@color/white"
      app:menu="@menu/bottom_nav_menu"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
For landscape mode create another layout that has NavigationView.

Create a new layout file for the landscape model with same name. In landscape mode we will show side navigation.

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    >
  <fragment
      android:id="@+id/nav_host_fragment"
      android:name="androidx.navigation.fragment.NavHostFragment"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:layout_weight="1"
      app:defaultNavHost="true"
      app:navGraph="@navigation/nav_graph"
      />

  <com.google.android.material.navigation.NavigationView
      android:id="@+id/nav_view"
      android:layout_width="wrap_content"
      android:layout_height="match_parent"
      android:layout_gravity="start"
      app:menu="@menu/bottom_nav_menu"
      />

</androidx.drawerlayout.widget.DrawerLayout>
Finally, configure navigation component code inside MainActivity.

Open the main activity, In this activity, we are setting bottom navigation, side navigation (for landscape mode) and setting up action menu as well.

For doing all things we are using Navigation Component only.

package com.navigation.example;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.drawerlayout.widget.DrawerLayout;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import androidx.navigation.ui.NavigationUI;
import com.google.android.material.bottomnavigation.BottomNavigationView;

public class MainActivity extends AppCompatActivity {

  private BottomNavigationView bottomNavigationView;
  private NavController navController;
  private DrawerLayout drawerLayout;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    bottomNavigationView = findViewById(R.id.bottom_nav);
    drawerLayout = findViewById(R.id.drawer_layout);

    setUpBottomNav(navController);
    setUpSideNav(navController);
    setUpActionBar(navController);
  }

  private void setUpBottomNav(NavController navController) {
    NavigationUI.setupWithNavController(bottomNavigationView, navController);
  }

  private void setUpSideNav(NavController navController) {
    NavigationUI.setupWithNavController(bottomNavigationView, navController);
  }

  private void setUpActionBar(NavController navController) {
    NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout);
  }

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.toolbar_menu, menu);
    return true;
  }

  @Override
  public boolean onOptionsItemSelected(@NonNull MenuItem item) {
    boolean navigated = NavigationUI.onNavDestinationSelected(item, navController);
    return navigated || super.onOptionsItemSelected(item);
  }

  @Override
  public boolean onSupportNavigateUp() {
    return NavigationUI.navigateUp(
        Navigation.findNavController(this, R.id.nav_host_fragment), drawerLayout);
  }
}
Navigation resource file

in res create a resource folder name is navigation and create navigation graph file. In file name, I’m taken here nav_graph.xml.

Basically, followings things we are doing here

  • Setting up navigation Id
  • Set start destination here is a home fragment
<?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"
    app:startDestination="@id/home_destination">
</navigation>
  • Declear fragments here
<fragment
      android:id="@+id/home_destination"
      android:name="com.navigation.example.fragments.HomeFragment"
      android:label="Home"
      tools:layout="@layout/fragment_home">
</fragment>
  • Setup action and action destination
    <action
        android:id="@+id/next_action"
        app:destination="@+id/profile_destination" />
  • Setup safe argument as well
    <android:name="com.navigation.example.fragments.ProductFragment"
      android:label="Product"
      tools:layout="@layout/fragment_product">

    <argument
        android:name="product_name"
        app:argType="string"
        app:nullable="true" />
    <argument
        android:name="amount"
        app:argType="float"
        android:defaultValue="0.0" />
    <argument
        android:name="item_count"
        app:argType="integer"
        android:defaultValue="0" />
  </fragment>
Finally, your nav_graph.xml code looks like this.
<?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"
    app:startDestination="@id/home_destination">

  <fragment
      android:id="@+id/home_destination"
      android:name="com.navigation.example.fragments.HomeFragment"
      android:label="Home"
      tools:layout="@layout/fragment_home">
    <action
        android:id="@+id/next_action"
        app:destination="@+id/profile_destination" />
  </fragment>

  <fragment
      android:id="@+id/notification_destination"
      android:name="com.navigation.example.fragments.NotificationFragment"
      android:label="Notification"
      tools:layout="@layout/fragment_notification" />
  <fragment

      android:id="@+id/profile_destination"
      android:name="com.navigation.example.fragments.ProfileFragment"
      android:label="Home"
      tools:layout="@layout/fragment_profile">
    <argument
        android:name="productCount"
        android:defaultValue="0"
        app:argType="integer" />
  </fragment>

  <fragment
      android:id="@+id/cart_destination"
      android:name="com.navigation.example.fragments.CartFragment"
      android:label="Cart"
      tools:layout="@layout/fragment_cart">
    <action
        android:id="@+id/next_action"
        app:destination="@+id/product_destination" />
  </fragment>

  <fragment
      android:id="@+id/product_destination"
      android:name="com.navigation.example.fragments.ProductFragment"
      android:label="Product"
      tools:layout="@layout/fragment_product">

    <argument
        android:name="product_name"
        app:argType="string"
        app:nullable="true" />
    <argument
        android:name="amount"
        app:argType="float"
        android:defaultValue="0.0" />
    <argument
        android:name="item_count"
        app:argType="integer"
        android:defaultValue="0" />
  </fragment>


</navigation>
Open HomeFragment and on button click navigate another fragment.

On button click, we are fetching the direction and next action.

package com.navigation.example.fragments;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.navigation.example.R;

/**
 * A HomeFragment {@link Fragment} subclass.
 */
public class HomeFragment extends Fragment {

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_home, container, false);
  }

  @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    Button viewProfile = view.findViewById(R.id.buttonViewProfile);
    viewProfile.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {
        // find navigation
        NavController navController = Navigation.findNavController(view);
        navController.navigate(HomeFragmentDirections.nextAction());
        //One line also can do like this
        // Navigation.findNavController(view).navigate(HomeFragmentDirections.nextAction());
      }
    });
  }
}
Open CartFragment and add below code

On view product button click will move to ProductFragment as pass some arguments.

package com.navigation.example.fragments;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.navigation.NavController;
import androidx.navigation.Navigation;
import com.navigation.example.R;

/**
 * A simple {@link Fragment} subclass.
 */
public class CartFragment extends Fragment {

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_cart, container, false);
  }

  @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    Button buttonViewProduct = view.findViewById(R.id.buttonViewProduct);
    buttonViewProduct.setOnClickListener(new View.OnClickListener() {
      @Override public void onClick(View view) {

        CartFragmentDirections.NextAction nextAction =
            CartFragmentDirections.nextAction("This is a sample product");
        nextAction.setAmount(100.00f);
        nextAction.setItemCount(2);

        NavController navController = Navigation.findNavController(view);
        navController.navigate(nextAction);
      }
    });
  }
}
Get argument using safe argument
 package com.navigation.example.fragments;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import com.navigation.example.R;

/**
 * ProductFragment  {@link Fragment} subclass.
 */
public class ProductFragment extends Fragment {

  public ProductFragment() {
    // Required empty public constructor
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_product, container, false);
  }

  @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    TextView tvInfo = view.findViewById(R.id.tv_product_info);

    ProductFragmentArgs args = ProductFragmentArgs.fromBundle(getArguments());
    //String name = args.getProductName();
    //Float amount = args.getAmount();
    //Integer itemCount = args.getItemCount();

    tvInfo.setText(getString(R.string.name_lable)
        + args.getProductName()
        + "\n" +
        " Count "
        + args.getItemCount()
        + "\n" +
        " Amount "
        + args.getAmount());
  }
}

Conclusion

With the help of this navigation component android tutorial, We have learned how to implement navigation android architecture components in Android. I hope it’s helpful for you, Help me by sharing this post with all your friends who learning android app development.

Get Solution Code

Keep in touch

If you want to keep in touch and get an email when I write new blog posts, Still, if you have any queries please put your comment below.

Check out the architecture components tutorial series

In this post, I will show you the best practices for Implementing an Event bus with RxJava. The RxBus that I’m going to demonstrate that very helpful for state propagation. So guys, let’s started.

1. Create an Android Project named RxBus Example

    //RxJava
    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

2. Create a simple RX bus that emits the data to the subscribers using Observable

package com.rxbusexample

import io.reactivex.Observable
import io.reactivex.subjects.PublishSubject

/*
 * Singleton instance
 * A simple rx bus that emits the data to the subscribers using Observable
 */
object RxBus {

    private val publisher = PublishSubject.create<Any>()

    fun publish(event: Any) {
        publisher.onNext(event)
    }

    // Listen should return an Observable and not the publisher
    // Using ofType we filter only events that match that class type
    fun <T> listen(eventType: Class<T>): Observable<T> = publisher.ofType(eventType)

}

3. Create a class named is RxBusEvent

package com.rxbusexample

class RxBusEvent {

    data class ProgressEvent(
        val showDialog: Boolean,
        val message: String? = null
    )

    data class LogOut(val logout: Boolean)

    data class MessageEvent(val message: String)
}

4. How to use this RxBus

package com.rxbusexample

import android.os.CountDownTimer
import android.os.Handler
import java.util.*
import java.util.concurrent.TimeUnit

/**
 * @timer is timer value in milliseconds (suppose 5 min = 5*60*1000)
 * @interval is a time interval of one count ideally it should be 1000
 */
class CountDownTimer(timer: Long, interval: Long) :
    CountDownTimer(timer, interval) {

    companion object {
        private const val SECONDS = 60
    }

    override fun onTick(millisUntilFinished: Long) {

        val textToShow = String.format(
            Locale.getDefault(), "%02d min: %02d sec",
            TimeUnit.MILLISECONDS.toMinutes(millisUntilFinished) % SECONDS,
            TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) % SECONDS
        )
        // Publish MessageEvents
        RxBus.publish(RxBusEvent.MessageEvent(textToShow))
    }

    override fun onFinish() {
        // Publish ProgressEvent for showing progressbar
        RxBus.publish(RxBusEvent.ProgressEvent(true, "Logging out ..."))
        Handler().postDelayed({
            // Publish ProgressEvent for dismissing progressbar
            RxBus.publish(RxBusEvent.ProgressEvent(false, "Log out Successfully"))
            RxBus.publish(RxBusEvent.LogOut(true))
        }, 3000)

    }

}

5. Let’s open the main activity layout file and add below

<?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"
    tools:context=".MainActivity">


    <TextView
        android:id="@+id/textViewCounter"
        android:layout_width="wrap_content"
        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:layout_marginBottom="16dp"
        android:textSize="20sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.29"
        android:text="Start" />

    <TextView
        android:id="@+id/textViewMessage"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/progressBar"
        tools:text="TextView" />

    <ProgressBar
        android:id="@+id/progressBar"
        style="?android:attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="60dp"
        android:visibility="gone"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textViewCounter" />

    <Button
        android:id="@+id/buttonStart"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:background="@color/colorAccent"
        android:text="Start"
        android:textColor="@color/colorWhite"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/buttonStop"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/buttonStop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="32dp"
        android:background="@color/colorAccent"
        android:text="Stop"
        android:textColor="@color/colorWhite"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/buttonStart" />

</androidx.constraintlayout.widget.ConstraintLayout>

6. Finally, open the MainActivity and listen to the Observable with event type filter

package com.rxbusexample

import android.annotation.SuppressLint
import android.os.Bundle
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    companion object {
        const val INTERVAL: Long = 1000
        const val TIMER_TIME: Long = 1 * 60 * 1000
    }

    @SuppressLint("CheckResult")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val counter = CountDownTimer(TIMER_TIME, INTERVAL)

        // set click event on start button
        buttonStart.setOnClickListener { counter.start() }
        // set click event on stop button
        buttonStop.setOnClickListener {
            counter.cancel()
            textViewCounter.text = "Stop"
        }


        // Listen for MessageEvents only
        RxBus.listen(RxBusEvent.MessageEvent::class.java)
            .subscribe { textViewCounter.text = it.message }

        // Listen for ProgressEvent only
        RxBus.listen(RxBusEvent.ProgressEvent::class.java)
            .subscribe {
                textViewMessage.text = it.message
                progressBar.visibility = if (it.showDialog) VISIBLE else GONE
            }


        // Listen for LogOut only
        RxBus.listen(RxBusEvent.LogOut::class.java)
            .subscribe {
                Toast.makeText(this, "Logout Done !", Toast.LENGTH_LONG).show()
            }

        /*
          // Listen for String events only
          RxBus.listen(String::class.java).subscribe({
              println("Im a String event $it")
          })

          // Listen for Int events only
          RxBus.listen(Int::class.java).subscribe({
              println("Im an Int event $it")
          })*/

    }
}

Conclusion

In this android app tutorial, we learned Implementing event bus with rxjava. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development.

Get Solution Code

Communication between Fragment and Acitivity using RxJava

In this blog, I’ll show you how to capture signature using canvas and save it. For doing that we’ll create a custom SignatureView and use it in our sample app. So let’s get started.

1. Create an android project and add resource file in the string.xml
<resources>
    <string name="app_name">Capture Signature</string>
    <string name="ok">Ok</string>
    <string name="clear">Clear</string>
    <string name="cancel">Cancel</string>
</resources>
    <style name="Dialog.App" parent="AppTheme">
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowFullscreen">false</item>
        <item name="android:windowIsFloating">false</item>

    </style>
2. Write attribute for SignatureView

Create a new file inside res=>value=>attrs.xml, and paste below code

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SignatureView">
        <attr name="penMinWidth" format="dimension" />
        <attr name="penMaxWidth" format="dimension" />
        <attr name="penColor" format="color" />
        <attr name="velocityFilterWeight" format="float" />
        <attr name="clearOnDoubleClick" format="boolean" />
    </declare-styleable>
</resources>
3. Create a Signature View.

In the src folder create a new java class named is SignatureView.java. let’s copy all code and paste into the own class. after that, we have to create a utils file that I explain below.

package com.capturesignature.view

import android.content.Context
import android.content.res.Resources.NotFoundException
import android.graphics.*
import android.graphics.Bitmap.Config.ARGB_8888
import android.graphics.Matrix.ScaleToFit.CENTER
import android.os.Bundle
import android.os.Parcelable
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import com.capturesignature.R.styleable
import com.capturesignature.utils.Bezier
import com.capturesignature.utils.ControlTimedPoints
import com.capturesignature.utils.TimedPoint
import com.capturesignature.view.ViewCompat.isLaidOut
import java.util.*
import kotlin.math.max
import kotlin.math.roundToInt

class SignatureView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
    //View state
    private var mPoints: MutableList<TimedPoint>? = null
    private var mIsEmpty = false
    private var mHasEditState: Boolean? = null
    private var mLastTouchX = 0f
    private var mLastTouchY = 0f
    private var mLastVelocity = 0f
    private var mLastWidth = 0f
    private val mDirtyRect: RectF
    private var mBitmapSavedState: Bitmap? = null

    // Cache
    private val mPointsCache: MutableList<TimedPoint> = ArrayList()
    private val mControlTimedPointsCached: ControlTimedPoints =
        ControlTimedPoints()
    private val mBezierCached: Bezier =
        Bezier()
    //Configurable parameters
    private var mMinWidth = 0
    private var mMaxWidth = 0
    private var mVelocityFilterWeight = 0f
    private var mOnSignedListener: OnSignedListener? = null
    private var mClearOnDoubleClick = false
    //Click values
    private var mFirstClick: Long = 0
    private var mCountClick = 0
    //Default attribute values
    private val DEFAULT_ATTR_PEN_MIN_WIDTH_PX = 2
    private val DEFAULT_ATTR_PEN_MAX_WIDTH_PX = 3
    private val DEFAULT_ATTR_PEN_COLOR = Color.BLACK
    private val DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT = 0.9f
    private val DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK = false
    private val mPaint = Paint()
    private var mSignatureBitmap: Bitmap? = null
    private var mSignatureBitmapCanvas: Canvas? = null
    override fun onSaveInstanceState(): Parcelable? {
        val bundle = Bundle()
        bundle.putParcelable("superState", super.onSaveInstanceState())
        if (mHasEditState == null || mHasEditState!!) {
            mBitmapSavedState = getTransparentSignatureBitmap()
        }
        bundle.putParcelable("signatureBitmap", mBitmapSavedState)
        return bundle
    }

    override fun onRestoreInstanceState(state: Parcelable) {
        var state: Parcelable? = state
        if (state is Bundle) {
            val bundle = state
            setSignatureBitmap(bundle.getParcelable<Parcelable>("signatureBitmap") as Bitmap)
            mBitmapSavedState = bundle.getParcelable("signatureBitmap")
            state = bundle.getParcelable("superState")
        }
        mHasEditState = false
        super.onRestoreInstanceState(state)
    }

    /**
     * Set the pen color from a given resource.
     * If the resource is not found, [Color.BLACK] is assumed.
     *
     * @param colorRes the color resource.
     */
    fun setPenColorRes(colorRes: Int) {
        try {
            setPenColor(resources.getColor(colorRes))
        } catch (ex: NotFoundException) {
            setPenColor(Color.parseColor("#000000"))
        }
    }

    /**
     * Set the pen color from a given color.
     *
     * @param color the color.
     */
    private fun setPenColor(color: Int) {
        mPaint.color = color
    }

    /**
     * Set the minimum width of the stroke in pixel.
     *
     * @param minWidth the width in dp.
     */
    fun setMinWidth(minWidth: Float) {
        mMinWidth = convertDpToPx(minWidth)
    }

    /**
     * Set the maximum width of the stroke in pixel.
     *
     * @param maxWidth the width in dp.
     */
    fun setMaxWidth(maxWidth: Float) {
        mMaxWidth = convertDpToPx(maxWidth)
    }

    /**
     * Set the velocity filter weight.
     *
     * @param velocityFilterWeight the weight.
     */
    fun setVelocityFilterWeight(velocityFilterWeight: Float) {
        mVelocityFilterWeight = velocityFilterWeight
    }

    private fun clearView() {
        mPoints = ArrayList()
        mLastVelocity = 0f
        mLastWidth = (mMinWidth + mMaxWidth) / 2.toFloat()
        if (mSignatureBitmap != null) {
            mSignatureBitmap = null
            ensureSignatureBitmap()
        }
        setIsEmpty(true)
        invalidate()
    }

    fun clear() {
        clearView()
        mHasEditState = true
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        if (!isEnabled) return false
        val eventX = event.x
        val eventY = event.y
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
                mPoints!!.clear()
                if (isDoubleClick()) {
                    return false
                }
                mLastTouchX = eventX
                mLastTouchY = eventY
                addPoint(getNewPoint(eventX, eventY))
                if (mOnSignedListener != null) mOnSignedListener!!.onStartSigning()
                resetDirtyRect(eventX, eventY)
                addPoint(getNewPoint(eventX, eventY))
                setIsEmpty(false)
            }
            MotionEvent.ACTION_MOVE -> {
                resetDirtyRect(eventX, eventY)
                addPoint(getNewPoint(eventX, eventY))
                setIsEmpty(false)
            }
            MotionEvent.ACTION_UP -> {
                resetDirtyRect(eventX, eventY)
                addPoint(getNewPoint(eventX, eventY))
                parent.requestDisallowInterceptTouchEvent(true)
            }
            else -> return false
        }
        //invalidate();
        invalidate(
            (mDirtyRect.left - mMaxWidth).toInt(),
            (mDirtyRect.top - mMaxWidth).toInt(),
            (mDirtyRect.right + mMaxWidth).toInt(),
            (mDirtyRect.bottom + mMaxWidth).toInt()
        )
        return true
    }

    override fun onDraw(canvas: Canvas) {
        if (mSignatureBitmap != null) {
            canvas.drawBitmap(mSignatureBitmap!!, 0f, 0f, mPaint)
        }
    }

    fun setOnSignedListener(listener: OnSignedListener?) {
        mOnSignedListener = listener
    }

    private fun setIsEmpty(newValue: Boolean) {
        mIsEmpty = newValue
        if (mOnSignedListener != null) {
            if (mIsEmpty) {
                mOnSignedListener!!.onClear()
            } else {
                mOnSignedListener!!.onSigned()
            }
        }
    }

    fun getSignatureBitmap(): Bitmap {
        val originalBitmap = getTransparentSignatureBitmap()
        val whiteBgBitmap = Bitmap.createBitmap(
            originalBitmap!!.width, originalBitmap.height, ARGB_8888
        )
        val canvas = Canvas(whiteBgBitmap)
        canvas.drawColor(Color.WHITE)
        canvas.drawBitmap(originalBitmap, 0f, 0f, null)
        return whiteBgBitmap
    }

    fun setSignatureBitmap(signature: Bitmap) {
        if (isLaidOut(this)) {
            clearView()
            ensureSignatureBitmap()
            val tempSrc = RectF()
            val tempDst = RectF()
            val dWidth = signature.width
            val dHeight = signature.height
            val vWidth = width
            val vHeight = height
            // Generate the required transform.
            tempSrc[0f, 0f, dWidth.toFloat()] = dHeight.toFloat()
            tempDst[0f, 0f, vWidth.toFloat()] = vHeight.toFloat()
            val drawMatrix = Matrix()
            drawMatrix.setRectToRect(tempSrc, tempDst, CENTER)
            val canvas = Canvas(mSignatureBitmap!!)
            canvas.drawBitmap(signature, drawMatrix, null)
            setIsEmpty(false)
            invalidate()
        } else {
            viewTreeObserver.addOnGlobalLayoutListener(object : OnGlobalLayoutListener {
                override fun onGlobalLayout() {
                    // Remove layout listener...
                    ViewTreeObserverCompat.removeOnGlobalLayoutListener(viewTreeObserver, this)
                    // Signature bitmap...
                    setSignatureBitmap(signature)
                }
            })

        }

    }

    private fun getTransparentSignatureBitmap(): Bitmap? {
        ensureSignatureBitmap()
        return mSignatureBitmap
    }

    private fun getTransparentSignatureBitmap(trimBlankSpace: Boolean): Bitmap? {
        if (!trimBlankSpace) {
            return getTransparentSignatureBitmap()
        }
        ensureSignatureBitmap()
        val imgHeight = mSignatureBitmap!!.height
        val imgWidth = mSignatureBitmap!!.width
        val backgroundColor = Color.TRANSPARENT
        var xMin = Int.MAX_VALUE
        var xMax = Int.MIN_VALUE
        var yMin = Int.MAX_VALUE
        var yMax = Int.MIN_VALUE
        var foundPixel = false
        // Find xMin
        for (x in 0 until imgWidth) {
            var stop = false
            for (y in 0 until imgHeight) {
                if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
                    xMin = x
                    stop = true
                    foundPixel = true
                    break
                }
            }
            if (stop) break
        }
        // Image is empty...
        if (!foundPixel) return null
        // Find yMin
        for (y in 0 until imgHeight) {
            var stop = false
            for (x in xMin until imgWidth) {
                if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
                    yMin = y
                    stop = true
                    break
                }
            }
            if (stop) break
        }
        // Find xMax
        for (x in imgWidth - 1 downTo xMin) {
            var stop = false
            for (y in yMin until imgHeight) {
                if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
                    xMax = x
                    stop = true
                    break
                }
            }
            if (stop) break
        }
        // Find yMax
        for (y in imgHeight - 1 downTo yMin) {
            var stop = false
            for (x in xMin..xMax) {
                if (mSignatureBitmap!!.getPixel(x, y) != backgroundColor) {
                    yMax = y
                    stop = true
                    break
                }
            }
            if (stop) break
        }
        return Bitmap.createBitmap(mSignatureBitmap!!, xMin, yMin, xMax - xMin, yMax - yMin)
    }

    private fun isDoubleClick(): Boolean {
        if (mClearOnDoubleClick) {
            if (mFirstClick != 0L && System.currentTimeMillis() - mFirstClick > DOUBLE_CLICK_DELAY_MS) {
                mCountClick = 0
            }
            mCountClick++
            if (mCountClick == 1) {
                mFirstClick = System.currentTimeMillis()
            } else if (mCountClick == 2) {
                val lastClick = System.currentTimeMillis()
                if (lastClick - mFirstClick < DOUBLE_CLICK_DELAY_MS) {
                    clearView()
                    return true
                }
            }
        }
        return false
    }

    private fun getNewPoint(
        x: Float,
        y: Float
    ): TimedPoint {
        val mCacheSize = mPointsCache.size
        val timedPoint: TimedPoint
        if (mCacheSize == 0) { // Cache is empty, create a new point
            timedPoint = TimedPoint()
        } else { // Get point from cache
            timedPoint = mPointsCache.removeAt(mCacheSize - 1)
        }
        return timedPoint.set(x, y)
    }

    private fun recyclePoint(point: TimedPoint) {
        mPointsCache.add(point)
    }

    private fun addPoint(newPoint: TimedPoint) {
        mPoints!!.add(newPoint)
        val pointsCount = mPoints!!.size
        if (pointsCount > 3) {
            var tmp: ControlTimedPoints =
                calculateCurveControlPoints(mPoints!![0], mPoints!![1], mPoints!![2])
            val c2: TimedPoint = tmp.c2
            recyclePoint(tmp.c1)
            tmp = calculateCurveControlPoints(mPoints!![1], mPoints!![2], mPoints!![3])
            val c3: TimedPoint = tmp.c1
            recyclePoint(tmp.c2)
            val curve: Bezier = mBezierCached.set(mPoints!![1], c2, c3, mPoints!![2])
            val startPoint: TimedPoint = curve.startPoint
            val endPoint: TimedPoint = curve.endPoint
            var velocity: Float = endPoint.velocityFrom(startPoint)
            velocity = if (java.lang.Float.isNaN(velocity)) 0.0f else velocity
            velocity = (mVelocityFilterWeight * velocity
                    + (1 - mVelocityFilterWeight) * mLastVelocity)
            // The new width is a function of the velocity. Higher velocities
            // correspond to thinner strokes.
            val newWidth = strokeWidth(velocity)

            addBezier(curve, mLastWidth, newWidth)
            mLastVelocity = velocity
            mLastWidth = newWidth
            // Remove the first element from the list,
            // so that we always have no more than 4 mPoints in mPoints array.
            recyclePoint(mPoints!!.removeAt(0))
            recyclePoint(c2)
            recyclePoint(c3)
        } else if (pointsCount == 1) {
            // To reduce the initial lag make it work with 3 mPoints
            // by duplicating the first point
            val firstPoint: TimedPoint = mPoints!![0]
            mPoints!!.add(getNewPoint(firstPoint.x, firstPoint.y))
        }
        mHasEditState = true
    }

    private fun addBezier(
        curve: Bezier,
        startWidth: Float,
        endWidth: Float
    ) { //  mSvgBuilder.append(curve, (startWidth + endWidth) / 2);
        ensureSignatureBitmap()
        val originalWidth = mPaint.strokeWidth
        val widthDelta = endWidth - startWidth
        val drawSteps = Math.ceil(curve.length().toDouble())
            .toFloat()
        var i = 0
        while (i < drawSteps) {
            // Calculate the Bezier (x, y) coordinate for this step.
            val t = i.toFloat() / drawSteps
            val tt = t * t
            val ttt = tt * t
            val u = 1 - t
            val uu = u * u
            val uuu = uu * u
            var x: Float = uuu * curve.startPoint.x
            x += 3 * uu * t * curve.control1.x
            x += 3 * u * tt * curve.control2.x
            x += ttt * curve.endPoint.x
            var y: Float = uuu * curve.startPoint.y
            y += 3 * uu * t * curve.control1.y
            y += 3 * u * tt * curve.control2.y
            y += ttt * curve.endPoint.y
            // Set the incremental stroke width and draw.
            mPaint.strokeWidth = startWidth + ttt * widthDelta
            mSignatureBitmapCanvas!!.drawPoint(x, y, mPaint)
            expandDirtyRect(x, y)
            i++
        }
        mPaint.strokeWidth = originalWidth
    }

    private fun calculateCurveControlPoints(
        s1: TimedPoint,
        s2: TimedPoint,
        s3: TimedPoint
    ): ControlTimedPoints {
        val dx1: Float = s1.x - s2.x
        val dy1: Float = s1.y - s2.y
        val dx2: Float = s2.x - s3.x
        val dy2: Float = s2.y - s3.y
        val m1X: Float = (s1.x + s2.x) / 2.0f
        val m1Y: Float = (s1.y + s2.y) / 2.0f
        val m2X: Float = (s2.x + s3.x) / 2.0f
        val m2Y: Float = (s2.y + s3.y) / 2.0f
        val l1 = Math.sqrt(dx1 * dx1 + dy1 * dy1.toDouble())
            .toFloat()
        val l2 = Math.sqrt(dx2 * dx2 + dy2 * dy2.toDouble())
            .toFloat()
        val dxm = m1X - m2X
        val dym = m1Y - m2Y
        var k = l2 / (l1 + l2)
        if (java.lang.Float.isNaN(k)) k = 0.0f
        val cmX = m2X + dxm * k
        val cmY = m2Y + dym * k
        val tx: Float = s2.x - cmX
        val ty: Float = s2.y - cmY
        return mControlTimedPointsCached.set(
            getNewPoint(m1X + tx, m1Y + ty),
            getNewPoint(m2X + tx, m2Y + ty)
        )
    }

    private fun strokeWidth(velocity: Float): Float {
        return max(mMaxWidth / (velocity + 1), mMinWidth.toFloat())
    }

    /**
     * Called when replaying history to ensure the dirty region includes all
     * mPoints.
     *
     * @param historicalX the previous x coordinate.
     * @param historicalY the previous y coordinate.
     */
    private fun expandDirtyRect(
        historicalX: Float,
        historicalY: Float
    ) {
        if (historicalX < mDirtyRect.left) {
            mDirtyRect.left = historicalX
        } else if (historicalX > mDirtyRect.right) {
            mDirtyRect.right = historicalX
        }
        if (historicalY < mDirtyRect.top) {
            mDirtyRect.top = historicalY
        } else if (historicalY > mDirtyRect.bottom) {
            mDirtyRect.bottom = historicalY
        }
    }

    /**
     * Resets the dirty region when the motion event occurs.
     *
     * @param eventX the event x coordinate.
     * @param eventY the event y coordinate.
     */
    private fun resetDirtyRect(
        eventX: Float,
        eventY: Float
    ) { // The mLastTouchX and mLastTouchY were set when the ACTION_DOWN motion event occurred.
        mDirtyRect.left = Math.min(mLastTouchX, eventX)
        mDirtyRect.right = Math.max(mLastTouchX, eventX)
        mDirtyRect.top = Math.min(mLastTouchY, eventY)
        mDirtyRect.bottom = Math.max(mLastTouchY, eventY)
    }

    private fun ensureSignatureBitmap() {
        if (mSignatureBitmap == null) {
            mSignatureBitmap = Bitmap.createBitmap(
                width, height,
                ARGB_8888
            )
            mSignatureBitmapCanvas = Canvas(mSignatureBitmap!!)
        }
    }

    private fun convertDpToPx(dp: Float): Int {
        return (context.resources.displayMetrics.density * dp).roundToInt()
    }

    interface OnSignedListener {
        fun onStartSigning()
        fun onSigned()
        fun onClear()
    }

    private fun getPoints(): List<TimedPoint?>? {
        return mPoints
    }

    companion object {
        private const val DOUBLE_CLICK_DELAY_MS = 200
    }

    init {
        val a = context.theme.obtainStyledAttributes(
            attrs,
            styleable.SignatureView,
            0, 0
        )
        //Configurable parameters
        try {
            mMinWidth = a.getDimensionPixelSize(
                styleable.SignatureView_penMinWidth,
                convertDpToPx(DEFAULT_ATTR_PEN_MIN_WIDTH_PX.toFloat())
            )
            mMaxWidth = a.getDimensionPixelSize(
                styleable.SignatureView_penMaxWidth,
                convertDpToPx(DEFAULT_ATTR_PEN_MAX_WIDTH_PX.toFloat())
            )
            mPaint.color = a.getColor(
                styleable.SignatureView_penColor, DEFAULT_ATTR_PEN_COLOR
            )
            mVelocityFilterWeight = a.getFloat(
                styleable.SignatureView_velocityFilterWeight,
                DEFAULT_ATTR_VELOCITY_FILTER_WEIGHT
            )
            mClearOnDoubleClick = a.getBoolean(
                styleable.SignatureView_clearOnDoubleClick,
                DEFAULT_ATTR_CLEAR_ON_DOUBLE_CLICK
            )
        } finally {
            a.recycle()
        }
        //Fixed parameters
        mPaint.isAntiAlias = true
        mPaint.style = Paint.Style.STROKE
        mPaint.strokeCap = Paint.Cap.ROUND
        mPaint.strokeJoin = Paint.Join.ROUND
        //Dirty rectangle to update only the changed portion of the view
        mDirtyRect = RectF()
        clearView()
    }
}
4. Create a view utils file ViewCompat
package com.capturesignature.view

import android.os.Build
import android.view.View

object ViewCompat {

  fun isLaidOut(view: View): Boolean {
    return if (Build.VERSION.SDK_INT >= 19) {
      view.isLaidOut
    } else view.width > 0 && view.height > 0
  }
}
5. Create a view utils class named ViewTreeObserverCompat
package com.capturesignature.view

import android.annotation.SuppressLint
import android.os.Build
import android.view.ViewTreeObserver
import android.view.ViewTreeObserver.OnGlobalLayoutListener

object ViewTreeObserverCompat {
  /**
   * Remove a previously installed global layout callback.
   * @param observer the view observer
   * @param victim the victim
   */
  @SuppressLint("NewApi")
  fun removeOnGlobalLayoutListener(
    observer: ViewTreeObserver,
    victim: OnGlobalLayoutListener?
  ) { // Future (API16+)...
    if (Build.VERSION.SDK_INT >= 16) {
      observer.removeOnGlobalLayoutListener(victim)
    } else {
      observer.removeGlobalOnLayoutListener(victim)
    }
  }
}
6. Create another package named is utils and paste 3 utilizes class.
Bezier.java
package com.capturesignature.utils

class Bezier {

  lateinit var startPoint: TimedPoint

  lateinit var control1: TimedPoint

  lateinit var control2: TimedPoint

  lateinit var endPoint: TimedPoint
  operator fun set(
    startPoint: TimedPoint,
    control1: TimedPoint,
    control2: TimedPoint,
    endPoint: TimedPoint
  ): Bezier {
    this.startPoint = startPoint
    this.control1 = control1
    this.control2 = control2
    this.endPoint = endPoint
    return this
  }

  fun length(): Float {
    val steps = 10
    var length = 0f
    var cx: Double
    var cy: Double
    var px = 0.0
    var py = 0.0
    var xDiff: Double
    var yDiff: Double
    for (i in 0..steps) {
      val t = i.toFloat() / steps
      cx = point(
          t, startPoint.x, control1.x,
          control2.x, endPoint.x
      )
      cy = point(
          t, startPoint.y, control1.y,
          control2.y, endPoint.y
      )
      if (i > 0) {
        xDiff = cx - px
        yDiff = cy - py
        length += Math.sqrt(xDiff * xDiff + yDiff * yDiff)
            .toFloat()
      }
      px = cx
      py = cy
    }
    return length
  }

  private fun point(
    t: Float,
    start: Float,
    c1: Float,
    c2: Float,
    end: Float
  ): Double {
    return start * (1.0 - t) * (1.0 - t) * (1.0 - t) + 3.0 * c1 * (1.0 - t) * (1.0 - t) * t + 3.0 * c2 * (1.0 - t) * t * t + end * t * t * t
  }
}
ControlTimedPoints.java
package com.capturesignature.utils

class ControlTimedPoints {
    lateinit var c1: TimedPoint
    lateinit var c2: TimedPoint
    operator fun set(c1: TimedPoint, c2: TimedPoint): ControlTimedPoints {
        this.c1 = c1
        this.c2 = c2
        return this
    }
}
TimedPoint.java
package com.capturesignature.utils

class TimedPoint {

  var x = 0f
  var y = 0f
  var timestamp: Long = 0

  operator fun set(
    x: Float,
    y: Float
  ): TimedPoint {
    this.x = x
    this.y = y
    timestamp = System.currentTimeMillis()
    return this
  }

  fun velocityFrom(start: TimedPoint): Float {
    var diff = timestamp - start.timestamp
    if (diff <= 0) {
      diff = 1
    }
    var velocity = distanceTo(start) / diff
    if (java.lang.Float.isInfinite(velocity) || java.lang.Float.isNaN(velocity)) {
      velocity = 0f
    }
    return velocity
  }

  private fun distanceTo(point: TimedPoint): Float {
    return Math.sqrt(
        Math.pow(
            point.x - x.toDouble(),
            2.0
        ) + Math.pow(point.y - y.toDouble(), 2.0)
    )
        .toFloat()
  }
}
7. Create a layout file for capture signature pad

In this layout file, we have three buttons with SignatureView

<?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:background="@color/colorWhite"
    android:fitsSystemWindows="true"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/buttonCancel"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:background="@color/colorAccent"
        android:text="@string/cancel"
        android:textColor="@drawable/button_text_color"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/buttonClear"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/buttonClear"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:background="@color/colorAccent"
        android:text="@string/clear"
        android:textColor="@drawable/button_text_color"
        android:visibility="visible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@+id/buttonOk"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/buttonCancel" />

    <Button
        android:id="@+id/buttonOk"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:background="@color/colorAccent"
        android:text="@string/ok"
        android:textColor="@drawable/button_text_color"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toEndOf="@+id/buttonClear" />

    <com.capturesignature.view.SignatureView
        android:id="@+id/signatureView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginBottom="16dp"
        app:layout_constraintBottom_toTopOf="@+id/buttonClear"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
8. Create a dialog fragment SignatureDialogFragment
package com.capturesignature

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
import com.capturesignature.view.SignatureView
import kotlinx.android.synthetic.main.fragment_signature_dialog.*

class SignatureDialogFragment(private val onSignedListener: OnSignedCaptureListener) :
    DialogFragment(),
    SignatureView.OnSignedListener {

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        isCancelable = false
        return inflater.inflate(R.layout.fragment_signature_dialog, container, false)
    }

    override fun getTheme(): Int {
        return R.style.Dialog_App
    }


    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        buttonCancel.setOnClickListener { dismiss() }
        buttonClear.setOnClickListener { signatureView.clear() }
        buttonOk.setOnClickListener {
            onSignedListener.onSignatureCaptured(signatureView.getSignatureBitmap(), "")
            dismiss()
        }
        signatureView.setOnSignedListener(this)

    }

    override fun onStartSigning() {

    }

    override fun onSigned() {
        buttonOk.isEnabled = true
        buttonClear.isEnabled = true
    }

    override fun onClear() {
        buttonClear.isEnabled = false
        buttonOk.isEnabled = false
    }
}
Open the MainActivity class and paste below code

Basically, we are creating an instance of SignatureDialogFragment. This fragment is written a signature image Bitmap in onSignatureCaptured() callback.

package com.capturesignature

import android.graphics.Bitmap
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*


class MainActivity : AppCompatActivity(), OnSignedCaptureListener {

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

    }

    private fun showDialog() {
        val dialogFragment = SignatureDialogFragment(this)
        dialogFragment.show(supportFragmentManager, "signature")
    }

    override fun onSignatureCaptured(bitmap: Bitmap, fileUri: String) {
        imageView.setImageBitmap(bitmap)
    }
}

Conclusion

In this blog, we have learned the implementation of a capture signature using Canvas in Android. I hope it’s helpful for you.

Get Solution Code

In this post,  I will show you how to setup auto retrying the request with retrofit Android. For doing that we’ll create an example application that contains auto retrying request functionality with Retrofit. Let’s get started.

Android app your network calls can and will fail randomly due to low bandwidth and low network connectivity. Hence it is a very good idea to add auto-retry policy for some important network calls. It makes for better user experience.

1. Create a new Android Project

Let open the android studio and create a new project with the default template. In this sample app, we are going to use Retrofit and Gson so we have to add dependencies in build.gradle.

  implementation 'com.squareup.retrofit2:retrofit:2.7.0'
  implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
2. Define Retry annotation

Create a @Retry annotation interface. Which APIs annotated with this annotation retry functionally will auto-enable. Here a have set the default attempts is 3 you can change it based on your app need.

package com.retrofitautoretryexample.retrofit;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Target(METHOD)
@Retention(RUNTIME)
public @interface Retry {
  int max() default 3;
}
3. Write a retry call adapter factory

Create a new java class named RetryCallAdapterFactory which extends CallAdapter.Factory. In this factory class, we’ll check is request is annotated with @Retry or not. If annotated then every failure request try to call again at least 3 times.

package com.retrofitautoretryexample.retrofit;

import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
import okhttp3.Request;
import retrofit2.Call;
import retrofit2.CallAdapter;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;

public class RetryCallAdapterFactory extends CallAdapter.Factory {
  private static final String TAG = "RetryCallAdapterFactory";

  public static RetryCallAdapterFactory create() {
    return new RetryCallAdapterFactory();
  }

  @Nullable
  @Override
  public CallAdapter<?, ?> get(@NonNull Type returnType, @NonNull Annotation[] annotations,
      @NonNull Retrofit retrofit) {
    /**
     * You can setup a default max retry count for all connections.
     */
    int itShouldRetry = 0;
    final Retry retry = getRetry(annotations);
    if (retry != null) {
      itShouldRetry = retry.max();
    }
    Log.d(TAG, "Starting a CallAdapter with {} retries." + itShouldRetry);
    return new RetryCallAdapter<>(
        retrofit.nextCallAdapter(this, returnType, annotations),
        itShouldRetry
    );
  }

  private Retry getRetry(@NonNull Annotation[] annotations) {

    for (Annotation annotation : annotations) {
      if (annotation instanceof Retry) {
        return (Retry) annotation;
      }
    }
    return null;
  }

  static final class RetryCallAdapter<R, T> implements CallAdapter<R, T> {

    private final CallAdapter<R, T> delegated;
    private final int maxRetries;

    public RetryCallAdapter(CallAdapter<R, T> delegated, int maxRetries) {
      this.delegated = delegated;
      this.maxRetries = maxRetries;
    }

    @Override
    public Type responseType() {
      return delegated.responseType();
    }

    @Override
    public T adapt(final Call<R> call) {
      return delegated.adapt(maxRetries > 0 ? new RetryingCall<>(call, maxRetries) : call);
    }
  }

  static final class RetryingCall<R> implements Call<R> {

    private final Call<R> delegated;
    private final int maxRetries;

    public RetryingCall(Call<R> delegated, int maxRetries) {
      this.delegated = delegated;
      this.maxRetries = maxRetries;
    }

    @Override
    public Response<R> execute() throws IOException {
      return delegated.execute();
    }

    @Override
    public void enqueue(@NonNull Callback<R> callback) {
      delegated.enqueue(new RetryCallback<>(delegated, callback, maxRetries));
    }

    @Override
    public boolean isExecuted() {
      return delegated.isExecuted();
    }

    @Override
    public void cancel() {
      delegated.cancel();
    }

    @Override
    public boolean isCanceled() {
      return delegated.isCanceled();
    }

    @Override
    public Call<R> clone() {
      return new RetryingCall<>(delegated.clone(), maxRetries);
    }

    @Override
    public Request request() {
      return delegated.request();
    }
  }

  static final class RetryCallback<T> implements Callback<T> {

    private final Call<T> call;
    private final Callback<T> callback;
    private final int maxRetries;

    public RetryCallback(Call<T> call, Callback<T> callback, int maxRetries) {
      this.call = call;
      this.callback = callback;
      this.maxRetries = maxRetries;
    }

    private final AtomicInteger retryCount = new AtomicInteger(0);

    @Override
    public void onResponse(@NonNull Call<T> call, @NonNull Response<T> response) {
      if (!response.isSuccessful() && retryCount.incrementAndGet() <= maxRetries) {
        Log.d(TAG, "Call with no success result code: {} " + response.code());
        retryCall();
      } else {
        callback.onResponse(call, response);
      }
    }

    @Override
    public void onFailure(@NonNull Call<T> call, @NonNull Throwable t) {
      Log.d(TAG, "Call failed with message:  " + t.getMessage(), t);
      if (retryCount.incrementAndGet() <= maxRetries) {
        retryCall();
      } else if (maxRetries > 0) {
        Log.d(TAG, "No retries left sending timeout up.");
        callback.onFailure(call,
            new TimeoutException(String.format("No retries left after %s attempts.", maxRetries)));
      } else {
        callback.onFailure(call, t);
      }
    }

    private void retryCall() {
      Log.w(TAG, "" + retryCount.get() + "/" + maxRetries + " " + " Retrying...");
      call.clone().enqueue(this);
    }
  }
}
4. Create POJO for server response

All retry stuff is done. Meanwhile, I will show you how to use this class in your project. So for doing that create a POJO for parsing server response. So, create a new class named is UserResponse and paste below code.

package com.retrofitautoretryexample.model;

import com.google.gson.annotations.SerializedName;

public class UserResponse {
  @SerializedName("authToken")
  private String authToken;
  @SerializedName("data")
  private Object data;
  @SerializedName("error")
  private Boolean error;
  @SerializedName("message")
  private String message;
  @SerializedName("statusCode")
  private Long statusCode;

  public String getAuthToken() {
    return authToken;
  }

  public Object getData() {
    return data;
  }

  public Boolean getError() {
    return error;
  }

  public String getMessage() {
    return message;
  }

  public Long getStatusCode() {
    return statusCode;
  }
}
5. Write a UserApiService interface for Retrofit

For integrating with server let’s create a Retrofit Interface and add below code

package com.retrofitautoretryexample;

import com.retrofitautoretryexample.model.UserResponse;
import com.retrofitautoretryexample.retrofit.Retry;
import retrofit2.Call;
import retrofit2.http.GET;

public interface UserApiService {

  @Retry
  @GET("user")
  Call<UserResponse> getUsers();
}
6. Create a Retrofit Client and add RetryCallAdapterFactory with client

Furthermore, create a retrofit instance that returns UserApiService service class. I have added RetryCallAdapterFactory with retrofit client.

package com.retrofitautoretryexample;

import com.retrofitautoretryexample.retrofit.RetryCallAdapterFactory;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {

  private static final String BASE_URL = "https://androidwave.com/api/";

  private static Retrofit retrofit = null;

  public static UserApiService getApiService() {
    if (retrofit == null) {
      retrofit = new Retrofit
          .Builder()
          .baseUrl(BASE_URL)
          .addCallAdapterFactory(RetryCallAdapterFactory.create())
          .addConverterFactory(GsonConverterFactory.create())
          .build();
    }
    return retrofit.create(UserApiService.class);
  }
}
7. Finally, add below code in MainActivity

In this activity, we’ll call get user API and show the result on TextView

package com.retrofitautoretryexample;

import android.os.Bundle;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.retrofitautoretryexample.model.UserResponse;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;

public class MainActivity extends AppCompatActivity {

  private TextView txvResult;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    txvResult = findViewById(R.id.txvResults);

    UserApiService apiService = RetrofitClient.getApiService();
    apiService.getUsers().enqueue(new Callback<UserResponse>() {
      @Override public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {
        txvResult.setText(response.body().getMessage());
        Toast.makeText(getApplicationContext(), "Success " + response.body().getMessage(),
            Toast.LENGTH_LONG).show();
      }

      @Override public void onFailure(Call<UserResponse> call, Throwable t) {
        txvResult.setText(t.getMessage());
        Toast.makeText(getApplicationContext(), "Failure " + t.getMessage(), Toast.LENGTH_LONG)
            .show();
      }
    });
  }
}
Verify this solution

For verifying this solution, run this app and see you will able to see the result on TextView. Now disconnect the internet from the device and run again. If you check on logcat output will be like this

    RetryCallAdapterFactory: Starting a CallAdapter with {} retries.3 
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: 1/3  Retrying...
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: 2/3  Retrying...
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: 3/3  Retrying...
    RetryCallAdapterFactory: Call failed with message:  Unable to resolve host "androidwave.com": No address associated with hostname
    RetryCallAdapterFactory: No retries left sending timeout up.
Conclusion

With the help of this tutorial, we have learned the implementation Retrying Request with Retrofit. I hope it’s helpful for you, then help me by sharing this post with all your friends who learning android app development.

In this post, I will show you how to implement android TextView enable copy paste. For doing that we’ll create a sample app and Implement copy/paste in your TextView in Android programmatically.

Android EditText has inbuilt functionality whereby you can copy text through a long press selected text but in TextView you have to write some code for that. Let’s get started

1. Register View for ContextMenu

One activity that has an inbuilt method is registerForContextMenu(View view). Using this method we’ll register view for the pop-menu. In this article, we’re making a simple app. So let’s open activity layout file and add TextView and EditText

<?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"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hi Welcome All "
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <EditText
        android:id="@+id/editText"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:ems="10"
        android:inputType="textPersonName"
        android:text=" "
        app:layout_constraintBottom_toTopOf="@+id/textView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

For demonstration, we’ll copy text from TextView and paste in EditText. Now let register TextView for ContextMenu

2. In Activity, on create method add below line of code

In activity onCreate() method you have to initialize TextView and call registerForContextMenu() method and pass your TextView instance on it .

registerForContextMenu(textView)

According to an android official, the doc registerForContextMenu() used to register a context menu to be shown for the given view. In this sample, we are registered TextView.

3. Now to override a method onCreateContextMenu()

Now you have to override a method onCreateContextMenu(), which will help us achieve our copy functionality.

  override fun onCreateContextMenu(
        menu: ContextMenu?,
        v: View?,
        menuInfo: ContextMenu.ContextMenuInfo?
    ) {
        menu?.add(0, v!!.id, 0, "Copy")
        //setting header title for menu
        menu?.setHeaderTitle("Copy Selected Text")

        var manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
        val textView = v as TextView
        val clipData = ClipData.newPlainText("text", textView.text)
        manager.setPrimaryClip(clipData)
        super.onCreateContextMenu(menu, v, menuInfo)

    }
Finally, your full source code seems like below
 package com.textviewcopypaste

import android.content.ClipData
import android.content.ClipboardManager
import android.os.Bundle
import android.view.ContextMenu
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

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

        registerForContextMenu(textView)
    }

    override fun onCreateContextMenu(
        menu: ContextMenu?,
        v: View?,
        menuInfo: ContextMenu.ContextMenuInfo?
    ) {
        menu?.add(0, v!!.id, 0, "Copy")
        //setting header title for menu
        menu?.setHeaderTitle("Copy Selected Text")

        var manager = getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
        val textView = v as TextView
        val clipData = ClipData.newPlainText("text", textView.text)
        manager.setPrimaryClip(clipData)
        super.onCreateContextMenu(menu, v, menuInfo)

    }
}

Conclusion

Wow! Great job! You just finished the Implement copy/paste in your TextView in Android. If you have any queries, feel free to connect us.

Get Solution Code

Best way of logging and debugging your android application