How to build a Complete Messenger in 10 minutes using Laravel, Retrofit and MQTT CHAT Android Library.

MQTT CHAT
8 min readSep 10, 2022

--

In this post I will show you step by step haw to build a complete Messenger in android using MQTT CHAT Library v4.2.0.

Server Side (PHP)

In server side we will use LARAVEL PHP Framework to build API and to connect to Mysql Database. So let’s begin by creating new Laravel application using PHP Composer tool.

composer create-project laravel/laravel android-tuto

Then we add MQTT CHAT PHP SDK as dependency using composer.

composer require med_aboub/mqttchat-php-sdk -W

Ok, Now we will create our first controller named UsersController. It will be used to add new Users to MQTT-CHAT, to Mysql Database and also for users authentifications afterwards.

php artisan make:controller UsersController

Note that we use DB transaction in function add() to be sure to add in the same time the new user to database using Users Model and to MQTT-CHAT using MQTT-CHAT PHP SDK. If one of two operations fail we rollback entire transaction and return HTTP server error 500.

class UsersController extends Controller
{

public function add(Request $request){
try{
DB::beginTransaction();
$post=json_decode($request->getContent(),true);
$user= \App\Models\Users::create($post);
$users=new \telifoun\mqttchat\users();
$post_array=array("userid"=>$user["userid"],
"name"=>$user["name"],
"surname"=>$user["surname"],
"profile_link"=>"",
"avatar_link"=>"",
"gender"=>0);
$result=$users->add($post_array);
if($result["ok"]){
DB::commit();
return response()->json(["ok"=>true,"response"=>$result["data"]["userid"]]);
}else{
DB::rollBack();
return response()->json($result["error"],500);
}
}catch(\Exception $ex){
DB::rollBack();
return response()->json($ex->getMessage(),500);
}
}
public function login(Request $request){
try{
$user=\App\Models\Users::where("email","=",$request->get("email"))
->where("password","=",$request->get("password"))
->first();
if($user){
return response()->json(["ok"=>true,"response"=>$user->toArray()]);
}else{
return response()->json(["ok"=>false,"error"=>"Invalid Credentails"]);
}

} catch (Exception $ex) {
return response()->json($ex->getMessage(),500);
}
}
}

d’ont forget to add routes in routes/api.php for the two controller functions add() and login().

Route::post('/users',[App\Http\Controllers\UsersController::class,'add']);
Route::get('/login',[App\Http\Controllers\UsersController::class,'login']);

Users Model :

class Users extends Model
{
use HasFactory;
protected $table = 'users';
protected $primaryKey = 'userid';
public $timestamps = false;
protected $fillable = [
'name',
'surname',
'email',
'password'
];
}

Users table:

CREATE TABLE `users` (
`userid` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`surname` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`email` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`password` varchar(45) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`userid`)
)

Android APPLICATION

Project SETUP

Create a new project in Android Studio from File ⇒ New Project by filling the required details.

As we need to make network requests, we need to add INTERNET permission in AndroidManifest.xml.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.test.test123">
<application
android:name=".mApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".activity.RegisterActivity" />
<activity android:name=".activity.MainActivity" />
<activity android:name=".activity.LoginActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

Open app build.gradle and add MQTT CHAT version 4.2.0 library dependencies. There are two sub-libraries: mqttchat-core which contains MQTT CHAT (core) and mqttchat-gui which contains all graphical interfaces (ui). We will use also Retrofit library for HTTP requests.

Please note that, you should enable multidex support and databinding in android defaultConfig section like below:

