Android Notification Direct Reply

Filed Under: Android

Android Notification Direct Reply action lets us reply to the notification message, it’s very popular with chat notifications such as Whatsapp and Facebook messenger notification messages.

Android Nougat has introduced several new features. It offers some awesome features such as Inline Reply Actions and Bundled Notifications. In this tutorial, we’ll be implementing Inline Replies in our application

Android Notification Direct Reply

Inline Reply Actions (also known as Direct Replies) allows us to reply to messages from the notifications itself. It makes life easier by removing the need to open applications for providing input. Such features are commonly seen in messaging applications. Direct Replies uses a combination of Notification Actions and Remote Input. Remote Input API provides a mechanism to access the entered text from the notification in our application.

RemoteInput requires the following Strings as input.

  • Unique Key: This is used to correctly identify the text entered from the inline notification later on.
  • Label: This is displayed to the user as a hint text.

Let’s implement a basic application that triggers a Notification with Inline Reply set as the Action.

Android Notification Direct Reply Project Structure

Android Notification Direct Reply

Android Notification Direct Reply Code

The code for the activity_main.xml layout is given below.


<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="com.journaldev.directreplynotification.MainActivity">

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="BASIC INLINE REPLY NOTIFICATION"
        android:id="@+id/btn_basic_inline_reply"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/txt_inline_reply"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Replied text will be displayed here"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/btn_basic_inline_reply"
        android:layout_marginLeft="8dp"
        app:layout_constraintLeft_toLeftOf="parent"
        android:layout_marginRight="8dp"
        app:layout_constraintRight_toRightOf="parent" />

    <Button
        android:id="@+id/btn_inline_replies_with_history"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="INLINE REPLIES WITH HISTORY"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        android:layout_marginTop="8dp"
        app:layout_constraintTop_toBottomOf="@+id/txt_inline_reply" />

</android.support.constraint.ConstraintLayout>

Note: We’ll deal with the second Button later in the tutorial.

The code for the MainActivity.java class is given below.


package com.journaldev.directreplynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    String KEY_REPLY = "key_reply";
    public static final int NOTIFICATION_ID = 1;

    Button btnBasicInlineReply;
    TextView txtReplied;


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


        clearExistingNotifications();

        btnBasicInlineReply = (Button) findViewById(R.id.btn_basic_inline_reply);
        txtReplied = (TextView) findViewById(R.id.txt_inline_reply);
        btnBasicInlineReply.setOnClickListener(this);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_basic_inline_reply:

                //Create notification builder
                NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                        .setSmallIcon(android.R.drawable.stat_notify_chat)
                        .setContentTitle("Inline Reply Notification");

                String replyLabel = "Enter your reply here";

                //Initialise RemoteInput
                RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
                        .setLabel(replyLabel)
                        .build();

                //PendingIntent that restarts the current activity instance.
                Intent resultIntent = new Intent(this, MainActivity.class);
                resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                PendingIntent resultPendingIntent = PendingIntent.getActivity(this, 0, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);


                //Notification Action with RemoteInput instance added.
                NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
                        android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
                        .addRemoteInput(remoteInput)
                        .setAllowGeneratedReplies(true)
                        .build();

                //Notification.Action instance added to Notification Builder.
                builder.addAction(replyAction);

                Intent intent = new Intent(this, MainActivity.class);
                intent.putExtra("notificationId", NOTIFICATION_ID);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);


                builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

                //Create Notification.
                NotificationManager notificationManager =
                        (NotificationManager)
                                getSystemService(Context.NOTIFICATION_SERVICE);

                notificationManager.notify(NOTIFICATION_ID,
                        builder.build());
                break;

        }
    }

    private void clearExistingNotifications()
    {
        int notificationId = getIntent().getIntExtra("notificationId", 0);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.cancel(notificationId);
    }
}

Two Notification Actions namely “REPLY” and “DISMISS” are set on the notification. Clicking REPLY would trigger the Direct Reply feature. Running the above code on the emulator would give the following output.

Android Notification Direct Reply

