Introduction to Appwrite Cloud Functions with Android and Kotlin
Tags: kotlin, android, hacktoberfest, serverless
Published: 2021-10-21 16:59:06

*Originally posted on [hashnode](https://hardiksachan.hashnode.dev/introduction-to-appwrite-cloud-functions-with-android-and-kotlin)*

In this article, we'll take a look at [Cloud Functions in appwrite.io](https://appwrite.io/docs/functions) and how to use them with Kotlin or Android.

Here's an overview of what we'll be doing. 
- We'll create a classic "Hello, World!" program with functions
- We'll write a function to get Covid-19 stats from [Covid19Api](https://covid19api.com/)
- We'll build an android app around the above function

Before we start, if you like to dive directly into the code, here's the [github repo](https://github.com/hardiksachan/introduction-to-appwrite-functions)

# Creating a new Appwrite project

Before we dive into creating functions, let's set up a new Appwrite project to which we will add our functions. If you already have an appwrite project you can skip this section.

To get Appwrite up and running, follow [these instructions](https://appwrite.io/docs/installation) and create a new account.

Next, follow the onscreen instructions to create a new project - 
![New project name](https://cdn.hashnode.com/res/hashnode/image/upload/v1634754810423/fzuntU29V.png)

# Hello World with Appwrite Functions

This is a pretty basic project to implement but it will make us familiar with the process of creating a new Appwrite Function. Let's get started  -

First of all, we need a new Kotlin project, I'm going to use [Intellij Idea](https://www.jetbrains.com/idea/) as an IDE with [gradle build system](https://gradle.org/).

Here's the configuration I'm using to create a new project - 
![New project config](https://cdn.hashnode.com/res/hashnode/image/upload/v1634751219898/R50XksTOl.png)

Next, let's create a new file named `Main.kt` in `hello-world/src/main/kotlin` and put the following code in it - 

```kotlin
fun main() {
    println("Hello, world!")
}
```

...and we are done! 

#### Now let's add this to an Appwrite function. 

Appwrite requires us to create a "fat jar" which is just a fancy way of saying that we need to include all runtime dependencies in our jar file. To achieve this we have to add the following lines to `build.gradle.kts` - 

```kotlin
tasks.withType<Jar>() {
    manifest {
        attributes["Main-Class"] = "MainKt"
    }

    from(sourceSets.main.get().output)

    dependsOn(configurations.runtimeClasspath)
    from({
        configurations.runtimeClasspath.get()
            .filter { it.name.endsWith("jar") }
            .map { zipTree(it) }
    })
}
```

These lines basically instruct Gradle to build a "fat jar". Now to actually build a jar, we need to run the following task -

![Build jar task](https://cdn.hashnode.com/res/hashnode/image/upload/v1634752559348/wGvz-w9Cq.png)

This will create the required jar file in `hello-world/build/libs/hello-world-1.0-SNAPSHOT.jar`

Now, to upload this to Appwrite we need to package this in a `tar` file. To do this run the following command inside the `libs` directory - 

```bash
tar -zcvf code.tar.gz .\hello-world-1.0-SNAPSHOT.jar
```

This will create a file named `code.tar.gz` in the `libs` directory. We will need this file later.

### Creating a new Appwrite Function

Now, let's create a new appwrite function from the appwrite console. Navigate to `Functions` tab on Left pane, and click on `Add Function` button. Give it a name, in this case I'm using `hello-world`, and for the runtime, choose `Java 16.0`

Next, let's create a new deploy tag - 

![Hello World Deploy tag](https://cdn.hashnode.com/res/hashnode/image/upload/v1634755606240/kwYycTjkU.png)

Here's the Command - 
```bash
java -jar hello-world-1.0-SNAPSHOT.jar
```

Here we upload the tar file we created earlier. Click `Activate` and voila, we're done. :D

Let's test out the function. Click on `Execute Now` we don't need to pass any data to this function. Navigate to logs and check the `output`

![Hello World successful execution](https://cdn.hashnode.com/res/hashnode/image/upload/v1634755836224/6yPnIcio4.png)

And you just created your first appwrite function. Cheers 🍻

### A little upgrade

Now let's make this function a little more useful. Instead of printing `Hello, world!` all the time, we'll pass a name to this function.

#### A note on `APPWRITE_FUNCTION_DATA` and `APPWRITE_FUNCTION_EVENT_DATA`

As mentioned in the [documentation](https://appwrite.io/docs/functions), these *environment variables* contains the information pertinent to execution of a custom appwrite function.

We use `APPWRITE_FUNCTION_DATA` when we trigger the function through Appwrite console or via [SDK or HTTP API](https://appwrite.io/docs/client/functions?sdk=web#functionsCreateExecution). *This variable contains the data passed in those executions.*

`APPWRITE_FUNCTION_EVENT_DATA` is used when the function is triggered by some **event** like inserting a new document. *This variable contains information regarding that event.*

#### Let's get back to the upgrade

Now we need to read the name from the `APPWRITE_FUNCTION_DATA` variable, to do this we add a new function - 

```kotlin
fun getNameFromEnv(): String =
    System.getenv("APPWRITE_FUNCTION_DATA")
```

and update the `println` statement as follows - 

```kotlin
println("Hello, ${getNameFromEnv()}!")
```

Let's build the jar and add a new deploy tag to our appwrite console. You can follow the same instructions from above. And then execute the function passing you name as input.

Let's test it out. 

![Hello Hardik Input](https://cdn.hashnode.com/res/hashnode/image/upload/v1634757891686/uIfRnmh8B.png)
![Hello Hardik output](https://cdn.hashnode.com/res/hashnode/image/upload/v1634757847134/45cZRlwGl.png)

Yay! We're done. 🚀

# Display Covid-19 stats

By creating the previous project, we got familiar with the process of creating a new Appwrite function with Kotlin. Now, let's build another project which is a little more complex. In this project, we'll see how we can integrate an Appwrite Function with a third-party API. Let's get started.

We will be using [Covid19Api](https://covid19api.com/) to get the data.

Before we dive into implementing the function, let's take a quick look at the requirements of what we'll be building - 
- First, we need to read the country from `APPWRITE_FUNCTION_DATA`.
- Now, we need to check if the country is valid.
- If the country is valid, we return the stats for that country.
- If the country is not valid, we return global stats, with a message indicating the country was not valid.

Let's first create a new project. I'll name it `get-covid-stats`. Then we need a `Main.kt` file in `get-covid-stats/src/main/kotlin`. 

Now we need to read the country name from `APPWRITE_FUNCTION_DATA`. Let's do that - 

```kotlin
fun readCountryFromEnv(): String = 
    System.getenv("APPWRITE_FUNCTION_DATA")
```

and in `main` let's call it - 

```kotlin
suspend fun main() {
    val country = readCountryFromEnv()
}
```

Next, we need to install some dependencies. We need to make Network Requests for that, I'm going to use [Ktor HTTP Client](https://ktor.io/docs/client.html) and to parse json let's use [`kotlinx.serialization`](https://github.com/Kotlin/kotlinx.serialization). If you don't know these libraries, don't worry, I'll explain how they work. To install these dependencies add the following lines to `build.gradle.kts` - 

```kotlin
dependencies {
    // ....
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0")
    implementation("io.ktor:ktor-client-core:1.6.4")
    implementation("io.ktor:ktor-client-cio:1.6.4")
    implementation("io.ktor:ktor-client-serialization:1.6.4")
}
```

There is one more line we need to add for `kotlinx.serialization` to work properly. You can read more about it [here](https://github.com/Kotlin/kotlinx.serialization). Let's add the following line to `build.gradle.kts` - 

```kotlin
plugins {
    // ...
    kotlin("plugin.serialization") version "1.5.31"
}
```

Next, we need some classes to hold responses we receive from [Covid19Api](https://covid19api.com/). We'll save the classes in `model` package. Let's create them - 

First, let's create an interface with data we need to return in `get-covid-stats/src/main/kotlin/model/ICovidStats.kt`

```kotlin
package model

interface ICovidStats {
    val newConfirmed: Int
    val totalConfirmed: Int
    val newDeaths: Int
    val totalDeaths: Int
    val newRecovered: Int
    val totalRecovered: Int
}
```

Now, if we take a look at data returned from the [endpoint](https://documenter.getpostman.com/view/10808728/SzS8rjbc#00030720-fae3-4c72-8aea-ad01ba17adf8) we see we need three classes `Response.kt`, `GlobalStats.kt` and `CountryStats.kt`. Let's create them -

```kotlin
// GlobalStats.kt

package model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class GlobalStats(
    @SerialName("NewConfirmed") override val newConfirmed: Int,
    @SerialName("TotalConfirmed") override val totalConfirmed: Int,
    @SerialName("NewDeaths") override val newDeaths: Int,
    @SerialName("TotalDeaths") override val totalDeaths: Int,
    @SerialName("NewRecovered") override val newRecovered: Int,
    @SerialName("TotalRecovered") override val totalRecovered: Int,
) : ICovidStats
```

`@Serializable` tells `kotlinx.serialization` that this class can be parsed to/from JSON and `@SerialName` is used to indicate the JSON field name.

```kotlin
// CountryStats.kt

package model

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class CountryStats(
    @SerialName("Country") val country: String,
    @SerialName("CountryCode") val countryCode: String,
    @SerialName("Slug") val slug: String,
    @SerialName("NewConfirmed") override val newConfirmed: Int,
    @SerialName("TotalConfirmed") override val totalConfirmed: Int,
    @SerialName("NewDeaths") override val newDeaths: Int,
    @SerialName("TotalDeaths") override val totalDeaths: Int,
    @SerialName("NewRecovered") override val newRecovered: Int,
    @SerialName("TotalRecovered") override val totalRecovered: Int,
) : ICovidStats
```

```kotlin
// Response.kt

package model

import kotlinx.serialization.Serializable
import kotlinx.serialization.SerialName

@Serializable
data class Response(
    @SerialName("Global") val global : GlobalStats,
    @SerialName("Countries") val countries : List<CountryStats>,
)
```

Okay 🤯, these are the response models we need.

We also need an object which we will return from this function. Let's do that - 

```kotlin
// FunctionResult.kt

package model

import kotlinx.serialization.Serializable

@Serializable
data class FunctionResult(
    val isGlobal: Boolean,
    val newConfirmed: Int,
    val totalConfirmed: Int,
    val newDeaths: Int,
    val totalDeaths: Int,
    val newRecovered: Int,
    val totalRecovered: Int,
)

```

We will also need a JSON Parser, Let's set up `kotlinx.serialization` json Parser - 

```kotlin
val jsonParser = Json {
    isLenient = true
    ignoreUnknownKeys = true
}
```

Next, let's get the stats from the API. First, we need an HTTP Client to make the requests. Let's do that - 

```kotlin
HttpClient() {
    install(JsonFeature) {
        serializer = KotlinxSerializer(json = jsonParser)
    }
}
```

Here, we also install `JsonFeature` which automatically parses the response to classes we created earlier.  Now, let's use this client to make a get request to `https://api.covid19api.com/summary`.

```kotlin
HttpClient() {
    // ...
}.use { client ->
    val response: Response = client.get("https://api.covid19api.com/summary")
}
```

Now let's get the country or global data from this and create a `FunctionResult` object - 

```kotlin
val result: FunctionResult = response.countries.find {
    it.country.equals(country, ignoreCase = true) ||
            it.countryCode.equals(country, ignoreCase = true) ||
            it.slug.equals(country, ignoreCase = true)
}?.run {
    FunctionResult(
        false, newConfirmed, totalConfirmed, newDeaths,
        totalDeaths, newRecovered, totalRecovered
    )
} ?: response.global.run {
    FunctionResult(
        true, newConfirmed, totalConfirmed, newDeaths,
        totalDeaths, newRecovered, totalRecovered
    )
}
```

Let's put this information in stdout - 

```kotlin
println(jsonParser.encodeToString(result))
```

Here's the complete code - 

```kotlin
import io.ktor.client.*
import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.*
import io.ktor.utils.io.core.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import model.FunctionResult
import model.Response

suspend fun main() {
    val country = readCountryFromEnv()

    val jsonParser = Json {
        isLenient = true
        ignoreUnknownKeys = true
    }

    HttpClient() {
        install(JsonFeature) {
            serializer = KotlinxSerializer(json = jsonParser)
        }
    }.use { client ->
        val response: Response = client.get("https://api.covid19api.com/summary")

        val result: FunctionResult = response.countries.find {
            it.country.equals(country, ignoreCase = true) ||
                    it.countryCode.equals(country, ignoreCase = true) ||
                    it.slug.equals(country, ignoreCase = true)
        }?.run {
            FunctionResult(
                false, newConfirmed, totalConfirmed, newDeaths,
                totalDeaths, newRecovered, totalRecovered
            )
        } ?: response.global.run {
            FunctionResult(
                true, newConfirmed, totalConfirmed, newDeaths,
                totalDeaths, newRecovered, totalRecovered
            )
        }

        println(jsonParser.encodeToString(result))
    }
}

fun readCountryFromEnv(): String =
    System.getenv("APPWRITE_FUNCTION_DATA")
```

Whew, that was a lot of code. Let's add our function to appwrite console (see steps in above example) and test it out.


![Get Covid Stats Input](https://cdn.hashnode.com/res/hashnode/image/upload/v1634826677291/YYl179QTZ.png)

![iGet Covid Stats Output](https://cdn.hashnode.com/res/hashnode/image/upload/v1634826947659/B5dr0U5KY.png)

Here's what it prints - 
```json
{"isGlobal":false,"newConfirmed":14623,"totalConfirmed":34108996,"newDeaths":197,"totalDeaths":452651,"newRecovered":0,"totalRecovered":0}
```

Alright! In this article we learned the basics of Appwrite Function Service. In the next post, I'll show you how to connect an Appwrite Function to an android application. Stay tuned for that.