Appwrite, Android and Realtime
Tags: android, opensource, news, tutorial
Published: 2021-09-06 11:32:07

## 🤔 What is Appwrite?

Appwrite is a new open-source, end-to-end, backend server for web and mobile developers that allows you to build apps much faster. It abstracts and simplifies common development tasks behind REST APIs and tools, helping you build advanced apps faster.

![Social Media posts (7)](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yxb9c2wc0o8k4y0vbfzn.gif)
 

## 🤖 Appwrite for Android

Appwrite provides **sleek REST APIs** for various common features that are required for web and mobile application development such as **cloud functions**, **database**, **storage**, as well as **realtime** support for each service so that we as developers can focus on our applications rather than on backend implementations. This makes Appwrite very suitable for those of us who want to build Android applications. In this tutorial we'll build an Android realtime product application using Appwrite and the new **realtime** API, let's get started.

## 📝 Prerequisites

In order to continue with this tutorial, you need to have access to an Appwrite console, and either an existing project, or permission to create one. If you have not already installed Appwrite, please do so. Installing Appwrite is really simple following Appwrite's official [installation docs](https://appwrite.io/docs/installation). Installation should only take around 2 minutes. Once installed, login to your console and **create a new Project**.

## 💾 Database Setup

In the Appwrite console, let's select the project that we will be using for our Android app. If you don't have a project yet, you can easily create one by clicking on the **Create Project** button. Once inside, select **Database** from the left sidebar. On the database page:

1. Click on the **Add Collection** button
2. Inside the dialog:
    1. Set the collection name to **Products**
    2. Click the **Create** button

This will then create a collection, and you will be redirected to the new collection's page where we can define its rules. Define the following rules, then click the **Update** button.

- **Name**
    - label: Name
    - Key: name
    - Rule Type: Text
    - Required: true
    - Array: false
- **Description**
    - label: Description
    - Key: description
    - Rule Type: Text
    - Required: true
    - Array: false
- **SKU**
    - label: SKU
    - Key: sku
    - Rule Type: Text
    - Required: true
    - Array: false
- **Price**
    - label: Price
    - Key: price
    - Rule Type: Numeric
    - Required: true
    - Array: false
- **Image URL**
    - label: Image URL
    - Key: imageUrl
    - Rule Type: Text
    - Required: true
    - Array: false

![Products Collection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/guf4hngrqboiabzkba2g.png)

Now that the collection is created, we can move on to setting up the Android application.

## ⚙️ Setup Android Project and Dependencies

Using Android Studio, create a new Android Application project choosing the **Empty Activity** template. Once the project is created, add the following dependencies to your app's `build.gradle(.kts)` file:

```groovy
    // Appwrite
    implementation("io.appwrite:sdk-for-android:0.2.0")
    
    // Appcompat, LiveData, ViewModel and Activity extensions
    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.3.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
    implementation 'androidx.activity:activity-ktx:1.3.1'
    
    // JSON
    implementation 'com.google.code.gson:gson:2.8.7'
    
    // Image loading
    implementation 'com.github.bumptech.glide:glide:4.12.0'
    kapt 'com.github.bumptech.glide:compiler:4.12.0'
```

## ➕️ Add Android Platform

