发布于 2026-01-06 3 阅读
0

Appwrite、Android 和实时

Appwrite、Android 和实时

🤔 Appwrite是什么?

Appwrite 是一个全新的开源端到端后端服务器,面向 Web 和移动开发者,可显著提升应用开发速度。它将常见的开发任务抽象化并简化到 REST API 和工具背后,帮助您更快地构建高级应用。

社交媒体帖子(7)

🤖 Appwrite安卓版

Appwrite为 Web 和移动应用开发所需的各种常用功能提供了简洁的 REST API,例如云函数数据库存储,以及对每项服务的实时支持,使开发者能够专注于应用本身,而不是后端实现。这使得 Appwrite 非常适合想要构建 Android 应用的开发者。在本教程中,我们将使用 Appwrite 和新的实时API 构建一个 Android 实时产品应用,让我们开始吧。

📝 先决条件

要继续学习本教程,您需要访问 Appwrite 控制台,并且需要一个现有项目或拥有创建新项目的权限。如果您尚未安装 Appwrite,请先安装。按照 Appwrite 的官方安装文档进行安装非常简单,大约只需 2 分钟。安装完成后,登录您的控制台并创建一个新项目

💾 数据库设置

在 Appwrite 控制台中,我们选择要用于 Android 应用的项目。如果您还没有项目,可以点击“创建项目”按钮轻松创建一个。进入项目后,从左侧边栏选择“数据库”。在数据库页面:

  1. 点击“添加收藏”按钮
  2. 对话内容:
    1. 将集合名称设置为“产品”
    2. 点击“创建”按钮

这将创建一个集合,您将被重定向到新集合的页面,我们可以在该页面定义其规则。定义以下规则,然后单击“更新”按钮。

  • 姓名
    • 标签:名称
    • 键:名称
    • 规则类型:文本
    • 必填:是
    • 数组:false
  • 描述
    • 标签:描述
    • 图例:描述
    • 规则类型:文本
    • 必填:是
    • 数组:false
  • SKU
    • 标签:SKU
    • 图例:sku
    • 规则类型:文本
    • 必填:是
    • 数组:false
  • 价格
    • 标签:价格
    • 图例:价格
    • 规则类型:数值型
    • 必填:是
    • 数组:false
  • 图片网址
    • 标签:图片网址
    • 键:imageUrl
    • 规则类型:文本
    • 必填:是
    • 数组:false

产品系列

现在集合已经创建完成,我们可以开始设置 Android 应用程序了。

⚙️ 设置 Android 项目和依赖项

使用 Android Studio 创建一个新的 Android 应用程序项目,并选择“空 Activity”模板。项目创建完成后,将以下依赖项添加到应用程序的build.gradle(.kts)文件中:

    // 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'
Enter fullscreen mode Exit fullscreen mode

➕️ 添加 Android 平台

要初始化 Appwrite SDK 并开始与 Appwrite 服务交互,您首先需要向项目中添加一个新的 Android 平台。要添加新平台,请转到 Appwrite 控制台,选择您的项目(如果还没有项目,请创建一个),然后单击项目控制面板上的“添加平台”按钮。

从选项中选择添加新的 Android 平台并添加您的应用凭据。

添加您的应用名称和包名。包名通常位于applicationId应用级别的build.gradle文件中。您也可以在配置文件中找到包名AndroidManifest.xml。注册新平台后,您即允许您的应用与 Appwrite API 通信。

🧱 创建产品模型

现在我们需要创建一个模型来表示 Android 端的产品。创建一个新的 Kotlin 文件Product.kt并声明一个简单的数据类:

data class Product(
    val name: String,
    val sku: String,
    val price: Double,
    val imageUrl: String
)
Enter fullscreen mode Exit fullscreen mode

⚒️ 建筑景观

现在,打开您的文件app/src/main/res/layout/activity_main.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>
Enter fullscreen mode Exit fullscreen mode

你会注意到这个活动非常简单;我们只需要Button订阅一个应用和一个产品列表RecyclerView。产品列表RecyclerView将用于在我们添加新产品时实时显示产品集合。现在我们需要定义一个单独的视图来表示每个单独的产品。

从 创建一个新布局File > New > Layout Resource File,命名item_product.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>
Enter fullscreen mode Exit fullscreen mode

我们所有的部件RecyclerView都已到位,接下来我们将着手进行ViewModel大部分繁重的工作。

👩‍🔧 创建视图模型

使用以下代码创建app/src/main/java/com/example/realtimestarter/RealtimeViewModel.kt并更新该文件。请务必将文件顶部附近的所有属性值替换为您自己的值,这些值可以在 AppWrite 控制台中找到。

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()
    }
}
Enter fullscreen mode Exit fullscreen mode

ViewModel函数用于调用实时 API 并订阅与集合中 id 为 的文档相关的创建/更新/删除事件的通知collectionId,该集合对我们的用户也是可见的。

为了允许观察来自外部的实时更新,它还ViewModel公开了该LiveData属性productStream,我们Activity稍后将利用该属性在我们的系统中获取实时更新RecyclerView

♻️ 回收站视图

我们还需要添加两个文件才能使其正常RecyclerView运行:

  1. 该组件将负责为每个添加到数据库的元素ProductAdapter创建并绑定视图。构造函数中提供的参数将用于在后台线程中计算列表更新差异,然后将任何更改发布到 UI 线程,非常适合实时应用!您可以在这里找到更多信息ProductDiffUtil.ItemCallbackDiffUtil
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)
            }
        )
    }
}
Enter fullscreen mode Exit fullscreen mode
  1. ProductViewHolder视图描述了单个Product视图及其在视图中的位置的元数据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)
    }
}
Enter fullscreen mode Exit fullscreen mode

💆 活动

一切准备就绪后,让我们把所有内容整合到我们的项目中MainActivity。打开app/src/main/java/com/example/realtimestarter/MainActivity.kt并更新如下:

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)
    }
}


Enter fullscreen mode Exit fullscreen mode

🏎️ 让我们实时互动

好了,剩下的就是运行应用程序,然后添加一些文档。在模拟器或设备上运行时,点击“订阅”即可开始监听实时更新。

返回 Appwrite 控制台,导航至Products我们之前创建的集合。从这里,我们可以开始添加新文档,并在应用中查看它们的显示效果。

在控制台中添加产品后,您会立即在应用程序的用户界面中看到它们!这就是 Appwrite Realtime 的真正魅力所在,如下所示。

产品 GIF

🥂 结论

希望您喜欢使用 Appwrite 和 Android 构建这款实时应用程序。该应用程序的完整源代码可在Demo Realtime Application代码库中找到。如果您有任何反馈或建议,请告诉我们。期待看到社区使用 Appwrite Realtime 和 Android 能创造出怎样的作品!

🎓了解更多

文章来源:https://dev.to/appwrite/appwrite-android-and-realtime-42jd