Getting started with Appwrite's Android SDK
Tags: android, tutorial, opensource, discuss
Published: 2021-07-09 07:51:07

One of the major highlights of Appwrite 0.9 is the official support for Android. We've also released a brand new Android SDK to go alongside it 😉!

In this tutorial, we'll learn to setup Appwrite's Android SDK, interact with Appwrite's Accounts API and also learn to setup OAuth Logins in your App! We'll extensively use [JetPack components](https://developer.android.com/jetpack) like Fragments, ViewModels, Data Binding, LiveData and Kotlin Coroutines! I hope you're as excited as I am! Let's get started!

## 📝 Prerequisites

At this stage, we assume that you already have an Appwrite instance up and running. If you do not have Appwrite setup yet, you can follow the super easy installation **step** over at [appwrite.io](https://appwrite.io/docs/installation). It's not a typo. There really is only 1 step!

You should have also setup an OAuth provider with Appwrite to be able to follow the OAuth section of this tutorial. You can learn to setup OAuth providers in Appwrite by going through [our tutorial](https://dev.to/appwrite/30daysofappwrite-oauth-providers-3jf6).

## 🛠️ Create a new Android Project

Setup your Android project and choose the **Empty Activity** Template. Give your project a name, Choose **Android 6.0 ( Marshmallow )** as your minimum SDK and click **Finish**.

![create_project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9zv8spzgx4e2d07kpun3.png)

Now is also a good time to add our Android App as a platform in the Appwrite Console. Head over to your `AndroidManifest.xml` and locate the package name of your app. It should look something like `com.example.myapplication`.

In your Appwrite dashboard, click on **Add Platform** and select a **New Android App**. Give your app a name, add the package name and click **Register**.

![add_platform](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0ujb83ae99ei6dfe4lk.png)

Once this is complete, it's time to head back to our Android project add our dependencies.

## 👷 Setup Appwrite's Android SDK

Appwrite's Android SDK is hosted on Maven Central, so you'll first need to add the `mavenCentral()` repository to your project's `build.gradle` file ( if it's not already present ).

```groovy
allprojects {
    repositories {
        google()
        mavenCentral()
    }
}
```

Then add the Appwrite Android SDK to your app's `build.gradle` file.

```groovy
dependencies {
    // ... Other dependencies
    implementation 'io.appwrite:sdk-for-android:0.0.1'
}
```

We'll also be using [Data Binding](https://developer.android.com/topic/libraries/data-binding) and we need to enable that in our app's `build.gradle` file

```groovy
android {
    // ...
    buildFeatures {
        dataBinding true
    }
}
```

Sync your gradle dependencies and if there are no errors, we're ready to proceed!

## ⌨️ Create helper classes

The first step is to initialise the `Client()` class from the Appwrite SDK. For this, we will create a new file `utils/Client.kt` where we will create a Singleton object to use across our App.

```kotlin
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")
    }
}
```

> If your Appwrite setup is running on localhost, you will need to make sure localhost is accessible from your emulator by using a proxy. Else, you can replace `localhost` with your private IP address which you can find using the command `hostname -I` on UNIX systems.

If you're using Appwrite on **localhost**, you need to set the `setSelfSigned(true)` flag to true.

```kotlin
client = Client(context)
            .setEndpoint("https://192.168.1.35/v1")
            .setProject("6070749e6acd4")
            .setSelfSigned(true)
```

Next, we're going to create another utility class called `utils/Event.kt`. This utility class will allow us to handle **LiveData Events** ( Changes that need to be observed only once ).

```kotlin
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
}
```

## 🏗️ Create the layouts

Now, we're going to update the layout in `activity_main.xml` to allow us to host fragments. Replace the contents of `activity_main.xml` with this snippet.

```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>
```

Now, create layout file for our Fragment at `layout/fragment_account.xml`

```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>

```

## 🔨 Create the ViewModel

Let's now create a ViewModel at `ui/accounts/AccountsViewModel.kt` to manage our app state.

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

}
```

Our ViewModel has 2 LiveData Objects: One to keep track of errors and one to keep track of the response.

We also have 4 functions

- onLogin - onClick handler for the Login Button
- onSignup - onClick handler for the Signup Button
- onLogout - onClick handler for the Logout Button
- onGetUser - onClick handler for the Get User Button

## 📝 Create the Fragment

Awesome! Let's move on to create our Fragment at `ui/accounts/AccountsFragment.kt`

```kotlin
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
    }
}
```

## 🔧 Update Main Activity

Finally, let's update our MainActivity.kt, that will initialise our `Client` singleton and add the `AccountsFragment` to the **FragmentContainerView**.

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

You should now be able to run your app and create users, login, logout and get information about the currently logged in user!

## 🔐 Adding OAuth Support

You would have noticed that we have a **Login With Facebook** button in our UI but it doesn't really do anything yet. So let's now add Facebook OAuth to our app!

The first step is to add a callback activity in the `AndroidManifest.xml` file.

```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>
```

Make sure you replace the Project ID in `<data android:scheme="appwrite-callback-[PROJECT-ID]" />` with your own.

Next, we'll add a function to our ViewModel to call the `createOAuth2Session()` method of the Appwrite SDK.

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

The success and failure URLs take the following form
`appwrite-callback-[PROJECT-ID]://[YOUR APPWRITE ENDPOINT]/auth/oauth2/[ success | failure ]`

Make sure you replace the Project ID and Endpoint with that of your own.

The last step is to invoke this function from `AccountsFragment.kt`

```kotlin
override fun onCreateView(
        inflater: LayoutInflater ,
        container: ViewGroup? ,
        savedInstanceState: Bundle?
    ): View? {

        // ... Existing Methods

        binding.oAuth.setOnClickListener{
            viewModel.oAuthLogin(activity as ComponentActivity)
        }
    }
```

Re-run your app and you should now be able to trigger your Facebook OAuth Flow! With that, you now know how to interact with Appwrite's Accounts API in your Android Apps!

We've built a complete app that interacts with all of Appwrite's APIs, which you can find over at our [Github Repo](https://github.com/appwrite/demo-getstarted-with-android). If you'd like to learn more about Appwrite or how Appwrite works under the hood, we've just curated all the resources for you during [30 Days of Appwrite](https://30days.appwrite.io/).

## ✨️ Credits

Hope you enjoyed this article! We love contributions and encourage you to take a look at our [open isuses](https://github.com/appwrite/appwrite/issues) and [ongoing RFCs](https://github.com/appwrite/rfc/pulls).

If you get stuck anywhere, feel free to reach out to us on our [friendly support channels](https://appwrite.io/discord) run by humans 👩‍💻.

Here are some handy links for more information:

- [Appwrite Contribution Guide](https://github.com/appwrite/appwrite/blob/master/CONTRIBUTING.md)
- [Appwrite Github](https://github.com/appwrite)
- [Appwrite Documentation](https://appwrite.io/docs)