Let's build an Android Quiz Application with Appwrite

🤔 What is Appwrite?

In case you’re new to Appwrite, Appwrite is an new open-source, end-to-end, backend server for front-end and mobile developers that allows you to build apps much faster. Its goal is to abstract and simplify common development tasks behind REST APIs and tools, helping you to build advanced apps faster.

🤖 Appwrite for Android

Appwrite provides sleek REST APIs for various common features that are required for mobile application development such as a database, storage, cloud functions, authentication etc. so that we as a developer can focus on our application rather than on backend implementation. This makes Appwrite very suitable for those of us who want to build Android applications. And with the latest release, Appwrite now has an official Android SDK to make it even easier to use Appwrite in our Android applications. In this tutorial we'll build a simple android quiz application using Appwrite. So let's get started.

📝 Prerequisites

In order to continue with this tutorial, you need to have an Appwrite console that you can access and have permission to create a project or already have an existing project. If you have not already installed Appwrite, please do so. Installing Appwrite is really simple following Appwrite's official installation docs. Installation should be done in less than 2 minutes. Once installed login to your console and create a new Project.

💾 Setup Database

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. Once on the database page click on the Add Collection button and inside the dialog set the collection name to Questions and then 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 rules for our collection. Define the following rules and click on the Update button.

  • Question
    • label: Question
    • Key: question
    • Rule Type: Text
    • Required: true
    • Array: false
  • Options
    • label: Options
    • Key: options
    • Rule Type: Text
    • Required: true
    • Array: true
  • Answer
    • label: Answer
    • Key: answer
    • Rule Type: Text
    • Required: true
    • Array: false

Now you can navigate to the Documents tab and using the Add Document button, add as many questions with options and answers as you like for your quiz application. Make sure to add a few questions so that we can easily fetch and build our quiz application in Android.

⚙️ Setup Android Project and Dependencies

Using Android Studio, create a new Android Application project choosing the Empty Activity template. Once the project is created, in your app's root level build.gradle(.kts) file, add the mavenCentral() repository in order to fetch the Appwrite Android SDK.

repositories {      
    mavenCentral()
}

Next, add the dependency to your app's build.gradle(.kts) file:

implementation("io.appwrite:sdk-for-android:0.0.1")

➕️ Add Android Platform

To initialize your SDK and start interacting with Appwrite services, you need to add a new Android platform to your project. To add a new platform, go to your Appwrite console, select your project (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.

⚒️ Build Activity and Layout

Add new activity by going to File>New>Activity>Empty Activity and name it QuizActivity. This will create QuizActivity.kt and activity_quiz.xml files in their respective folders.

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

<?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"
    android:background="@color/darkslategray"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="16dp"
        android:fontFamily="sans-serif-medium"
        android:text="Demo Quiz With Android"
        android:textColor="@color/darkgray"
        android:textSize="24sp"
        app:layout_constraintBottom_toTopOf="@+id/btnPlay"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

    <Button
        android:id="@+id/btnPlay"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Play Now"
        style="@style/buttonStyle"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Then, open app/src/main/java/com/example/demoquizwithandroid/MainActivity.kt and update the onCreate function like so:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val button = findViewById<Button>(R.id.btnPlay)
    button.setOnClickListener {
        val intent = Intent(this, QuizActivity::class.java)
        startActivity(intent)
    }
}

Now, open app/src/main/res/layout/activity_quiz.xml and update the layout to the following:

<?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"
    android:padding="24dp"
    android:background="@color/darkslategray"
    tools:context=".QuizActivity">

    <TextView
        android:id="@+id/questionNumber"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Question 5/15"
        android:textSize="32sp"
        android:textStyle="bold"
        android:textColor="@color/darkgray"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/dashedLine"
        android:layout_width="match_parent"
        android:layout_height="4dp"
        android:layout_marginTop="16dp"
        android:src="@drawable/dashed_line"
        app:layout_constraintTop_toBottomOf="@id/questionNumber"
        android:layerType="software" />

    <TextView
        android:id="@+id/question"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginTop="16dp"
        android:text="Question"
        android:textColor="@color/white"
        android:textSize="26sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toTopOf="@+id/options"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/dashedLine" />

    <RadioGroup
        android:id="@+id/options"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        app:layout_constraintBottom_toTopOf="@+id/btnNext"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent">

        <RadioButton
            android:id="@+id/option1"
            style="@style/optionRadioButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Option 1"
            tools:layout_editor_absoluteX="116dp"
            tools:layout_editor_absoluteY="75dp" />

        <RadioButton
            android:id="@+id/option2"
            style="@style/optionRadioButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Option 2" />

        <RadioButton
            android:id="@+id/option3"
            style="@style/optionRadioButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Option 3" />

        <RadioButton
            android:id="@+id/option4"
            style="@style/optionRadioButton"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="Option 4" />
    </RadioGroup>

    <Button
        android:id="@+id/btnNext"
        style="@style/buttonStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="8dp"
        android:text="Next"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