In the above gif, clicking the send icon from the notification shows an indicator. This implies that the notification is waiting for an acknowledgment from the activity. We haven’t processed the replied text in our activity yet. The inline replied text is retrieved using the key set in the RemoteInput instance as shown in the updated code for MainActivity.java below.


package com.journaldev.directreplynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    String KEY_REPLY = "key_reply";
    public static final int NOTIFICATION_ID = 1;

    Button btnBasicInlineReply;
    TextView txtReplied;


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

        btnBasicInlineReply = (Button) findViewById(R.id.btn_basic_inline_reply);
        txtReplied = (TextView) findViewById(R.id.txt_inline_reply);
        btnBasicInlineReply.setOnClickListener(this);

        clearExistingNotifications()
    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        processInlineReply(intent);
        
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_basic_inline_reply:

                //Create notification builder
                NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                        .setSmallIcon(android.R.drawable.stat_notify_chat)
                        .setContentTitle("Inline Reply Notification");

                String replyLabel = "Enter your reply here";

                //Initialise RemoteInput
                RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
                        .setLabel(replyLabel)
                        .build();


                int randomRequestCode = new Random().nextInt(54325);

                //PendingIntent that restarts the current activity instance.
                Intent resultIntent = new Intent(this, MainActivity.class);
                //Set a unique request code for this pending intent
                PendingIntent resultPendingIntent = PendingIntent.getActivity(this, randomRequestCode, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);


                //Notification Action with RemoteInput instance added.
                NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
                        android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
                        .addRemoteInput(remoteInput)
                        .setAllowGeneratedReplies(true)
                        .build();

                //Notification.Action instance added to Notification Builder.
                builder.addAction(replyAction);

                Intent intent = new Intent(this, MainActivity.class);
                intent.putExtra("notificationId", NOTIFICATION_ID);
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
                PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);


                builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

                //Create Notification.
                NotificationManager notificationManager =
                        (NotificationManager)
                                getSystemService(Context.NOTIFICATION_SERVICE);

                notificationManager.notify(NOTIFICATION_ID,
                        builder.build());
                break;

        }
    }

    private void clearExistingNotifications() {
        int notificationId = getIntent().getIntExtra("notificationId", 0);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.cancel(notificationId);
    }

    private void processInlineReply(Intent intent) {
        Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);

        if (remoteInput != null) {
            String reply = remoteInput.getCharSequence(
                    KEY_REPLY).toString();

            //Set the inline reply text in the TextView
            txtReplied.setText("Reply is "+reply);


            //Update the notification to show that the reply was received.
            NotificationCompat.Builder repliedNotification =
                    new NotificationCompat.Builder(this)
                            .setSmallIcon(
                                    android.R.drawable.stat_notify_chat)
                            .setContentText("Inline Reply received");

            NotificationManager notificationManager =
                    (NotificationManager)
                            getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.notify(NOTIFICATION_ID,
                    repliedNotification.build());

        }
    }
}

In the above code, onNewIntent is invoked when the reply key is pressed(Thanks to the Intent flags SINGLE_TOP and CLEAR_TOP).

The method processInlineReply() is where we fetch the text entered in the notification and set it in the TextView. The notification is then updated (with the same NOTIFICATION_ID as when created) to get rid of the progress indicator and display that the reply was received.

In another world, instead of updating the notification we could have cancelled the notification by using the below snippet.


//Cancel notification
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.cancel(NOTIFICATION_ID);

//Remove this from the MainActivity.java
NotificationManager notificationManager =
                    (NotificationManager)
                            getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.notify(NOTIFICATION_ID,
                    repliedNotification.build());

The updated code above would give the following output in the emulator.
android notification inline reply example

Note: Multiple Inline Reply Actions can be added in a notification by adding multiple addAction(remoteInput) instances on the builder instance. Though it’s recommended to have just one inline reply action button per notification.

Notifications With Reply History

We can display previous inline responses in the Notification too with the help of the method setRemoteInputHistory.

