A few days ago I wrote a post describing how to run an HTTP server from an iOS app. This intrigued me to start investigating how to implement a similar application on an Android app too. So, in this post I will describe how to setup and run an HTTP server from an Android app.

As I mentioned in the iOS post, such a setup (running a server from an app) can be utilized in many ways, with performing usability testing being one of those. If the app under test depends on a backend service, then we could apply some configuration to target the app with the server, which in turn would act as a mock server.

But enough with the intro, let’s move to the action.

Implementation

Ktor

For the purposes of this app I am going to use Ktor framework to run the server. Ktor is a framework that helps implementing web-based applications and it can be used either on an Android app for the client-side logic or on a Kotlin server-side project. There is also support for Kotlin Multiplatform Projects which enables you to write the networking client code once in Kotlin and then compile to whatever platform you are interested in, for example iOS.

If are interested in that and you want to learn more, you can check out my previous post about how to create a Feed reader app with Kotlin Native.

Sadly, Ktor Server is not currently supported but hopefully it will be sooner or later.

Dependencies

On a vanilla Android project, let’s add the dependencies on the app/build.gradle file:

implementation "io.ktor:ktor:1.2.5"
implementation "io.ktor:ktor-server-netty:1.2.5"
implementation "io.ktor:ktor-gson:1.2.5"

We also have to add the following packagingOptions to avoid any build conflicts

    packagingOptions {
        exclude 'META-INF/*'
    }

The final app/build.gradle should look like the following:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "io.github.diamantidis.androidServer"
        minSdkVersion 28
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    packagingOptions {
        exclude 'META-INF/*'
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test:runner:1.2.0'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'

    implementation "io.ktor:ktor:1.2.5"
    implementation "io.ktor:ktor-server-netty:1.2.5"
    implementation "io.ktor:ktor-gson:1.2.5"
}

Permissions

The next step is to add an entry on the AndroidManifest.xml regarding the INTERNET permissions like in the following snippet:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="io.github.diamantidis.androidServer">
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        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=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Code

Finally, we can open our MainActivity.kt and add the following content:

package <your_package>

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import io.ktor.application.call
import io.ktor.application.install
import io.ktor.features.ContentNegotiation
import io.ktor.gson.gson
import io.ktor.response.respond
import io.ktor.routing.get
import io.ktor.routing.routing
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        embeddedServer(Netty, 8080) {
            install(ContentNegotiation) {
                gson {}
            }
            routing {
                get("/") {
                    call.respond(mapOf("message" to "Hello world"))
                }
            }
        }.start(wait = true)
    }
}

We first create a server with Netty as the application engine, 8080 as the port and a module function. Inside this module function, we add the ContentNegotiation feature and register the gson converter. This will allow us to convert the request data to our model and our models to JSON responses. After that, we use the routing feature to define our endpoint and the response. The current implementation returns just a simple map, but it could also return a more complex data structure. Finally we start the server and we explicitly set to wait until we stop it.

We are now ready to run the app. After the app is successfully installed and running on either a device or a simulator, open the browser and hit localhost:8080.

Voilà! You should get {"message": "Hello world"} as a response!

Conclusion

To sum up, in this post we have seen how to run a simple HTTP server from an Android app using Ktor Server. In just 10 lines of code, we manage to create, set up and run an HTTP server. And with the JSON serialization installed.

Thanks for reading and should you have any questions, suggestions or comments, just let me know on Twitter or email me!!