android {
compileSdk 32

defaultConfig {
applicationId "com.app.tuto"
minSdk 16
targetSdk 32
versionCode 1
versionName "1.0"
multiDexEnabled true

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildFeatures{
dataBinding true
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}

dependencies {

implementation 'com.android.support:appcompat-v7:28.0.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
implementation 'com.telifoun.mqttchat:mqttchat-core:4.2.0'
implementation 'com.telifoun.mqttchat:mqttchat-gui:4.2.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'com.squareup.okhttp3:logging-interceptor:3.9.0'
}

To be able to access the MQTT CHAT libraries, you must add MQTT CHAT Artifactory repository to the list of Maven repositories in your project top level build.gradle file.

allprojects {
repositories {
maven {
url "https://mqttchat.jfrog.io/artifactory/libs-release-local"
credentials {
username = "mqttchat"
password = "telifoun"
}
}
}
}

Tuto android application Structure consists of 4 packages : api (Retrofit calls), models (data objetcs), network (Mapping and response objects) and ui (Activities, Fragments, Adapters etc ..)

1- Ceate User class in models package like below:

public class User  {

@SerializedName("userid")
@Expose(serialize = false, deserialize = true)
private int userid;

@SerializedName("name")
@Expose
private String name;

@SerializedName("surname")
@Expose
private String surName;

@SerializedName("mail")
@Expose
private String email;

@SerializedName("password")
@Expose
private String password;

public User(int userid, String name, int surname, String email, String password) {
this.userid = userid;
this.name=name;
this.email=email;
this.password=password;
}

2- In network package add new API responses Classes : ApiRegisterResponse.java, ApiLoginResponse.java and MqttChatResponse.java.

public class ApiRegisterResponse {public ApiRegisterResponse(Boolean ok, String error) {
this.ok = ok;
this.error = error;
}
@SerializedName("ok")
@Expose
private Boolean ok;

@SerializedName("error")
@Expose
private String error;

@SerializedName("response")
@Expose
private String response;

}

ApiLoginResponse.java will return User data in response if login is success.

public class ApiLoginResponse {public ApiLoginResponse(Boolean ok, String error) {
this.ok = ok;
this.error = error;
}
@SerializedName("ok")
@Expose
private Boolean ok;


@SerializedName("error")
@Expose
private String error;

@SerializedName("response")
@Expose
private User user;
}

MqttChatResponse.java will return user connect result to MQTT CHAT.

public class MqttChatResponse {

public MqttChatResponse(){
this.ok = Boolean.TRUE;
}
public MqttChatResponse(String error) {
this.ok = Boolean.FALSE;
this.error = error;
}
private Boolean ok;

private String error;

public Boolean getOk() {
return ok;
}

public String getError() {
return error;
}
}

3- Now we can add retrofit calls in api/APIService.java interface.

interface APIService {

public interface APIService {

@POST(Config.BASE_URL + "users")
Call<ApiRegisterResponse> register(@Body User newUser);

@GET(Config.BASE_URL + "login")
Call<ApiLoginResponse> login(@Query("email") String email,
@Query("password") String password);
}
}

Config.java file contain Url of Laravel API seen in top of this tutorial.

public class Config {
public static String REST_SERVER_URL="http://10.0.1.155";
public static final String BASE_URL ="/android-tuto/api";
}

MQTT CHAT initialisation

You should first get APP_ID and APP_SECRET from your MQTT CHAT admin panel.

In Application class mApplication.java add MQTT CHAT initialisation code.

public class mApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
new MqttChat.getBuilder()
.context(this.getApplicationContext())
.appIcon(R.mipmap.ic_launcher)
.domain("domain.com")
.appId("mqttchat-*******")
.appSecret("****************")
.debugMode(true)
.build();
}
}

ADD NEW USER

Add new package register under ui package then add:

  • New Activity RegisterActivity.java.
  • DataSource class RegisterDataSource.java.
  • ViewModel class RegisterViewModel.java.

RegisterActivity.java will contain register form, send register POST to Laravel API on registerBtn button click (using the ViewModel) and finally go to LoginActivity on registration success.

public class RegisterActivity extends AppCompatActivity {
private ActivityRegisterBinding binding;
private RegisterViewModel mRegisterViewModel;
private ProgressDialog mProgressDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding= DataBindingUtil.setContentView(this, R.layout.activity_register);

mRegisterViewModel= new ViewModelProvider(this).get(RegisterViewModel.class);
mRegisterViewModel.getProgressDialog().observe(RegisterActivity.this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
if(aBoolean){
mProgressDialog= ProgressDialog.show(RegisterActivity.this,"",
"Please Wait",true);
}else{
mProgressDialog.dismiss();
}
}
});
mRegisterViewModel.getRegisterResponse().observe(RegisterActivity.this, new Observer<ApiRegisterResponse>() {
@Override
public void onChanged(ApiRegisterResponse apiRegisterResponse) {
if(apiRegisterResponse.getOk()){
Toast.makeText(RegisterActivity.this,"Register Success ", Toast.LENGTH_LONG).show();
Intent i = new Intent(RegisterActivity.this, LoginActivity.class);
startActivity(i);
}else{
Toast.makeText(RegisterActivity.this, apiRegisterResponse.getError(), Toast.LENGTH_LONG).show();
}
}
});
binding.registerBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
mRegisterViewModel.register(new User(0,
binding.name.getText().toString(),
binding.surname.getText().toString(),
binding.email.getText().toString(),
binding.password.getText().toString()));

}
});
}
}

