Skip to content

Commit 4dcb334

Browse files
Add quiz module with questions
1 parent 11111fb commit 4dcb334

File tree

8 files changed

+302
-0
lines changed

8 files changed

+302
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,11 @@
321321
android:exported="false"
322322
android:label="@string/navigation_drawer"
323323
android:parentActivityName=".ui.screens.android.lessons.navigation.drawer.NavigationDrawerActivity" />
324+
<activity
325+
android:name=".ui.screens.quiz.QuizActivity"
326+
android:exported="false"
327+
android:label="@string/quiz_title"
328+
android:parentActivityName=".ui.screens.quiz.QuizActivity" />
324329

325330
<service
326331
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[
2+
{
3+
"question": "What language is primarily used for Android app development?",
4+
"options": ["Python", "Java", "Swift", "Ruby"],
5+
"answer": 1
6+
},
7+
{
8+
"question": "Which file declares your app's activities?",
9+
"options": ["build.gradle", "AndroidManifest.xml", "strings.xml", "MainActivity.java"],
10+
"answer": 1
11+
}
12+
]
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.d4rk.androidtutorials.java.data.model;
2+
3+
/**
4+
* Model representing a multiple-choice quiz question.
5+
*/
6+
public record QuizQuestion(
7+
String question,
8+
String[] options,
9+
int answerIndex
10+
) {}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.quiz;
2+
3+
import android.os.Bundle;
4+
import android.view.MenuItem;
5+
6+
import androidx.annotation.NonNull;
7+
import androidx.appcompat.app.ActionBar;
8+
import androidx.appcompat.app.AppCompatActivity;
9+
import androidx.lifecycle.ViewModelProvider;
10+
11+
import com.d4rk.androidtutorials.java.R;
12+
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
13+
import com.d4rk.androidtutorials.java.databinding.ActivityQuizBinding;
14+
import com.d4rk.androidtutorials.java.utils.EdgeToEdgeDelegate;
15+
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
16+
17+
/**
18+
* Activity that displays a simple multiple-choice quiz.
19+
*/
20+
public class QuizActivity extends AppCompatActivity {
21+
22+
private ActivityQuizBinding binding;
23+
private QuizViewModel viewModel;
24+
25+
@Override
26+
protected void onCreate(Bundle savedInstanceState) {
27+
super.onCreate(savedInstanceState);
28+
binding = ActivityQuizBinding.inflate(getLayoutInflater());
29+
setContentView(binding.getRoot());
30+
31+
EdgeToEdgeDelegate edgeToEdgeDelegate = new EdgeToEdgeDelegate(this);
32+
edgeToEdgeDelegate.applyEdgeToEdge(binding.container);
33+
34+
ActionBar actionBar = getSupportActionBar();
35+
if (actionBar != null) {
36+
actionBar.setDisplayHomeAsUpEnabled(true);
37+
}
38+
39+
viewModel = new ViewModelProvider(this).get(QuizViewModel.class);
40+
showQuestion(viewModel.getCurrentQuestion());
41+
42+
binding.buttonNext.setOnClickListener(v -> onNextClicked());
43+
}
44+
45+
private void onNextClicked() {
46+
int selectedId = binding.optionsGroup.getCheckedRadioButtonId();
47+
int selectedIndex = -1;
48+
if (selectedId == binding.option1.getId()) {
49+
selectedIndex = 0;
50+
} else if (selectedId == binding.option2.getId()) {
51+
selectedIndex = 1;
52+
} else if (selectedId == binding.option3.getId()) {
53+
selectedIndex = 2;
54+
} else if (selectedId == binding.option4.getId()) {
55+
selectedIndex = 3;
56+
}
57+
if (selectedIndex != -1) {
58+
viewModel.answer(selectedIndex);
59+
}
60+
if (viewModel.getCurrentIndex().getValue() >= viewModel.getTotalQuestions()) {
61+
showResult();
62+
} else {
63+
showQuestion(viewModel.getCurrentQuestion());
64+
binding.optionsGroup.clearCheck();
65+
}
66+
}
67+
68+
private void showQuestion(QuizQuestion question) {
69+
if (question == null) {
70+
return;
71+
}
72+
binding.textQuestion.setText(question.question());
73+
binding.option1.setText(question.options()[0]);
74+
binding.option2.setText(question.options()[1]);
75+
binding.option3.setText(question.options()[2]);
76+
binding.option4.setText(question.options()[3]);
77+
}
78+
79+
private void showResult() {
80+
int score = viewModel.getScore().getValue();
81+
int total = viewModel.getTotalQuestions();
82+
new MaterialAlertDialogBuilder(this)
83+
.setMessage(getString(R.string.quiz_finished, score, total))
84+
.setPositiveButton(android.R.string.ok, (d, w) -> finish())
85+
.setCancelable(false)
86+
.show();
87+
}
88+
89+
@Override
90+
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
91+
if (item.getItemId() == android.R.id.home) {
92+
finish();
93+
return true;
94+
}
95+
return super.onOptionsItemSelected(item);
96+
}
97+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.quiz;
2+
3+
import android.app.Application;
4+
5+
import androidx.annotation.NonNull;
6+
import androidx.lifecycle.AndroidViewModel;
7+
import androidx.lifecycle.LiveData;
8+
import androidx.lifecycle.MutableLiveData;
9+
10+
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
11+
import com.d4rk.androidtutorials.java.ui.screens.quiz.repository.QuizRepository;
12+
13+
import java.util.List;
14+
15+
/**
16+
* ViewModel managing quiz state and scoring.
17+
*/
18+
public class QuizViewModel extends AndroidViewModel {
19+
20+
private final List<QuizQuestion> questions;
21+
private final MutableLiveData<Integer> currentIndex = new MutableLiveData<>(0);
22+
private final MutableLiveData<Integer> score = new MutableLiveData<>(0);
23+
24+
public QuizViewModel(@NonNull Application application) {
25+
super(application);
26+
QuizRepository repository = new QuizRepository(application);
27+
questions = repository.loadQuestions();
28+
}
29+
30+
public QuizQuestion getCurrentQuestion() {
31+
if (questions.isEmpty()) return null;
32+
int index = currentIndex.getValue();
33+
return questions.get(Math.min(index, questions.size() - 1));
34+
}
35+
36+
public LiveData<Integer> getCurrentIndex() {
37+
return currentIndex;
38+
}
39+
40+
public LiveData<Integer> getScore() {
41+
return score;
42+
}
43+
44+
public void answer(int optionIndex) {
45+
QuizQuestion question = getCurrentQuestion();
46+
if (question != null && optionIndex == question.answerIndex()) {
47+
score.setValue(score.getValue() + 1);
48+
}
49+
currentIndex.setValue(currentIndex.getValue() + 1);
50+
}
51+
52+
public int getTotalQuestions() {
53+
return questions.size();
54+
}
55+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.d4rk.androidtutorials.java.ui.screens.quiz.repository;
2+
3+
import android.content.Context;
4+
5+
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
6+
7+
import org.json.JSONArray;
8+
import org.json.JSONException;
9+
import org.json.JSONObject;
10+
11+
import java.io.IOException;
12+
import java.io.InputStream;
13+
import java.nio.charset.StandardCharsets;
14+
import java.util.ArrayList;
15+
import java.util.Collections;
16+
import java.util.List;
17+
18+
/**
19+
* Repository responsible for loading quiz question data from JSON assets.
20+
*/
21+
public class QuizRepository {
22+
23+
private final Context context;
24+
25+
public QuizRepository(Context context) {
26+
this.context = context.getApplicationContext();
27+
}
28+
29+
/**
30+
* Loads the quiz questions from the assets folder.
31+
*/
32+
public List<QuizQuestion> loadQuestions() {
33+
try {
34+
InputStream is = context.getAssets().open("quiz_questions.json");
35+
byte[] buffer = new byte[is.available()];
36+
int read = is.read(buffer);
37+
is.close();
38+
String json = new String(buffer, 0, read, StandardCharsets.UTF_8);
39+
JSONArray array = new JSONArray(json);
40+
List<QuizQuestion> result = new ArrayList<>();
41+
for (int i = 0; i < array.length(); i++) {
42+
JSONObject obj = array.getJSONObject(i);
43+
String question = obj.getString("question");
44+
JSONArray opts = obj.getJSONArray("options");
45+
String[] options = new String[opts.length()];
46+
for (int j = 0; j < opts.length(); j++) {
47+
options[j] = opts.getString(j);
48+
}
49+
int answer = obj.getInt("answer");
50+
result.add(new QuizQuestion(question, options, answer));
51+
}
52+
return result;
53+
} catch (IOException | JSONException e) {
54+
return Collections.emptyList();
55+
}
56+
}
57+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
3+
xmlns:app="http://schemas.android.com/apk/res-auto"
4+
android:id="@+id/container"
5+
android:layout_width="match_parent"
6+
android:layout_height="match_parent">
7+
8+
<TextView
9+
android:id="@+id/text_question"
10+
android:layout_width="0dp"
11+
android:layout_height="wrap_content"
12+
android:padding="16dp"
13+
android:textAppearance="@style/TextAppearance.Material3.TitleMedium"
14+
app:layout_constraintEnd_toEndOf="parent"
15+
app:layout_constraintStart_toStartOf="parent"
16+
app:layout_constraintTop_toTopOf="parent" />
17+
18+
<RadioGroup
19+
android:id="@+id/options_group"
20+
android:layout_width="0dp"
21+
android:layout_height="wrap_content"
22+
android:orientation="vertical"
23+
app:layout_constraintEnd_toEndOf="parent"
24+
app:layout_constraintStart_toStartOf="parent"
25+
app:layout_constraintTop_toBottomOf="@id/text_question">
26+
27+
<RadioButton
28+
android:id="@+id/option1"
29+
android:layout_width="wrap_content"
30+
android:layout_height="wrap_content"
31+
android:text="Option 1" />
32+
33+
<RadioButton
34+
android:id="@+id/option2"
35+
android:layout_width="wrap_content"
36+
android:layout_height="wrap_content"
37+
android:text="Option 2" />
38+
39+
<RadioButton
40+
android:id="@+id/option3"
41+
android:layout_width="wrap_content"
42+
android:layout_height="wrap_content"
43+
android:text="Option 3" />
44+
45+
<RadioButton
46+
android:id="@+id/option4"
47+
android:layout_width="wrap_content"
48+
android:layout_height="wrap_content"
49+
android:text="Option 4" />
50+
</RadioGroup>
51+
52+
<com.google.android.material.button.MaterialButton
53+
android:id="@+id/button_next"
54+
style="@style/Widget.Material3.Button"
55+
android:layout_width="wrap_content"
56+
android:layout_height="wrap_content"
57+
android:layout_marginTop="16dp"
58+
android:text="@string/next_question"
59+
app:layout_constraintBottom_toBottomOf="parent"
60+
app:layout_constraintEnd_toEndOf="parent"
61+
app:layout_constraintStart_toStartOf="parent"
62+
app:layout_constraintTop_toBottomOf="@id/options_group" />
63+
</androidx.constraintlayout.widget.ConstraintLayout>

app/src/main/res/values/strings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -435,5 +435,8 @@
435435
<string name="daily_tip_unit_tests">Write unit tests with JUnit and Espresso.</string>
436436
<string name="daily_tip_mvvm">Follow the MVVM architecture for maintainable code.</string>
437437
<string name="tip_of_the_day">Tip of the Day</string>
438+
<string name="quiz_title">Quiz</string>
439+
<string name="next_question">Next Question</string>
440+
<string name="quiz_finished">Quiz complete! Your score: %1$d/%2$d</string>
438441
<string name="other_apps_title">More apps by the developer</string>
439442
</resources>

0 commit comments

Comments
 (0)