Apps für Android programmieren leicht gemacht!
Direct Reply

Direct Reply

Direct Reply kennen wir bereits von namenhaften Apps, wie zum Beispiel Whatsapp.
Unter Android 7.0 Nougat ändert sich, dass die Funktion nun auch von Android nativ unterstützt wird.

Unter Direct Reply versteht man, im Gegensatz zu der Implementierung in Whatsapp, kein Pop-Up, in welchem man die Antwort eintippen kann.
Vielmehr handelt es sich um ein anzeigbares Textfeld direkt in der Benachrichtigungsleiste von Android (siehe Titelbild).

Programmieren wir eine Direct Reply Funktion ein, so wird ein Antworten Knopf zu unserer Benachrichtigung hinzugefügt.
Klicken wir auf diesen Knopf wird er durch ein Textfeld ersetzt, in das wir unsere Antwort eintippen und absenden können.
Der eingegebene Text wird schließlich an eine von uns vorher definierte Klasse gesendet und kann dort je nach Belieben verwendet werden.

Wir wollen uns heute einmal mit der Thematik beschäftigen und auch wenn Android 7.0 Nougat sein Roll-Out noch nicht gefeiert hat, so können wir unsere Apps schon jetzt anpassen, per Emulator testen und sind ab dem Roll-Out sofort mit unserer App auf dem neuesten Stand.

 

Erklärung:

Ich habe ja bereits angeschnitten, wie die neue Funktion funktioniert.
Hier nochmal ein wenig auf anderer Ebene eine tiefgreifendere Erklärung.

So weit ich zurückdenken kann gab es in Android bereits Benachrichtigungen und das schon bevor Google das Betriebssystem Android gekauft hat.

Seit der API Version 20 ist es dann möglich gewesen Aktionen zu den erstellten Benachrichtigungen hinzu zu fügen.
Zu diesen Aktionen gehören zum Beispiel auch die Button, die wir an einer Benachrichtigung anfügen können. Whatsapp nutzt diese Aktionen, um seine Pop-Ups anzeigen zu können.

Der neueste Schritt war es in der API Version 24 Direct Reply einzuführen.
Wir erstellen hierzu einen RemoteInput, also eine entfernte Eingabe, die dann an unsere App übergeben wird.
Dieses RemoteInput fügen wir dann in eine Aktion hinzu und diese wiederum an eine ganz normale Benachrichtigung.

Über den RemoteInput können wir z. B. definieren, wie der Platzhalter für das Textfeld aussehen soll und an welche Klasse die Eingabe des Textfeldes gesendet werden soll.
Über die Aktion definieren wir einen Button, dessen Text, dessen Icon und fügen den RemoteInput als Ausführung hinzu.
Schlussendlich nutzen wir die Benachrichtigung wiederum wie eine ganz normale Benachrichtigung, fügen allerdings zusätzlich noch unsere Aktion hinzu. – Eine Anleitung zu normalen Benachrichtigungen findet ihr hier: Benachrichtigungen

Damit sowohl unsere Benachrichtigung, als auch unsere App später weiß, um welches Textfeld es sich handelte müssen wir natürlich noch einen Namen übergeben.
Es wäre theoretisch denkbar, dass wir zwei Direct Reply in eine Benachrichtigung einbauen (z. B. private oder öffentliche Antwort in Gruppenchats). In diesem Fall können wir über unterschiedliche RemoteInput Namen herausfinden wie geantwortet wurde.

 

Vorarbeit:

Für unseren Artikel dachte ich mir, dass wir vielleicht einen Button in unsere App einbauen könnten, um eine Benachrichtigung zu starten, so können wir den Code ersten ganz gut schachteln und zweitens die Direct Reply Funktion mehrfach und unkompliziert testen.
Ich denke mal das Layout muss ich nicht extra erläutern. Hier wird einfach ein TextView und ein Button (ID: button) angezeigt.

<?xml version="1.0" encoding="utf-8"?>
<!--
  ~ Copyright 2016 www.droid-lernen.de
  ~
  ~ Licensed under the Apache License, Version 2.0 (the „License“);
  ~ you may not use this file except in compliance with the License.
  ~ You may obtain a copy of the License at
  ~
  ~ http://www.apache.org/licenses/LICENSE-2.0
  ~
  ~ Unless required by applicable law or agreed to in writing, software
  ~ distributed under the License is distributed on an „AS IS“ BASIS,
  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  ~ See the License for the specific language governing permissions and
  ~ limitations under the License.
  -->

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="de.droid_lernen.direktreply.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Droid-Lernen.de" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"
        android:id="@+id/button"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

 