We used ViewModel RegisterViewModel.java to send HTTP request and receive response from DataSource.

public class RegisterViewModel extends ViewModel {

private RegisterDataSource mDataSource;
private LiveData<Boolean> progressDialog;
private LiveData<ApiRegisterResponse> registerResponse;

public RegisterViewModel(){
mDataSource =new RegisterDataSource();
}


public void register(User user){
mDataSource.register(user);
}

public LiveData<Boolean> getProgressDialog() {
if(progressDialog==null){
progressDialog= mDataSource.getProgressDialog();
}
return progressDialog;
}

public LiveData<ApiRegisterResponse> getRegisterResponse() {
if(registerResponse==null){
registerResponse=mDataSource.getRegisterResponse();
}
return registerResponse;
}
}

And finally RegisterDataSource.java.

public class RegisterDataSource {

private MutableLiveData<Boolean> progressDialog;
private MutableLiveData<ApiRegisterResponse> registerResponse;

public RegisterDataSource() {
this.progressDialog=new MutableLiveData<Boolean>();
this.registerResponse=new MutableLiveData<ApiRegisterResponse>();
}


public void register(User user){
APIService apiService= ServiceGenerator.createService(APIService.class);
getProgressDialog().postValue(true);
apiService.register(user).enqueue(new Callback<ApiRegisterResponse>() {
@Override
public void onResponse(Call<ApiRegisterResponse> call, Response<ApiRegisterResponse> response) {
getProgressDialog().postValue(false);
if(response.isSuccessful()) {
getRegisterResponse().postValue(response.body());
}else{
try {
getRegisterResponse().postValue(new ApiRegisterResponse(false,response.errorBody().string()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<ApiRegisterResponse> call, Throwable t) {
getProgressDialog().postValue(false);
getRegisterResponse().postValue(new ApiRegisterResponse(false,t.getMessage()));
}
});
}

public MutableLiveData<Boolean> getProgressDialog() {
return progressDialog;
}

public MutableLiveData<ApiRegisterResponse> getRegisterResponse() {
return registerResponse;
}
}

USER LOGIN

Add new package login under ui package then add:

  • New Activity LoginActivity.java.
  • DataSource class LoginDataSource.java.
  • ViewModel class LoginViewModel.java.

LoginActivity.java will contain login form. User will login to Laravel WEB application and connect to MQTT CHAT before go to chat UI in MainActivity.

public class LoginActivity extends AppCompatActivity {
private ActivityLoginBinding binding;
private LoginViewModel mLoginViewModel;
private ProgressDialog mProgressDialog;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding= DataBindingUtil.setContentView(this, R.layout.activity_login);

mLoginViewModel= new ViewModelProvider(this).get(LoginViewModel.class);
mLoginViewModel.getProgressDialog().observe(LoginActivity.this, new Observer<Boolean>() {
@Override
public void onChanged(Boolean aBoolean) {
if(aBoolean){
mProgressDialog= ProgressDialog.show(LoginActivity.this,"",
"Please Wait",true);
}else{
mProgressDialog.dismiss();
}
}
});
mLoginViewModel.getLoginResponse().observe(LoginActivity.this, new Observer<ApiLoginResponse>() {
@Override
public void onChanged(ApiLoginResponse apiLoginResponse) {
if(apiLoginResponse.getOk()){
mLoginViewModel.connectToChat(getApplication(),apiLoginResponse.getResponse().getUserId());
}else{
Toast.makeText(LoginActivity.this,apiLoginResponse.getError(), Toast.LENGTH_LONG).show();
}
}
});
mLoginViewModel.getMqttChatResponse().observe(LoginActivity.this, new Observer<MqttChatResponse>() {
@Override
public void onChanged(MqttChatResponse mqttchatResponse) {
if(mqttchatResponse.getOk()){
Intent i = new Intent(LoginActivity.this, MainActivity.class);
startActivity(i);
}else{
Toast.makeText(LoginActivity.this,mqttchatResponse.getError(), Toast.LENGTH_LONG).show();
}
}
});
binding.loginBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
mLoginViewModel.login(binding.email.getText().toString(),
binding.password.getText().toString());

}
});
binding.registerBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
Intent i=new Intent(LoginActivity.this,RegisterActivity.class);
startActivity(i);
}
});

}
}

We used ViewModel LoginViewModel.java :

  • To login user using HTTP Request.
  • To connect user to MQTT-CHAT .