In this application we’ll be storing the Charsequences returned in inline replies in the form of a LinkedList as shown below.


package com.journaldev.directreplynotification;

import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.RemoteInput;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    String KEY_REPLY = "key_reply";
    String KEY_REPLY_HISTORY = "key_reply_history";
    public static final int NOTIFICATION_ID = 1;

    Button btnBasicInlineReply, btnInlineReplyHistory;
    TextView txtReplied;

    private static List<CharSequence> responseHistory = new LinkedList<>();

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

        btnBasicInlineReply = (Button) findViewById(R.id.btn_basic_inline_reply);
        btnInlineReplyHistory = (Button) findViewById(R.id.btn_inline_replies_with_history);
        txtReplied = (TextView) findViewById(R.id.txt_inline_reply);
        btnBasicInlineReply.setOnClickListener(this);
        btnInlineReplyHistory.setOnClickListener(this);

        clearExistingNotifications();

    }

    @Override
    protected void onNewIntent(Intent intent) {
        super.onNewIntent(intent);
        processInlineReply(intent);

    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_basic_inline_reply:
                createInlineNotification();
                break;

            case R.id.btn_inline_replies_with_history:

                if (!responseHistory.isEmpty()) {
                    CharSequence[] history = new CharSequence[responseHistory.size()];
                    createInlineNotificationWithHistory(responseHistory.toArray(history));
                } else {
                    createInlineNotificationWithHistory(null);
                }
                break;


        }
    }

    private void clearExistingNotifications() {
        int notificationId = getIntent().getIntExtra("notificationId", 0);
        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        manager.cancel(notificationId);
    }

    private void processInlineReply(Intent intent) {

        Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);

        if (remoteInput != null) {
            CharSequence charSequence = remoteInput.getCharSequence(
                    KEY_REPLY);

            if (charSequence != null) {
                //Set the inline reply text in the TextView

                String reply = charSequence.toString();

                txtReplied.setText("Reply is " + reply);


                //Update the notification to show that the reply was received.
                NotificationCompat.Builder repliedNotification =
                        new NotificationCompat.Builder(this)
                                .setSmallIcon(
                                        android.R.drawable.stat_notify_chat)
                                .setContentText("Inline Reply received");

                NotificationManager notificationManager =
                        (NotificationManager)
                                getSystemService(Context.NOTIFICATION_SERVICE);
                notificationManager.notify(NOTIFICATION_ID,
                        repliedNotification.build());


                /**Uncomment the below code to cancel the notification.
                 * Comment the above code too.
                 * **/
            /*NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
            manager.cancel(NOTIFICATION_ID);*/

            } else {

                String reply = remoteInput.getCharSequence(KEY_REPLY_HISTORY).toString();
                responseHistory.add(0, reply);
                if (!responseHistory.isEmpty()) {
                    CharSequence[] history = new CharSequence[responseHistory.size()];
                    createInlineNotificationWithHistory(responseHistory.toArray(history));
                } else {
                    createInlineNotificationWithHistory(null);
                }

            }

        }
    }

    private void createInlineNotification() {

        //Create notification builder
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(android.R.drawable.stat_notify_chat)
                .setContentTitle("Inline Reply Notification");

        String replyLabel = "Enter your reply here";

        //Initialise RemoteInput
        RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY)
                .setLabel(replyLabel)
                .build();


        int randomRequestCode = new Random().nextInt(54325);

        //PendingIntent that restarts the current activity instance.
        Intent resultIntent = new Intent(this, MainActivity.class);
        resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        //Set a unique request code for this pending intent
        PendingIntent resultPendingIntent = PendingIntent.getActivity(this, randomRequestCode, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);


        //Notification Action with RemoteInput instance added.
        NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
                android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
                .addRemoteInput(remoteInput)
                .setAllowGeneratedReplies(true)
                .build();

        //Notification.Action instance added to Notification Builder.
        builder.addAction(replyAction);

        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra("notificationId", NOTIFICATION_ID);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);


        builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

        //Create Notification.
        NotificationManager notificationManager =
                (NotificationManager)
                        getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(NOTIFICATION_ID,
                builder.build());

    }

    private void createInlineNotificationWithHistory(CharSequence[] history) {

        //Create notification builder
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setSmallIcon(android.R.drawable.stat_notify_chat)
                .setContentTitle("Inline Reply Notification With History");

        String replyLabel = "Enter your reply here";

        //Initialise RemoteInput
        RemoteInput remoteInput = new RemoteInput.Builder(KEY_REPLY_HISTORY)
                .setLabel(replyLabel)
                .build();


        int randomRequestCode = new Random().nextInt(54325);

        //PendingIntent that restarts the current activity instance.
        Intent resultIntent = new Intent(this, MainActivity.class);
        resultIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        //Set a unique request code for this pending intent
        PendingIntent resultPendingIntent = PendingIntent.getActivity(this, randomRequestCode, resultIntent, PendingIntent.FLAG_UPDATE_CURRENT);


        //Notification Action with RemoteInput instance added.
        NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
                android.R.drawable.sym_action_chat, "REPLY", resultPendingIntent)
                .addRemoteInput(remoteInput)
                .setAllowGeneratedReplies(true)
                .build();

        //Notification.Action instance added to Notification Builder.
        builder.addAction(replyAction);

        Intent intent = new Intent(this, MainActivity.class);
        intent.putExtra("notificationId", NOTIFICATION_ID);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
        PendingIntent dismissIntent = PendingIntent.getActivity(getBaseContext(), 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);


        builder.addAction(android.R.drawable.ic_menu_close_clear_cancel, "DISMISS", dismissIntent);

        if (history != null) {
            builder.setRemoteInputHistory(history);
        }


        //Create Notification.
        NotificationManager notificationManager =
                (NotificationManager)
                        getSystemService(Context.NOTIFICATION_SERVICE);

        notificationManager.notify(NOTIFICATION_ID,
                builder.build());

    }


}

