Blog

Das Laravel Framework bietet eine sehr schöne Möglichkeit, Kanäle zu abonnieren und Events zu empfangen. In diesem Artikel werden wir untersuchen, wie wir von der Laravel-Webanwendung übertragene Events in Android-Anwendungen empfangen können, und zwar unter Verwendung von WebSockets.

Laravel Echo Web & Android App Beispiel

Im ersten Abschnitt dieses Artikels wird die Einrichtung der Laravel-Webanwendung beschrieben und im zweiten Abschnitt wird beschrieben, wie wir Socket IO in eine Android-Anwendung integrieren können.

Wenn Sie am Android-Teil des Artikels interessiert sind, können Sie den Abschnitt über die Web-Einrichtung überspringen. Am Ende des Artikels befinden sich die Links zum Quellcode beider Anwendungen, sodass Sie das gesamte Setup testen können.

Laravel Web-Anwendungs-Setup

Der Hauptzweck der Laravel-Webanwendung ist die Erstellung und das Broadcasten von Messages an verfügbare Clients. In unserer Webanwendung werden wir Redis Broadcaster mit Socket.IO Server verwenden. In diesem Abschnitt zeigen wir alle Schritte, die für die Laravel Webanwendung erforderlich sind, um gesendete Messages über WebSocket zu broadcasten.

Der Redis-Broadcaster wird Messages von unserem MessagesController (den wir später erstellen werden) mit Hilfe des pub/sub-Features broadcasten. Wir werden ihn mit einem Socket IO-Server koppeln, sodass wir Messages von Redis empfangen und an Socket IO-Kanäle senden können.

Als allererstes müssen wir also Laravel Echo installieren:

npm install --save laravel-echo

und Redis, über den composer:

composer require predis/predis

Vergessen Sie nicht, Redis als broadcast driver in der .env-Datei festzulegen:

BROADCAST_DRIVER=redis

Und geben Sie Redis-Eigenschaften an:

REDIS_CLIENT=predis
REDIS_PREFIX=""

Als nächstes werden wir den Redis-Broadcaster mit dem Socket.IO-Server koppeln. Da wir Messages in einer Web-Anwendung (und auch in einer Android-Anwendung) empfangen und senden wollen, müssen wir die entsprechende Javascript-Bibliothek installieren:

npm install — save socket.io-client 

Nach der Installation des Socket.IO Javascript-Clients müssen wir das globale Echo-Objekt mit Socket.IO als Broadcaster instanziieren (in resources/js/bootstrap.js).

import Echo from 'laravel-echo';

window.io = require('socket.io-client');

window.Echo = new Echo({
    broadcaster: 'socket.io',
    host: window.location.hostname + ':6001'
});

Nachdem wir das Echo-Objekt angegeben haben, müssen wir den folgenden Befehl ausführen, um es in unsere app.js (die wir später verwenden werden) zum Listening von Echo-Ereignissen in unserer Webanwendung aufzunehmen:

npm install
npm run dev

Nun stehen wir kurz vor dem Abschluss des Konfigurationsprozesses der Laravel dependencies. Der allerletzte Schritt ist die Installation des Socket.IO-Servers. Da Laravel nicht mit einem Socket.IO-Server ausgeliefert wird, werden wir den von der Community verwalteten Socket.IO-Server verwenden: https://github.com/tlaverdure/laravel-echo-serverUm den laravel-echo-server zu installieren, müssen wir den folgenden Befehl ausführen:

npm install -g laravel-echo-server

Nach der Installation müssen wir den laravel-echo-server initialisieren, indem wir den folgenden Befehl ausführen:

laravel-echo-server init

und wenn die Konfiguration des Echoservers abgeschlossen ist, können wir ihn durch Ausführen des folgenden Befehls starten:

laravel-echo-server start

Wenn der Server erfolgreich gestartet wurde, erhalten Sie die folgende Ausgabe:

Mit dem Abschluss der Installation des laravel-echo-server haben wir auf unserer Laravel-Webanwendungsseite alles vorkonfiguriert, was wichtig ist, um Messages über Laravel Echo zu versenden. Im nächsten Schritt werden wir also einen Controller erstellen, der Laravel-Echo-Messages versenden wird.Bevor wir einen Controller erstellen, der unsere Messages sendet, wollen wir ein wenig erklären, wie diese Messages gesendet werden können.

Events, die von der Laravel-App gesendet werden, werden über Channel übertragen, die öffentlich oder privat sein können. In unserem Beispiel werden wir einen öffentlichen Channel benutzen, aber wenn Sie verhindern wollen, dass unautorisierte Benutzer die Channel-Events abhören, können Sie sich für einen privaten Channel anmelden. Lassen Sie uns nun ein Event mit dem Namen MessageCreated (im Ordner app/Events) erstellen, das gesendet wird, wenn wir eine Laravel-Echo-Message an unsere Subscriber senden wollen (Web-App und Android-App):

<?php 

namespace App\Events;