public class LoginViewModel extends ViewModel{

private LoginDataSource mDataSource;
private LiveData<Boolean> progressDialog;
private LiveData<ApiLoginResponse> loginResponse;
private LiveData<MqttChatResponse> mqttChatResponse;

public LoginViewModel(){
mDataSource =new LoginDataSource();
}


public void login(String email,String password){
mDataSource.login(email,password);
}

public void connectToChat(Context ctx, int userid){
mDataSource.connect(ctx,userid);
}

public LiveData<Boolean> getProgressDialog() {
if(progressDialog==null){
progressDialog= mDataSource.getProgressDialog();
}
return progressDialog;
}

public LiveData<ApiLoginResponse> getLoginResponse() {
if(loginResponse==null){
loginResponse=mDataSource.getLoginResponse();
}
return loginResponse;
}

public LiveData<MqttChatResponse> getMqttChatResponse() {
if(mqttChatResponse==null){
mqttChatResponse=mDataSource.getMqttChatResponse();
}
return mqttChatResponse;
}
}

Finally LoginDataSource:

public class LoginDataSource {

private MutableLiveData<Boolean> progressDialog;
private MutableLiveData<ApiLoginResponse> loginResponse;
private MutableLiveData<MqttChatResponse> mqttChatResponse;

public LoginDataSource() {
this.progressDialog=new MutableLiveData<Boolean>();
this.loginResponse=new MutableLiveData<ApiLoginResponse>();
this.mqttChatResponse=new MutableLiveData<MqttChatResponse>();
}


public void login(String email,String password){
APIService apiService= ServiceGenerator.createService(APIService.class);
getProgressDialog().postValue(true);
apiService.login(email,password).enqueue(new Callback<ApiLoginResponse>() {
@Override
public void onResponse(Call<ApiLoginResponse> call, Response<ApiLoginResponse> response) {
getProgressDialog().postValue(false);
if(response.isSuccessful()) {
getLoginResponse().postValue(response.body());
}else{
try {
getLoginResponse().postValue(new ApiLoginResponse(false,response.errorBody().string()));
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void onFailure(Call<ApiLoginResponse> call, Throwable t) {
getProgressDialog().postValue(false);
getLoginResponse().postValue(new ApiLoginResponse(false,t.getMessage()));
}
});
}

public void connect(Context ctx, int userid){
getProgressDialog().postValue(true);
MqttChat.getInstance().logIn(ctx, userid, new CallbackListener() {
@Override
public void onSuccess(Object o) {
MqttChat.getInstance().Connect(new CallbackListener() {
@Override
public void onSuccess(Object o) {
getProgressDialog().postValue(false);
mqttChatResponse.postValue(new MqttChatResponse());
}
@Override
public void onError(String error) {
getProgressDialog().postValue(false);
mqttChatResponse.postValue(new MqttChatResponse(error));
}
});
}
@Override
public void onError(String s) {
}
});
}

public MutableLiveData<Boolean> getProgressDialog() {
return progressDialog;
}

public MutableLiveData<ApiLoginResponse> getLoginResponse() {
return loginResponse;
}

public MutableLiveData<MqttChatResponse> getMqttChatResponse() {
return mqttChatResponse;
}
}

SHOW MESSENGER IN MainActivity

Add new package main under ui package then add:

  • New Activity MainActivity.java.

Since user is already added to MQTT-CHAT domain users using PHP SDK and Connected to MQTT Servers using connectToChat() function. Now we can show Chat UI in MainActivity.

To do this, we will exactly follow steps in documentation.

First add FrameLayout component to MainActivity layout activity_layout.xml.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >

<FrameLayout
android:name=".gui.ui.fragments.mqttchat.MqttChatFragment"
android:id="@+id/mqttchatFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>

Then in the oncreate() method of your main activity, affect MqttChatFragment to FrameLayout component.

Be carefull, MainActivity must extends PresenceActivityA to keep user presence state updated on servers.

public class MainActivity extends PresenceActivityA {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getSupportFragmentManager().beginTransaction().replace(R.id.mqttchatFragment,new MqttChatFragment(), "mqttchat").commit();
}
}

That is all, MQTT-CHAT Fragment is loaded in Framelayout element.

In this tutorial, I showed you a basic integration example of MQTT CHAT in an android application. If you want to customize the integration even more or add advanced features such as notifications with google FCM or use differents listeners and webhooks. Please read the complete documentation.

The complete android project seen in this tutorial has been published in this GitHub repository . Concerning Laravel application (Server side) , You can create application very easy in your local envirement by following this tutorial.

--

--

MQTT CHAT
MQTT CHAT

No responses yet