👩‍🔧 Create View Model

Create app/src/main/java/com/example/demoquizwithandroid/QuizViewModel.kt and update it with following code. Make sure to replace YOUR_APPWRITE_ENDPOINT, YOUR_PROJECT_ID and YOUR_QUESTIONS_COLLECTION_ID with your values which can all be found in your Appwrite console.

import android.content.Context
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import io.appwrite.Client
import io.appwrite.exceptions.AppwriteException
import io.appwrite.services.Database
import kotlinx.coroutines.launch
import org.json.JSONArray
import org.json.JSONObject

class QuizViewModel : ViewModel() {
    private val collectionId = "YOUR_QUESTIONS_COLLECTION_ID" // Replace with your own questions collection id that we created above
    private val db by lazy {
        Database(client)
    }
    private val _questions = MutableLiveData<JSONArray>().apply { value = null }
    lateinit var client : Client
    val questions: LiveData<JSONArray> = _questions;
    val selectedQuestion = MutableLiveData<Int>().apply { value = 0}
    val correct = MutableLiveData<Int>().apply { value = 0 }

    fun create(context: Context) {
        client = Client(context)
            .setEndpoint("YOUR_APPWRITE_ENDPOINT") // Replace with your own endpoint
            .setProject("YOUR_PROJECT_ID") // Replace with your project id
        getQuestions()
    }

    private fun getQuestions() {
        viewModelScope.launch {
            try {
                var response = db.listDocuments(collectionId)
                val json = response.body?.string() ?: ""
                var que = JSONObject(json)
                _questions.postValue( que["documents"] as JSONArray)
            } catch(e : AppwriteException) {
                Log.e("Get questions",e.message.toString())
            }
        }
    }
}

For this to work you also need to add following dependencies to your app/build.gradle file. These are the dependencies required for LiveData and ViewModel to work.

dependencies{
    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'
}

Finally update app/src/main/java/com/example/demoquizwithandroid/QuizActivity.kt with the following code and build your application

class QuizActivity : AppCompatActivity() {
    private lateinit var viewModel: QuizViewModel
    private var currentQuestion : JSONObject? = null;
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_quiz)
        viewModel = ViewModelProvider(this).get(QuizViewModel::class.java)
        viewModel.create(this)
        viewModel.questions.observe(this, {
            questions ->
            if(questions != null) {
                viewModel.selectedQuestion.observe(this, { index ->
                    findViewById<TextView>(R.id.questionNumber).text = "Question ${(index+1)}/${questions.length()}"
                    currentQuestion = questions[index] as JSONObject

                    val options = currentQuestion!!["options"] as JSONArray
                    findViewById<TextView>(R.id.question).text = currentQuestion!!["question"].toString()
                    findViewById<RadioButton>(R.id.option1).text = options[0].toString()
                    findViewById<RadioButton>(R.id.option2).text = options[1].toString()
                        val option3= findViewById<RadioButton>(R.id.option3)
                        val option4=findViewById<RadioButton>(R.id.option4)
                    if(options.length() > 2) {
                            option4.text = options[3].toString()
                            option3.text = options[2].toString()
                        option3.visibility = View.VISIBLE
                        option4.visibility = View.VISIBLE
                    } else {
                        option3.visibility = View.GONE
                        option4.visibility = View.GONE

                    }
                })
            }
        })
        findViewById<Button>(R.id.btnNext).setOnClickListener {
            _ -> onNextClicked()
        }
    }

    private fun onNextClicked() {
        if(currentQuestion == null) return
        val rbGroup : RadioGroup = findViewById<RadioGroup>(R.id.options)
        val rbSelected : RadioButton = findViewById(rbGroup.checkedRadioButtonId) ?: return
        val ans: String = rbSelected.text.toString();
        if (ans == currentQuestion!!["answer"].toString()) {
            viewModel.correct.postValue(viewModel.correct.value?.inc())
        }
        rbGroup.clearCheck()
        if(viewModel.selectedQuestion.value!! >= (viewModel.questions.value!!.length() - 1)) {
            //show complete dialog
            Log.d("Quiz complete","Quiz complete")
            val alertDialog = AlertDialog.Builder(this)
                .setTitle("Quiz complete")
                .setMessage("Score: ${viewModel.correct.value!!} / ${viewModel.questions.value!!.length()}")
                .setNeutralButton("Done", DialogInterface.OnClickListener { _, _ ->
                    val intent = Intent(this, MainActivity::class.java)
                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
                        .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                startActivity(intent)
            })
            alertDialog.show()
        } else {
            viewModel.selectedQuestion.postValue(viewModel.selectedQuestion.value?.inc())
        }
    }
}

We should now have our complete quiz application which should look as follows.

🥂 Conclusion

I hope you enjoyed building this Quiz Application with Appwrite and Android. Let's us know if you have any feedback and suggestions. Can't wait for the community to build awesome apps with Appwrite and Android.

🎓 Learn More

46