Appwrite 0.9 的一大亮点是对 Android 的官方支持。我们还发布了一个全新的 Android SDK 来配合它😉!
在本教程中,我们将学习设置 Appwrite 的 Android SDK,与 Appwrite 的 Accounts API 交互,并学习在您的应用程序中设置 OAuth 登录!我们将广泛使用JetPack 组件,例如 Fragments、ViewModels、Data Binding、LiveData 和 Kotlin Coroutines!我希望你和我一样兴奋!让我们开始吧!
📝 先决条件
在这个阶段,我们假设您已经有一个 Appwrite 实例启动并运行。如果您还没有安装 Appwrite,您可以按照appwrite.io上的超级简单安装步骤进行操作。这不是一个错字。真的只有1步!
您还应该使用 Appwrite 设置 OAuth 提供程序,以便能够遵循本教程的 OAuth 部分。您可以通过阅读我们的教程来学习在 Appwrite 中设置 OAuth 提供程序。
🛠️ 创建一个新的 Android 项目
设置您的 Android 项目并选择Empty Activity Template。为您的项目命名,选择Android 6.0 (Marshmallow)作为您的最低 SDK,然后单击Finish。
现在也是将我们的 Android 应用程序添加为 Appwrite 控制台中的平台的好时机。转到您的AndroidManifest.xml并找到您的应用程序的包名称。它应该看起来像com.example.myapplication.
在您的 Appwrite 仪表板中,单击Add Platform并选择一个New Android App。为您的应用命名,添加包名称,然后单击注册。
完成后,是时候回到我们的 Android 项目添加我们的依赖项了。
👷 设置 Appwrite 的 Android SDK
Appwrite 的 Android SDK 托管在 Maven Central 上,因此您首先需要将mavenCentral()存储库添加到项目build.gradle文件中(如果它不存在的话)。
allprojects {
repositories {
google()
mavenCentral()
}
}
然后将 Appwrite Android SDK 添加到您的应用程序build.gradle文件中。
dependencies {
// ... Other dependencies
implementation 'io.appwrite:sdk-for-android:0.0.1'
}
我们还将使用数据绑定,我们需要在我们的应用程序build.gradle文件中启用它
android {
// ...
buildFeatures {
dataBinding true
}
}
同步您的 gradle 依赖项,如果没有错误,我们就可以继续了!
⌨️ 创建辅助类
第一步是Client()从 Appwrite SDK 初始化类。为此,我们将创建一个新文件utils/Client.kt,我们将在其中创建一个单例对象以在我们的应用程序中使用。
package com.example.myapplication.utils
import android.content.Context
import io.appwrite.Client
object Client {
lateinit var client : Client
fun create(context: Context) {
client = Client(context)
// Replace with your own endpoint and project ID
.setEndpoint("https://demo.appwrite.io/v1")
.setProject("6070749e6acd4")
}
}
如果您的 Appwrite 设置在 localhost 上运行,您需要确保 localhost 可以通过使用代理从您的模拟器访问。否则,您可以替换为您可以在 UNIX 系统上localhost使用命令找到的私有 IP 地址。hostname -I
如果您在localhost上使用 Appwrite ,则需要将该setSelfSigned(true)标志设置为 true。
client = Client(context)
.setEndpoint("https://192.168.1.35/v1")
.setProject("6070749e6acd4")
.setSelfSigned(true)
接下来,我们将创建另一个名为utils/Event.kt. 这个实用程序类将允许我们处理LiveData 事件(只需要观察一次的更改)。
package com.example.myapplication.utils
/**
* Used as a wrapper for data that is exposed via a LiveData that represents an event.
*/
open class Event<out T>(private val content: T) {
var hasBeenHandled = false
private set // Allow external read but not write
/**
* Returns the content and prevents its use again.
*/
fun getContentIfNotHandled(): T? {
return if (hasBeenHandled) {
null
} else {
hasBeenHandled = true
content
}
}
/**
* Returns the content, even if it's already been handled.
*/
fun peekContent(): T = content
}
🏗️ 创建布局
现在,我们将更新布局activity_main.xml以允许我们托管片段。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.fragment.app.FragmentContainerView
android:id="@+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
现在,为我们的片段创建布局文件layout/fragment_account.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<EditText
android:id="@+id/responseTV"
android:layout_width="match_parent"
android:layout_height="200dp"
android:background="@null"
android:enabled="true"
android:fadeScrollbars="false"
android:focusable="true"
android:longClickable="true"
android:scrollbars="vertical"
android:textIsSelectable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="Email"
android:inputType="textEmailAddress"
android:text="test@test.com"
app:layout_constraintStart_toStartOf="@id/responseTV"
app:layout_constraintTop_toBottomOf="@id/responseTV" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="password"
android:inputType="textPassword"
android:text="testtest"
app:layout_constraintStart_toStartOf="@id/email"
app:layout_constraintTop_toBottomOf="@id/email" />
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:hint="name"
android:inputType="text"
app:layout_constraintStart_toStartOf="@id/password"
app:layout_constraintTop_toBottomOf="@id/password" />
<Button
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Login"
app:layout_constraintEnd_toStartOf="@+id/signup"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/name" />
<Button
android:id="@+id/signup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Signup"
app:layout_constraintEnd_toStartOf="@id/getUser"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/login"
app:layout_constraintTop_toBottomOf="@+id/name" />
<Button
android:id="@+id/getUser"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Get User"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/signup"
app:layout_constraintTop_toBottomOf="@+id/name" />
<Button
android:id="@+id/oAuth"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="Login with Facebook"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@+id/logout"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/signup" />
<Button
android:id="@+id/logout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Logout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/oAuth"
app:layout_constraintTop_toTopOf="@id/oAuth" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>
🔨 创建 ViewModel
现在让我们创建一个 ViewModelui/accounts/AccountsViewModel.kt来管理我们的应用程序状态。
package com.example.myapplication.ui.accounts
import android.text.Editable
import androidx.activity.ComponentActivity
import androidx.lifecycle.*
import com.example.myapplication.utils.Client.client
import com.example.myapplication.utils.Event
import io.appwrite.exceptions.AppwriteException
import io.appwrite.services.Account
import kotlinx.coroutines.launch
import org.json.JSONObject
class AccountsViewModel : ViewModel() {
private val _error = MutableLiveData<Event<Exception>>().apply {
value = null
}
val error: LiveData<Event<Exception>> = _error
private val _response = MutableLiveData<Event<String>>().apply {
value = null
}
val response: LiveData<Event<String>> = _response
private val accountService by lazy {
Account(client)
}
fun onLogin(email: Editable , password : Editable) {
viewModelScope.launch {
try {
var response = accountService.createSession(email.toString(), password.toString())
var json = response.body?.string() ?: ""
json = JSONObject(json).toString(8)
_response.postValue(Event(json))
} catch (e: AppwriteException) {
_error.postValue(Event(e))
}
}
}
fun onSignup(email: Editable , password : Editable, name: Editable) {
viewModelScope.launch {
try {
var response = accountService.create(email.toString(), password.toString(), name.toString())
var json = response.body?.string() ?: ""
json = JSONObject(json).toString(2)
_response.postValue(Event(json))
} catch (e: AppwriteException) {
_error.postValue(Event(e))
}
}
}
fun onGetUser() {
viewModelScope.launch {
try {
var response = accountService.get()
var json = response.body?.string() ?: ""
json = JSONObject(json).toString(2)
_response.postValue(Event(json))
} catch (e: AppwriteException) {
_error.postValue(Event(e))
}
}
}
fun onLogout() {
viewModelScope.launch {
try {
var response = accountService.deleteSession("current")
var json = response.body?.string()?.ifEmpty { "{}" }
json = JSONObject(json).toString(4)
_response.postValue(Event(json))
} catch (e: AppwriteException) {
_error.postValue(Event(e))
}
}
}
}
我们的 ViewModel 有 2 个 LiveData 对象:一个用于跟踪错误,一个用于跟踪响应。
我们还有4个功能
- onLogin - 登录按钮的 onClick 处理程序
- onSignup - 注册按钮的 onClick 处理程序
- onLogout - 注销按钮的 onClick 处理程序
- onGetUser - 获取用户按钮的 onClick 处理程序
📝 创建片段
惊人的!让我们继续创建我们的片段ui/accounts/AccountsFragment.kt
package com.example.myapplication.ui.accounts
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.databinding.DataBindingUtil
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.R
import com.example.myapplication.databinding.FragmentAccountBinding
class AccountsFragment : Fragment() {
private lateinit var binding: FragmentAccountBinding
private lateinit var viewModel: AccountsViewModel
override fun onCreateView(
inflater: LayoutInflater ,
container: ViewGroup? ,
savedInstanceState: Bundle?
): View {
viewModel = ViewModelProvider(this).get(AccountsViewModel::class.java)
binding = DataBindingUtil.inflate(
inflater,
R.layout.fragment_account,
container,
false
)
binding.lifecycleOwner = viewLifecycleOwner
binding.login.setOnClickListener{
viewModel.onLogin(binding.email.text, binding.password.text)
}
binding.signup.setOnClickListener{
viewModel.onSignup(binding.email.text, binding.password.text, binding.name.text)
}
binding.getUser.setOnClickListener{
viewModel.onGetUser()
}
binding.logout.setOnClickListener{
viewModel.onLogout()
}
viewModel.error.observe(viewLifecycleOwner, Observer { event ->
event?.getContentIfNotHandled()?.let { // Only proceed if the event has never been handled
Toast.makeText(requireContext(), it.message , Toast.LENGTH_SHORT).show()
}
})
viewModel.response.observe(viewLifecycleOwner, Observer { event ->
event?.getContentIfNotHandled()?.let {
binding.responseTV.setText(it)
}
})
return binding.root
}
}
🔧 更新主活动
最后,让我们更新我们的 MainActivity.kt,它将初始化我们的Client单例并将其添加AccountsFragment到FragmentContainerView。
package com.example.myapplication
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.add
import androidx.fragment.app.commit
import com.example.myapplication.utils.Client
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Client.create(applicationContext)
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<AccountsFragment>(R.id.fragment_container_view)
}
}
}
}
您现在应该能够运行您的应用程序并创建用户、登录、注销并获取有关当前登录用户的信息!
🔐 添加 OAuth 支持
您会注意到我们的 UI 中有一个Login With Facebook按钮,但它实际上并没有做任何事情。现在让我们将 Facebook OAuth 添加到我们的应用程序中!
第一步是在AndroidManifest.xml文件中添加回调活动。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<application
<!-- Other Activities -->
<activity android:name="io.appwrite.views.CallbackActivity" >
<intent-filter android:label="android_web_auth">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="appwrite-callback-6070749e6acd4" />
</intent-filter>
</activity>
</application>
</manifest>
确保将项目 ID 替换为您自己的。
接下来,我们将向 ViewModel 添加一个函数来调用createOAuth2Session()Appwrite SDK 的方法。
fun oAuthLogin(activity: ComponentActivity) {
viewModelScope.launch {
try {
accountService.createOAuth2Session(activity, "facebook", "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/success", "appwrite-callback-6070749e6acd4://demo.appwrite.io/auth/oauth2/failure")
} catch (e: Exception) {
_error.postValue(Event(e))
} catch (e: AppwriteException) {
_error.postValue(Event(e))
}
}
}
成功和失败 URL 采用以下形式
appwrite-callback-[PROJECT-ID]://[YOUR APPWRITE ENDPOINT]/auth/oauth2/[ success | failure ]
确保将项目 ID 和端点替换为您自己的。
最后一步是调用这个函数AccountsFragment.kt
override fun onCreateView(
inflater: LayoutInflater ,
container: ViewGroup? ,
savedInstanceState: Bundle?
): View? {
// ... Existing Methods
binding.oAuth.setOnClickListener{
viewModel.oAuthLogin(activity as ComponentActivity)
}
}
重新运行您的应用程序,您现在应该能够触发您的 Facebook OAuth Flow!有了它,您现在知道如何在您的 Android 应用程序中与 Appwrite 的 Accounts API 进行交互!
**一个零基础的新人,我认为坚持是最最重要的。**我的很多朋友都找我来学习过,我也很用心的教他们,可是不到一个月就坚持不下来了。我认为他们坚持不下来有两点主要原因:
他们打算入行不是因为兴趣,而是因为所谓的IT行业工资高,或者说完全对未来没有任何规划。
刚开始学的时候确实很枯燥,这确实对你是个考验,所以说坚持下来也很不容易,但是如果你有兴趣就不会认为这是累,不会认为这很枯燥,总之还是贵在坚持。
技术提升遇到瓶颈了?缺高级Android进阶视频学习提升自己吗?还有大量大厂面试题为你面试做准备!
文末可以免费领取安卓资料
对于很多Android工程师而言,想要提升技能,往往是自己摸索成长,不成体系的学习效果低效漫长且无助。整理的这些知识图谱希望对Android开发的朋友们有所参考以及少走弯路,本文的重点是你有没有收获与成长,其余的都不重要,希望读者们能谨记这一点。
不论遇到什么困难,都不应该成为我们放弃的理由!
如果有什么疑问的可以直接私我,我尽自己最大力量帮助你!
最后祝各位新人都能坚持下来,学有所成,如有需要可以点击下方微信卡片即可免费领取。