To initialize the Appwrite SDK and start interacting with Appwrite services, you first need to add a new Android platform to your project. To add a new platform, go to your Appwrite console, select your project (or create one if you haven't already), and click the **Add Platform** button on the project Dashboard.

From the options, choose to add a new Android platform and add your app credentials.

Add your app name and package name. Your package name is generally the `applicationId` in your app-level [`build.gradle`](https://github.com/abnegate/android-realtime-starter/blob/main/app/build.gradle) file. You may also find your package name in your [`AndroidManifest.xml`](https://github.com/abnegate/android-realtime-starter/blob/main/app/src/main/AndroidManifest.xml) file. By registering a new platform, you are allowing your app to communicate with the Appwrite API.

## 🧱 Create the Product Model

We now need to create a model to represent a product on the Android side. Create a new Kotlin file `Product.kt` and declare a simple data class:

```kotlin
data class Product(
    val name: String,
    val sku: String,
    val price: Double,
    val imageUrl: String
)
```

## ⚒️ Building Views

Now, open your `app/src/main/res/layout/activity_main.xml` and update the layout as following:

```xml
<?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">

    <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recyclerProducts"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toTopOf="@id/btnSubscribe"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="3"/>

    <Button
            android:id="@+id/btnSubscribe"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:text="Subscribe to products"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toBottomOf="@id/recyclerProducts"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

You'll notice that the activity is quite simple; we only have a `Button` to subscribe and a `RecyclerView`. The `RecyclerView` will be used to display the product collection in realtime as we add new products. We'll now need to define a separate view that will be used to represent each of the individual products.

Create a new layout from `File > New > Layout Resource File`, name it `item_product.xml` and add the following:

```xml
<?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"
        android:layout_width="match_parent"
        android:layout_height="120dp"
        android:layout_margin="2dp">

    <ImageView
            android:id="@+id/imgProduct"
            android:layout_width="match_parent"
            android:layout_height="120dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

    <View
            android:layout_width="match_parent"
            android:layout_height="34dp"
            android:alpha="0.6"
            android:background="@color/black"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

    <TextView
            android:id="@+id/txtName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:textColor="@color/white"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintStart_toStartOf="parent"/>

    <TextView
            android:id="@+id/txtPrice"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="8dp"
            android:textColor="@color/white"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```

All the pieces of our `RecyclerView` are in place, and we can move on to setting up the `ViewModel`, where most of the heavy lifting happens.

## 👩‍🔧 Create View Model

Create `app/src/main/java/com/example/realtimestarter/RealtimeViewModel.kt` and update it with following code. Make sure to replace all the property values near the top of the file with your own values which can be found in your Appwrite console.

```kotlin
package io.appwrite.realtimestarter

import android.content.Context
import android.util.Log
import androidx.lifecycle.*
import io.appwrite.Client
import io.appwrite.extensions.toJson
import io.appwrite.models.RealtimeResponseEvent
import io.appwrite.models.RealtimeSubscription
import io.appwrite.services.Account
import io.appwrite.services.Database
import io.appwrite.services.Realtime
import kotlinx.coroutines.launch

class RealtimeViewModel : ViewModel(), LifecycleObserver {

    private val endpoint = "YOUR_ENDPOINT"          // Replace with your endpoint
    private val projectId = "YOUR_PROJECT_ID"       // Replace with your project ID
    private val collectionId = "YOUR_COLLECTION_ID" // Replace with your product collection ID

    private val realtime by lazy { Realtime(client!!) }

    private val account by lazy { Account(client!!) }

    private val db by lazy { Database(client!!) }

    private val _productStream = MutableLiveData<Product>()
    val productStream: LiveData<Product> = _productStream

    private val _productDeleted = MutableLiveData<Product>()
    val productDeleted: LiveData<Product> = _productDeleted

    private var client: Client? = null

    var subscription: RealtimeSubscription? = null
        private set

    fun subscribeToProducts(context: Context) {
        buildClient(context)

        viewModelScope.launch {
            // Create a session so that we are authorized for realtime
            createSession()

            // Attach an error logger to our realtime instance
            realtime.doOnError { Log.e(this::class.java.name, it.message.toString()) }

            // Subscribe to document events for our collection and attach the handle product callback
            subscription = realtime.subscribe(
                "collections.${collectionId}.documents",
                payloadType = Product::class.java,
                callback = ::handleProductMessage
            )

            //createDummyProducts()
        }
    }

    private fun handleProductMessage(message: RealtimeResponseEvent<Product>) {
        when (message.event) {
            in
            "database.documents.create",
            "database.documents.update" -> {
                _productStream.postValue(message.payload!!)
            }
            "database.documents.delete" -> {
                _productDeleted.postValue(message.payload!!)
            }
        }
    }

    private suspend fun createDummyProducts() {
        // For testing; insert 100 products while subscribed
        val url = "https://dummyimage.com/600x400/cde/fff"
        for (i in 1 until 100) {
            db.createDocument(
                collectionId,
                Product("iPhone $i", "sku-$i", i.toDouble(), url).toJson(),
                listOf("*"),
                listOf("*")
            )
        }
    }

    private fun buildClient(context: Context) {
        if (client == null) {
            client = Client(context)
                .setEndpoint(endpoint)
                .setProject(projectId)
        }
    }

    private suspend fun createSession() {
        try {
            account.createAnonymousSession()
        } catch (ex: Exception) {
            ex.printStackTrace()
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    fun closeSocket() {
        // Activity is being destroyed; close our socket connection if it's open
        subscription?.close()
    }
}
```

The `ViewModel` contains a function to call the realtime API and subscribe to notifications for create/update/delete events relating to any documents within the collection with id `collectionId`, that is also visible to our user.

To allow observing the incoming realtime updates from outside itself, the `ViewModel` also exposes the `LiveData` property `productStream`, which we'll utilize in our `Activity` later on to get realtime updates in our `RecyclerView`.

## ♻️ Recycler View

There's 2 more files we need to add to get our `RecyclerView` working:

1. The `ProductAdapter`, which will handle creating and binding a view for each `Product` as they're added to the database. The `DiffUtil.ItemCallback` provided in the constructor will be used to calculate list update diffs on a background thread, then post any changes on the UI thread, perfect for realtime! You can find more information on `DiffUtil` [here](https://developer.android.com/reference/androidx/recyclerview/widget/DiffUtil).

```kotlin
package io.appwrite.realtimestarter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter

class ProductAdapter :
    ListAdapter<Product, ProductViewHolder>(object : DiffUtil.ItemCallback<Product>() {
        override fun areItemsTheSame(oldItem: Product, newItem: Product) =
            oldItem.sku == newItem.sku

        override fun areContentsTheSame(oldItem: Product, newItem: Product) =
            oldItem == newItem
    }) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProductViewHolder {
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.item_product, parent, false)

        return ProductViewHolder(view)
    }

    override fun onBindViewHolder(holder: ProductViewHolder, position: Int) {
        val item = currentList[position]

        holder.setName(item.name)
        holder.setPrice(item.price.toString())
        holder.setProductImage(item.imageUrl)
    }

    fun submitNext(product: Product) {
        val current = currentList.toMutableList()
        val index = currentList.indexOfFirst {
            it.sku == product.sku
        }
        if (index != -1) {
            current[index] = product
        } else {
            current.add(product)
        }
        submitList(current)
    }

    fun submitDeleted(product: Product) {
        submitList(
            currentList.toMutableList().apply {
                remove(product)
            }
        )
    }
}
```

2. The `ProductViewHolder` which describes a single `Product` view and metadata about it's position in the `RecyclerView`:

```kotlin
package io.appwrite.realtimestarter

import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide

class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    private var nameView: TextView = itemView.findViewById(R.id.txtName)
    private var priceView: TextView = itemView.findViewById(R.id.txtPrice)
    private var imageView: ImageView = itemView.findViewById(R.id.imgProduct)

    fun setName(name: String) {
        nameView.text = name
    }

    fun setPrice(price: String) {
        priceView.text = "\$$price"
    }

    fun setProductImage(url: String) {
        Glide.with(itemView)
            .load(url)
            .centerCrop()
            .into(imageView)
    }
}
```

## 💆 Activity

With everything else in place, lets tie it all together in our `MainActivity`. Open `app/src/main/java/com/example/realtimestarter/MainActivity.kt` and update like so:

```kotlin
package io.appwrite.realtimestarter

import android.os.Bundle
import android.widget.Button
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.RecyclerView

class RealtimeActivity : AppCompatActivity() {

    private val viewModel by viewModels<RealtimeViewModel>()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_realtime)

        val button = findViewById<Button>(R.id.btnSubscribe)
        button.setOnClickListener {
            viewModel.subscribeToProducts(this)
        }

        val adapter = ProductAdapter()
        val recycler = findViewById<RecyclerView>(R.id.recyclerProducts)
        recycler.adapter = adapter

        viewModel.productStream.observe(this) {
            adapter.submitNext(it)
        }
        viewModel.productDeleted.observe(this) {
            adapter.submitDeleted(it)
        }

        lifecycle.addObserver(viewModel)
    }

    override fun onStop() {
        super.onStop()
        lifecycle.removeObserver(viewModel)
    }
}


```

## 🏎️ Let's Get Realtime

That's it, all that's left is to run the application, then add some documents. When running on your emulator or device, click **Subscribe** to start listening for realtime updates.

Head back to your Appwrite console and navigate to the `Products` collection we created earlier. From here, we can start adding new documents and see them appear in our app.

As soon you add a product in the console, you'll see them appear in the UI of your app! That's the real beauty of Appwrite Realtime, as shown below.

![Products GIF](https://s9.gifyu.com/images/products-realtime90a165133e674618.gif)

## 🥂 Conclusion

I hope you enjoyed building this Realtime Application with Appwrite and Android. The full source for this application is available in the [Demo Realtime Application](https://github.com/abnegate/android-realtime-starter) repository. Let us know if you have any feedback or suggestions. Looking forward to seeing what the community can create with Appwrite Realtime and Android!

## 🎓 Learn More

- [Getting Started With Android](https://appwrite.io/getting-started-with-android)
- [Android Playground](https://github.com/appwrite/playground-for-android)
- [Appwrite Docs](https://appwrite.io/docs)