use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class MessageCreated implements ShouldBroadcast
{
    public $message;

    public function __construct($message) 
    {
        $this->message = $message;
    }

    public function broadcastOn()
    {
        return new Channel('messages');
    }
}

Im Konstruktor geben wir die Message an, die an alle Clients gesendet werden soll, während die Methode broadcastOn den Channel-Namen angibt, der diese Message empfangen soll.Im nächsten Schritt erstellen wir schließlich unseren MessagesController, der das zuvor von uns erstellte MessageCreated-Ereignis verwendet.

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use App\Events\MessageCreated;

class MessagesController extends Controller
{
    protected function index() 
    {
        return view('messages');
    }

    protected function sendMessage(Request $request)
    {
        $message = $request->message;
        broadcast(new MessageCreated($message));
        return response()->json([
            'status' => 'message is sent successfuly!'
        ]);
    }
} 

Wir sollten die Route zur Methode sendMessage() in web.php hinzufügen.

Route::post('messages', 'MessagesController@sendMessage');

Um dem Benutzer ein Formular anzuzeigen, in dem er die Nachricht angeben kann, die an Clients gesendet wird, müssen wir eine entsprechende View (messages.blade.php) erstellen.

<form id="newMessageForm">
  <div class="form-group">
      <label for="messageInput">Message</label>
      <input type="text" class="form-control" id="messageInput" placeholder="Enter some message">
  </div>
  <button id="submitButton" type="submit" class="btn btn-primary">Submit</button>
  <br/><br/>
  <div class="form-group">
    <label for="messagesList">Received messages:</label>
    <ul id="messagesList" class="list-group">
    </ul>
  </div>
</form>

Unser Formular besteht aus einem Eingabefeld, in das der Benutzer eine Message eingeben kann, einem Submit-Button und einem Container für empfangene Messages (da die Webanwendung auch ein Client ist, der Messages empfangen kann).In der Message-Ansicht müssen wir einige Methoden zum Listening von Echo-Events und Methoden zum Senden von Messages hinzufügen:

    <script src="//{{ Request::getHost() }}:6001/socket.io/socket.io.js"></script>
    <script src="{{ asset('/js/app.js') }}"></script>
    <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>   
    <script>
      $("#newMessageForm").submit(function(event) {
        event.preventDefault();
        const message = $("#messageInput").val();
        if (message === "") {
          alert("Please add some message!");
          return;
        }
        postMessageToBackend(message);
      });
      
      function postMessageToBackend(message) {
        $.post("messages", {
          "_token": "{{ csrf_token() }}",
          "message": message 
        }).fail(function() {
          alert("Error occurred during sending the data. Please retry again later.");
        })
      }

      window.Echo.channel('messages').listen('MessageCreated', (e) => {
        addMessageToList(e.message);
      });
      
      function addMessageToList(message) {
        const newLi = `<li class="list-group-item">${message}</li>`;
        $("#messagesList").prepend(newLi);
      }
    </script> 

Ein sehr wichtiger Teil im Code von oben ist die Zeile

window.Echo.channel(‘messages’)

wo unsere Webanwendung im Messages-Channel auf das Event MessageCreated wartet. Die Methode postMessageToBackend() führt einen asynchrone POST-Request aus und erzeugt einen neuen Message-Broadcast. Sie wird aufgerufen, wenn der Benutzer auf die Schaltfläche submit klickt.Route zum MessagesController, angegeben in web.php:

Route::get(‘messages’, ‘MessagesController@index’);

Jetzt ist es an der Zeit, unsere Webanwendung zu starten:

php artisan serve

und über die folgende URL können wir unser Messages-Formular ansehen:

http://localhost:8000/messages

Nun sind wir mit der einfachen Laravel-Anwendung fertig, sodass wir mit der Implementierung der Android-Anwendung beginnen können.

Einrichtung des Android Socket.IO-Clients

Die Android-Anwendung stellt die Verbindung mit dem Laravel Echo-Server her, empfängt die gesendeten Messages und zeigt sie auf dem Bildschirm an.Der Hauptbildschirm wird ein einfaches Layout haben – nur eine TextView, die alle empfangenen Messages anzeigt:

<?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/text_message"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        tools:text="Message"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout> 

Um die Socket-Verbindung zu unserem Laravel Echo-Server herzustellen, müssen wir eine entsprechende Library in das Android-Projekt importieren:

implementation ‘com.github.MrBin99:LaravelEchoAndroid:1.03’

Diese Library ermöglicht es uns, uns mit dem Socket-Server zu verbinden und vom Server gesendete Messages zu empfangen. Wir werden eine BaseActivity erstellen, eine Klasse, die unsere Basisklasse sein wird und alle erforderlichen Funktionen für die Verbindung mit dem Echo-Server enthält:

const val SERVER_URL = "http://10.0.2.2:6001"
const val CHANNEL_MESSAGES = "messages"
const val EVENT_MESSAGE_CREATED = "MessageCreated"
const val TAG = "msg"

