46
Let's build an Android Quiz Application with 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 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.
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.
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.
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")
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.
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 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.
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.
46