We’ve added a new Button to handle Inline Replies With History.
In the above code, responseHistory holds the history of replies.
The following snippet is used to set the previous inline replies in the notification.


if (history != null) {
            builder.setRemoteInputHistory(history);
        }

The else part of method processInlineReply() updates the current notification with the new history of inline replies as shown below:


else {

                String reply = remoteInput.getCharSequence(KEY_REPLY_HISTORY).toString();
                responseHistory.add(0, reply);
                if (!responseHistory.isEmpty()) {
                    CharSequence[] history = new CharSequence[responseHistory.size()];
                    createInlineNotificationWithHistory(responseHistory.toArray(history));
                } else {
                    createInlineNotificationWithHistory(null);
                }

            }

responseHistory.add(0, reply); adds the latest inline reply in the history of replies.

The output that the above code would give is:
android notification direct reply history

Note: The above concept is for demonstration purposes only. You can tweak it according to your requirements.

Inline Replies do not work on versions prior to Android N. So to make the application functional on them, you can add a Basic EditText in the activity.

This brings an end to this tutorial. You can download the final Android DirectReplyNotifications Project from the link below.

Comments

  1. Elito V. Circa says:

    add
    onNewIntent(getIntent());

    inside

    protected void onCreate(Bundle savedInstanceState) {
    to receive the reply

  2. Milen says:

    responseHistory.add(0, reply);

    if (!responseHistory.isEmpty()) { …

    … Really? There also is an “else” in there belonging to that :if:…

  3. tomer says:

    Input doesn’t work, simply exit notification when clicked on Reply
    Is it something to do with software version or something that should be define in the AndroidManifest (I googled it but still confuse) ?

  4. Amit says:

    Is it possible to reply to whatsapp notifications directly?

    1. Anupam says:

      Yes. On Android N and above.

  5. I simply wanted to write down a quick word to say thanks to you for those wonderful tips and hints you are showing on this site.

    1. Anupam says:

      Thanks, Anudeep. Glad it helped you.

Leave a Reply

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

close
Generic selectors
Exact matches only
Search in title
Search in content
Search in posts
Search in pages