Die Funktion „onCreate“ unserer Hauptklasse erweitern wir durch:

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

    Button btn = (Button) findViewById(R.id.button);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            starteBenachrichtigung();
        }
    });
}

Durch diese Anpassung ist es möglich bei einem Klick auf den eben gesetzten Button eine Funktion auszuführen. Wir werden später noch die passende Funktion „starteBenachrichtigung“ erstellen.

 

Benachrichtigung anzeigen:

Diese Funktion wird das Herzstück unseres Artikels. Sie zeigt die Benachrichtigung an und fügt, sofern die API Version ausreicht, Direct Reply hinzu.

In Zeile 5-17 definieren wir erst einmal die anzuzeigenden Texte der Benachrichtigung.

Ab Zeile 20 wird es jetzt allerdings interessant.
Wir prüfen hier, ob die Version des ausführenden Android Gerätes ausreicht, um Direct Reply zu unterstützen. Solle die Version nicht ausreifen, dann führen wir die Zeilen 50-56 aus. Dieser Code ist äquivalent zu diesem Artikel: Benachrichtigungen

Zeile 22-37 erkläre ist nach dem kompletten Code etwas genauer.

// Beispielfunktion, um eine Benachrichtigung mit Direct Reply ausgeben zu können.
// Reicht die Android Version nicht für eine Benachrichtigung mit Direct Reply, dann gib sie ohne Direct Reply aus.
public void starteBenachrichtigung(){

    Notification newMessageNotification;

    // Dies ist der Platzhaltertext, welcher im Eingabefeld der Nachricht angezeigt werden soll.
    String replyPlaceholder = "Nachricht";

    // Dies ist der Text, den der Antworten Button in der Benachrichtigung anzeigen soll.
    String replayLabel = "Antworten";

    // Titel der Benachrichtigung.
    String benachrichtigungTitel = "Titel";

    // Text der Benachrichtigung.
    String benachrichtigungText = "Benachrichtigung Text";

    // Prüfe, ob die Android Version ausreicht, um Direct Reply zu unterstützen.
    if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {

        // Erstelle ein Intent, um die Antwort vom Direct Reply an eine bestimmte Klasse (hier MainActivity) zu senden.
        final Intent notificationIntent = new Intent(this, MainActivity.class);
        notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
        final PendingIntent replyPendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT );
        
        // Da die Android Version ausreicht erstellen wir ein Textfeld.
        RemoteInput remoteInput = new RemoteInput.Builder(remoteInput_name)
                .setLabel(replyPlaceholder)
                .build();

        // Das Textfeld fügen wir zu einer Aktion hinzu.
        Notification.Action action =
                new Notification.Action.Builder(R.mipmap.ic_launcher,
                        replayLabel, replyPendingIntent)
                        .addRemoteInput(remoteInput)
                        .build();

        // Nun erstellen wir eine Benachrichtigung, füge die eben erstelle Aktion und damit das Textfeld zu der benachrichtung hinzu.
        newMessageNotification =
                new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle(benachrichtigungTitel)
                        .setContentText(benachrichtigungText)
                        .addAction(action)
                        .build();

    }else{

        // Die die Android Version nicht für ein Direct Reply ausreicht werden wir nur eine Benachrichtigung ausgeben, ohne Direct Reply.
        newMessageNotification =
                new Notification.Builder(this)
                        .setSmallIcon(R.mipmap.ic_launcher)
                        .setContentTitle(benachrichtigungTitel)
                        .setContentText(benachrichtigungText)
                        .build();

    }

    // Zeige die Benachrichtigung schlussendlich an.
    NotificationManager notificationManager =  (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(666, newMessageNotification);
}

 

