In this tutorial we will demonstrate how to connect an android app with a Python 3.x based RESTful API using retrofit 2 and PHP Slim framework.

In this example we will create a simple python script that calculate and return the sum of 2 given numbers, the android app will send the 2 numbers with retrofit 2 and the PHP script (we will use Slim framework) will receive and pass them with exec() to the python script then bring the result back to the android app.

Python Script

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
import sys


num_1 = sys.argv[1]
num_2 = sys.argv[2]


def sum(num_1, num_2):
    result = int(num_1) + int(num_2)
    print(result)
    sys.exit()


if __name__ == '__main__':
    sum(num_1, num_2)

With sys.argv[] we get the argument passed by the command line then print the result of the sum in sum()

We can test the result with our command line tool first:

1
2
PS D:\tutorial 1> python .\script.py 24 23
47

*Note: Make sure that Python 3 is installed on your machine or your host/server.

PHP Script

Creating new Slim app

  1. We need to download and install Composer.
  2. We need to make sure that composer is added in the environment paths in windows case.
  3. I am using xampp to create a local server so I will run the following command in C://xampp/htdocs path:

    1
    
    composer create-project slim/slim-skeleton tutorial

    tutorial is my app name

    Now navigate to the project root folder and run this command:

    1
    
    composer require slim/slim "^3.12"

Preparing the PHP script

  1. I copy my script.py to the same folder where there is the index.php which is C://xampp/htdocs/tutorial/public
  2. I write the php code inside the index.php:
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
<?php

declare(strict_types=1);
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
use \Interop\Container\ContainerInterface as ContainerInterface;


require __DIR__ . '/../vendor/autoload.php';

// Create and configure Slim app

$config = ['settings' => [

    'displayErrorDetails' => true,

    'addContentLengthHeader' => false

]];

$app = new \Slim\App($config);

// Define app routes

$app->post('/process', function (Request $request, Response $response, $args) {

    if (!haveEmptyParameters(array('num1', 'num2'), $request, $response)){

        //Get data

        $request_data = $request->getParsedBody();

        // $files = $request->getUploadedFiles();

        $num1 = urldecode($request_data['num1']);

        $num2 = urldecode($request_data['num2']);

        //processing

        $result =  exec("C:\Users\cergo\AppData\Local\Programs\Python\Python37\python.exe script.py ".$num1." ".$num2);

        $response_data = array();

        $response_data['error'] = false;

        $response_data['message'] = $result;

        $response->write(json_encode($response_data));

        return $response

            ->withHeader('Content-type', 'application/json')

            ->withStatus(202);
    }

    return $response

    ->withHeader('Content-type', 'application/json')

    ->withStatus(422);

});

//To check the passed parameters

function haveEmptyParameters($required_params,$request, $response){

    $error = false;

    $error_params = '';

    $requested_params = $request->getParsedBody();

    foreach($required_params as $param){

        if(!isset($requested_params[$param]) || strlen($requested_params[$param]) <= 0){

            $error = true;

            $error_params .= $param . ', ';

        }

    }

    if($error){

        $error_details = array();

        $error_details['error'] = true;

        $error_details['message'] = 'Requierd parameter '.substr($error_params, 0, -2) . ' are missing or empty.';

        $response->write(json_encode($error_details));
    }
    return $error;
}

$app->run();

In my case C:\Users\cergo\AppData\Local\Programs\Python\Python37\python.exe is the path of python, this will change in your case and where you run the script; local machine or a server.

We test the result using postman as showed below:

Android

Adding dependencies

We create a new project in android studio and add the retrofit 2 dependencies to the app gradle as following:

1
2
3
4
5
6
7
dependencies {
    ...
    implementation 'com.squareup.okhttp3:okhttp:3.10.0'
    implementation 'com.google.code.gson:gson:2.8.2'
    implementation 'com.squareup.retrofit2:retrofit:2.3.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
}

Creating the xml layout

We create a simple layout with edittexts for the 2 numbers, button with action, progressbar and a textview to show the result:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:padding="16dp"
    android:layout_gravity="center"
    android:gravity="center"
    tools:context=".MainActivity">
    
    <EditText
        android:id="@+id/et_num1"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="num 1"
        android:textSize="24sp"
        android:inputType="number" />
    <EditText
        android:id="@+id/et_num2"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="num 2"
        android:textSize="24sp"
        android:inputType="number" />
    <Button
        android:textSize="24sp"
        android:id="@+id/btn_process"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Process" />
    <ProgressBar
        android:id="@+id/pb_progress"
        android:visibility="gone"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <TextView
        android:textSize="24sp"
        android:id="@+id/tv_result"
        android:textStyle="bold"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    

</LinearLayout>

Adding permissions

We add the following permission to AndroidManifest.xml:

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

Creating Retrofit Client, API interface and the response model

Response.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Response {
    String error, message;

    public Response(String error, String message) {
        this.error = error;
        this.message = message;
    }

    public String getError() {
        return error;
    }

    public void setError(String error) {
        this.error = error;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

Api.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import okhttp3.RequestBody;
import retrofit2.Call;
import retrofit2.http.Multipart;
import retrofit2.http.POST;
import retrofit2.http.Part;

public interface Api {
    @Multipart
    @POST("process")
    Call<Response> process(
            @Part("num1") RequestBody num1,
            @Part ("num2") RequestBody num2
    );
}

RetrofitClient.java

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import java.util.concurrent.TimeUnit;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

public class RetrofitClient {
    private Retrofit retrofit;
    public void generateClient(){

        final OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .readTimeout(6000, TimeUnit.SECONDS)
                .connectTimeout(6000, TimeUnit.SECONDS)
                .build();
        retrofit = new Retrofit.Builder()
                .baseUrl("http://192.168.70.117/tutorial/public/") //Make sure to include your local machine or server ip/url
                .addConverterFactory(GsonConverterFactory.create())
                .client(okHttpClient)
                .build();
    }


    public Api getApi(){
        return retrofit.create(Api.class);
    }
}

It worth it to check first in your emulator browser if it has the access to your machine IP.

Calling for action

In MainActivity.java we include the following action when the button btn_process is clicked:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
btn_process.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                pb_progress.setVisibility(View.VISIBLE);

                RequestBody num1 = RequestBody.create(MediaType.parse("multipart/form-data"), et_num1.getText().toString().trim());
                RequestBody num2 = RequestBody.create(MediaType.parse("multipart/form-data"), et_num2.getText().toString().trim());

                RetrofitClient retrofitClient = new RetrofitClient();
                retrofitClient.generateClient();
                Call<Response> call =  retrofitClient.getApi().process(
                        num1,
                        num2
                );

                call.enqueue(new Callback<Response>() {
                    @Override
                    public void onResponse(Call<Response> call, retrofit2.Response<Response> response) {
                        pb_progress.setVisibility(View.GONE);
                        if (response.isSuccessful()){
                            tv_result.setText(response.body().getMessage());
                        }else{
                            tv_result.setText("Response was not successful!");
                        }

                    }

                    @Override
                    public void onFailure(Call<Response> call, Throwable t) {
                        pb_progress.setVisibility(View.GONE);
                        tv_result.setText(t.getMessage());
                    }
                });

            }
        });
        

Result