open class BaseSocketActivity : AppCompatActivity() {

    private var _receivedEvent = MutableLiveData<Any>()
    var receivedEvent = _receivedEvent

    private var echo: Echo? = null

    protected fun connectToSocket() {
        val options = EchoOptions()
        options.host = SERVER_URL

        echo = Echo(options)
        echo?.connect(object : EchoCallback {
            override fun call(vararg args: Any?) {
                log("successful connect")
                listenForEvents()
            }
        }, object : EchoCallback {
            override fun call(vararg args: Any?) {
                log("error while connecting: " + args.toString())
            }
        })
    }

    protected fun listenForEvents() {
        echo?.let {
            it.channel(CHANNEL_MESSAGES)
                .listen(EVENT_MESSAGE_CREATED) {
                    val newEvent = MessageCreated.parseFrom(it)
                    displayNewEvent(newEvent)
                }
        }
    }

    protected fun disconnectFromSocket() {
        echo?.disconnect()
    }

    private fun log(message: String) {
        Log.i(TAG, message)
    }

    private fun displayNewEvent(event: MessageCreated?) {
        log("new event " + event?.message)
        _receivedEvent.postValue(event)
    }
}

Im obigen Code können wir sehen, dass wir ein paar socket-bezogene Methoden haben. Die Methoden connectToSocket() und disconnectFromSocket() sind Methoden, die für das Verbinden oder Trennen vom WebSocket verantwortlich sind. Methode listenForEvents() ist eine Methode, die aufgerufen wird, nachdem wir eine erfolgreiche Socket-Verbindung hergestellt haben, und deren Hauptaufgabe darin besteht, nach Ereignissen zu „listen“, die von unserem Socket-Backend übertragen werden.

Um die Socket-Übertragung zu empfangen, müssen wir denselben ‚channel‘ und dieselbe ‚message‘ angeben, wie sie in der Backend-Konfiguration definiert ist. Nachdem wir eine Socket-Broadcast-Message empfangen haben, müssen wir ihren Inhalt parsen. Dazu verwenden wir die parseFrom()-Methode aus einem companion Objekt der Klasse MessageCreated.

class MessageCreated(
    val message: String
) {

    companion object {
        fun parseFrom(value: Array<Any>): MessageCreated? {
            val messageData = value[1] as org.json.JSONObject
            try {
                return Gson().fromJson(messageData.toString(), MessageCreated::class.java)
            } catch (e: Exception) {
                e.printStackTrace()
            }
            return null
        }
    }

}

Um die empfangene Nachricht zu parsen, werden wir die Gson-Library verwenden, sodass wir sie in unsere Datei app/build.gradle importieren müssen:

implementation ‘com.google.code.gson:gson:2.8.6’

Wir müssen auch einen geeigneten Speicherort für das Repository, von dem diese Library geholt wird, in der Datei build.gradle auf Projektebene hinzufügen:

allprojects {
    repositories {
        maven { url 'https://jitpack.io' }
    }
}

Nachdem wir eine BaseSocketActivity erstellt haben, können wir unsere MainActivity so erstellen, dass sie von BaseSocketActivity erbt, und die entsprechenden Methoden zum Herstellen und Zerstören der Socket-Verbindung aufrufen:

class MainActivity : BaseSocketActivity() {

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

    override fun onDestroy() {
        super.onDestroy()
        disconnectFromSocket()
    }

    private fun initLiveDataListener() {
        receivedEvent.observe(this, Observer {
            displayEventData(it)
        })
    }

    private fun displayEventData(event: Any) {
        if (event is MessageCreated) {
            text_message.apply {
                val newText = event.message + "\n" + this.text.toString()
                text = newText
            }
        }
    }
}

Da der Klartext-HTTP-Verkehr nicht explizit erlaubt ist, müssen wir dies in der Netzwerksicherheitskonfiguration (Datei res/xml/network_security_config.xml) angeben:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="true">
        <domain includeSubdomains="true">10.0.2.2</domain>
    </domain-config>
</network-security-config> 

und fügen diese Datei als Application-Tag-Eigenschaft unseres Manifests ein:

android:networkSecurityConfig="@xml/network_security_config"

Vergessen Sie schließlich nicht, die Internet-Permission im Manifest festzulegen:

<uses-permission android:name="android.permission.INTERNET" />

Jetzt können Sie alles in einem Android-Emulator testen. Es ist wichtig, die Socket-Backend-URL als anzugeben:

http://10.0.2.2:6001

damit wir uns mit unserem Socket IO-Server auf unserem lokalen Computer verbinden können.

Links zu Beispielcode für Web-/Android-Anwendungen:

Herzlichen Glückwunsch, Sie können jetzt das Senden von Messages von einer Web-Anwendung an eine Android-Anwendung mit Laravel Echo testen!

Wenn Sie Fragen zu diesem oder einem anderen Tech-Thema haben, können Sie sich jederzeit bei uns melden.