Kommen wir zur näheren Erläuterung des relevanten Stück Codes.
Um den geantworteten Text von unserem RemoteInput erhalten zu können müssen wir eine Klasse angeben, an die die Antwort gesendet wird.
Die Klasse muss in der AndroidManifest.xml Datei aufgeführt sein. Es kann sich um auch um eine dort aufgelistete Activity handeln, wichtig ist, dass die Klasse die Funktion „onNewIntent“ enthält. Warum erkläre ich später.
Wir beschreiben die erhaltende Klasse mit einem Intent (Zeile 23).
In den Nachfolgenden Zeilen (24-25) sagen wir, wie die Klasse geöffnet werden soll. In unserem Fall lassen wir die Klasse sich öffnen und nach Bearbeitung schließt sich die Klasse wieder. Handelt es sich wie in diesem Artikel um die hauptklasse schließt sich die App natürlich nicht, wenn die Bearbeitung abgeschlossen ist.

Nun erstellen wir einen RemoteInput und übergeben ihm die Information, wie der Platzhalter des Textfeldes lauten soll. Außerdem sagen wir, wie das Textfeld heißen soll. Durch diesen Namen können wir später herausfinden, was für ein Textfeld es war. Später definieren wir den Namen als „Schnellantwort“ und prüfen darauf.
Haben wir beispielsweise mehrere Benachrichtigungen, von unterschiedlichen Absendern, am laufen könnten wir hinter den Textfeldnamen noch die Handynummer des Absenders hängen und beim erhalt des Intents prüfen, wie der Name anfängt und die Nummer dann wieder vom Namen trennen.
Heißt das Textfeld nun „Schnellantwort“ und der Absender hat die Handynummer „+4912345678“ benutzen wir den Namen „Schnellantwort_+4912345678“ und prüfen später, ob der Name mit „Schnellantwort_“ beginnt und streichen das heraus.

Zeile 33-37 hat den Sinn, der Benachrichtigung zu sagen, welcher Intent genutzt werden soll (welche Klasse die Eingabe erhalten soll), was der Icon des Buttons sein soll und wie der Text des Buttons lautet.

In Zeile 40-46 fügen wir alles zu unserer Benachrichtigung hinzu.

 

Antwort erhalten:

Wie bereits mehrfach angesprochen benötigen wir einen Namen für das Textfeld.

public static final String remoteInput_name = "Schnellantwort";

 
Wir reagieren auf die Eingabe, indem wir die Funktion „onNewIntent“ anpassen:

// Reagiere auf einen Direct Reply und gibt die Nachricht als Toast aus.
@Override
protected void onNewIntent(Intent intent) {

    // Lese Antwort aus.
    String result = (String) getAntwortText(intent);

    // Gib die Nachricht testweise als Toast aus.
    Context context = getApplicationContext();
    int duration = Toast.LENGTH_SHORT;
    Toast toast = Toast.makeText(context, result, duration);
    toast.show();

    // Entferne die soeben gemachte Benachrichtigung wieder. Die ID der Benachrichtigung ist 666.
    NotificationManager mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    mNotificationManager.cancel(666);
}

 
Ausgelesen wird die Nachricht dann von dieser Funktion.

// Lese die Nachricht aus dem erhaltenen Inten aus und gebe sie zurück.
private CharSequence getAntwortText(Intent intent) {
    Bundle remoteInput = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT_WATCH) {
        remoteInput = RemoteInput.getResultsFromIntent(intent);
        if (remoteInput != null) {
            return remoteInput.getCharSequence(remoteInput_name);
        }
    }
    return null;
}

 

Direct Reply - Source Code


Der komplette Source Code zu diesem Artikel im Download.

DownloadLizenzbedingungen

Marvin

Ich bin 23 Jahre jung und studiere zurzeit Wirtschaftsinformatik an der Georg-August-Universität in Göttingen. Ich bin ein Mensch, der sich neben der Programmierung noch für tausend andere Dinge interessiert, die mal mehr und mal weniger verrückt sind. Vor allem aber bin ich Feuer und Flamme mit der Programmierung von eigenen kleinen Apps und Programmen, die mein Leben bereichern.

5 Kommentare

*Pflichtfeld

    • Hallo Heinrich,

      entschuldige die späte Antwort. Das wäre ja perfekt, wenn der Fehler behoben ist.
      Wärst du so freundlich, vielleicht für weitere Leser, einmal zu erläutern, was genau das Problem war?
      Ich würde das dann in den Artikel mit aufnehmen.

      Gruß,
      Marvin

    • Das Problem lag darin, dass es keine Funktion gab durch die das Programm merkt, dass der knöpf gedrückt wurde. Das ganze lässt sich mit folgenden Zeilen ändern:
      Button button = (Button) findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
      starteBenachrichtigung();
      }
      });