Appwrite, Android and Realtime:
![Social Media posts (7)](https://64.media.tumblr.com/2f82a824f9eab328105a07000995b90b/f1dc7fb8a8b26d81-b9/s540x810/964b507337daadd8f23dcde4a0b41213507d5964.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. 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:
- Click on theĀ Add CollectionĀ button
- Inside the dialog:
- Set the collection name toĀ Products
- 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://64.media.tumblr.com/5cae3a4b8a2b400421ae4dad38609ac3/f1dc7fb8a8b26d81-68/s540x810/5c407fa79f0f1d10525741af6f06a073228bf0bc.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:
// 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
Ā file. You may also find your package name in yourĀ 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:
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:
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:
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.
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()
val productStream: LiveData = _productStream
private val _productDeleted = MutableLiveData()
val productDeleted: LiveData = _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) {
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:
- 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.
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(object : DiffUtil.ItemCallback() {
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)
}
)
}
}
- TheĀ
ProductViewHolder
Ā which describes a singleĀProduct
Ā view and metadata about itās position in theĀRecyclerView
:
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:
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()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_realtime)
val button = findViewById(R.id.btnSubscribe)
button.setOnClickListener {
viewModel.subscribeToProducts(this)
}
val adapter = ProductAdapter()
val recycler = findViewById(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.
from Tumblr https://generouspiratequeen.tumblr.com/post/661648134147014656