Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package com.d4rk.androidtutorials.java.data.repository;

import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
import com.d4rk.androidtutorials.java.data.source.QuizLocalDataSource;

import java.util.List;

/**
* Default implementation of {@link QuizRepository} using a local data source.
*/
Expand All @@ -17,7 +14,7 @@ public DefaultQuizRepository(QuizLocalDataSource localDataSource) {
}

@Override
public List<QuizQuestion> loadQuestions() {
return localDataSource.loadQuestions();
public void loadQuestions(QuestionsCallback callback) {
localDataSource.loadQuestions(callback::onResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
* Abstraction over quiz data operations.
*/
public interface QuizRepository {
List<QuizQuestion> loadQuestions();

interface QuestionsCallback {
void onResult(List<QuizQuestion> questions);
}

void loadQuestions(QuestionsCallback callback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,70 @@

import com.d4rk.androidtutorials.java.data.model.QuizQuestion;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.util.JsonReader;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;

/**
* Reads quiz questions from the assets folder.
*/
public class DefaultQuizLocalDataSource implements QuizLocalDataSource {

private final AssetManager assetManager;
private final ExecutorService executorService;

public DefaultQuizLocalDataSource(AssetManager assetManager) {
public DefaultQuizLocalDataSource(AssetManager assetManager, ExecutorService executorService) {
this.assetManager = assetManager;
this.executorService = executorService;
}

@Override
public List<QuizQuestion> loadQuestions() {
try (InputStream is = assetManager.open("quiz_questions.json")) {
byte[] buffer = new byte[is.available()];
int read = is.read(buffer);
String json = new String(buffer, 0, read, StandardCharsets.UTF_8);
JSONArray array = new JSONArray(json);
public void loadQuestions(QuestionsCallback callback) {
executorService.execute(() -> {
List<QuizQuestion> result = new ArrayList<>();
for (int i = 0; i < array.length(); i++) {
JSONObject obj = array.getJSONObject(i);
String question = obj.getString("question");
JSONArray opts = obj.getJSONArray("options");
String[] options = new String[opts.length()];
for (int j = 0; j < opts.length(); j++) {
options[j] = opts.getString(j);
try (InputStream is = assetManager.open("quiz_questions.json");
JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
reader.beginArray();
while (reader.hasNext()) {
reader.beginObject();
String question = null;
List<String> options = new ArrayList<>();
int answer = -1;
while (reader.hasNext()) {
String name = reader.nextName();
switch (name) {
case "question" -> question = reader.nextString();
case "options" -> {
reader.beginArray();
while (reader.hasNext()) {
options.add(reader.nextString());
}
reader.endArray();
}
case "answer" -> answer = reader.nextInt();
default -> reader.skipValue();
}
}
reader.endObject();
if (question != null && !options.isEmpty() && answer >= 0) {
result.add(new QuizQuestion(
question,
options.toArray(new String[0]),
answer));
}
}
int answer = obj.getInt("answer");
result.add(new QuizQuestion(question, options, answer));
reader.endArray();
} catch (IOException e) {
result = Collections.emptyList();
}
return result;
} catch (IOException | JSONException e) {
return Collections.emptyList();
}
callback.onResult(result);
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,10 @@
* Contract for reading quiz data from local storage.
*/
public interface QuizLocalDataSource {
List<QuizQuestion> loadQuestions();

interface QuestionsCallback {
void onResult(List<QuizQuestion> questions);
}

void loadQuestions(QuestionsCallback callback);
}
Original file line number Diff line number Diff line change
Expand Up @@ -224,9 +224,9 @@ public SetConsentAcceptedUseCase provideSetConsentAcceptedUseCase(SettingsReposi

@Provides
@Singleton
public QuizLocalDataSource provideQuizLocalDataSource(Application application) {
public QuizLocalDataSource provideQuizLocalDataSource(Application application, ExecutorService executorService) {
AssetManager manager = application.getAssets();
return new DefaultQuizLocalDataSource(manager);
return new DefaultQuizLocalDataSource(manager, executorService);
}

@Provides
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,22 @@

import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
import com.d4rk.androidtutorials.java.data.repository.QuizRepository;

import java.util.List;

/** Loads quiz questions from assets. */
public class LoadQuizQuestionsUseCase {
private final QuizRepository repository;

public interface Callback {
void onResult(List<QuizQuestion> questions);
}

public LoadQuizQuestionsUseCase(QuizRepository repository) {
this.repository = repository;
}

public List<QuizQuestion> invoke() {
return repository.loadQuestions();
public void invoke(Callback callback) {
repository.loadQuestions(callback::onResult);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,10 @@
import com.d4rk.androidtutorials.java.utils.FontManager;
import com.google.android.gms.ads.AdRequest;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -61,14 +63,15 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
for (Map.Entry<Integer, CodeView> entry : buttonXMLResources.entrySet()) {
Integer resourceId = entry.getKey();
CodeView codeView = entry.getValue();
try (InputStream inputStream = getResources().openRawResource(resourceId)) {
byte[] bytes = new byte[inputStream.available()];
int result = inputStream.read(bytes);
if (result != -1) {
String text = new String(bytes, StandardCharsets.UTF_8);
codeView.setText(text);
CodeHighlighter.applyXmlTheme(codeView);
try (InputStream inputStream = getResources().openRawResource(resourceId);
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line).append('\n');
}
codeView.setText(builder.toString());
CodeHighlighter.applyXmlTheme(codeView);
} catch (IOException e) {
Log.e("ButtonsTab", "Error reading button resource", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
import com.d4rk.androidtutorials.java.domain.quiz.LoadQuizQuestionsUseCase;

import java.util.Collections;
import java.util.List;

import dagger.hilt.android.lifecycle.HiltViewModel;
Expand All @@ -18,21 +19,22 @@
@HiltViewModel
public class QuizViewModel extends ViewModel {

private final List<QuizQuestion> questions;
private final MutableLiveData<List<QuizQuestion>> questions = new MutableLiveData<>(Collections.emptyList());
private final MutableLiveData<Integer> currentIndex = new MutableLiveData<>(0);
private final MutableLiveData<Integer> score = new MutableLiveData<>(0);
private final LoadQuizQuestionsUseCase loadQuizQuestionsUseCase;

@Inject
public QuizViewModel(LoadQuizQuestionsUseCase loadQuizQuestionsUseCase) {
this.loadQuizQuestionsUseCase = loadQuizQuestionsUseCase;
questions = loadQuizQuestionsUseCase.invoke();
loadQuizQuestionsUseCase.invoke(result -> questions.postValue(result));
}

public QuizQuestion getCurrentQuestion() {
if (questions.isEmpty()) return null;
List<QuizQuestion> list = questions.getValue();
if (list == null || list.isEmpty()) return null;
int index = currentIndex.getValue();
return questions.get(Math.min(index, questions.size() - 1));
return list.get(Math.min(index, list.size() - 1));
}

public LiveData<Integer> getCurrentIndex() {
Expand All @@ -43,6 +45,10 @@ public LiveData<Integer> getScore() {
return score;
}

public LiveData<List<QuizQuestion>> getQuestions() {
return questions;
}

public void answer(int optionIndex) {
QuizQuestion question = getCurrentQuestion();
if (question != null && optionIndex == question.answerIndex()) {
Expand All @@ -52,6 +58,7 @@ public void answer(int optionIndex) {
}

public int getTotalQuestions() {
return questions.size();
List<QuizQuestion> list = questions.getValue();
return list != null ? list.size() : 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import org.junit.Test;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.*;

Expand All @@ -19,18 +21,23 @@ private static class FakeQuizLocalDataSource implements QuizLocalDataSource {
}

@Override
public List<QuizQuestion> loadQuestions() {
return questions;
public void loadQuestions(QuestionsCallback callback) {
callback.onResult(questions);
}
}

@Test
public void loadQuestionsReturnsLocalData() {
public void loadQuestionsReturnsLocalData() throws InterruptedException {
List<QuizQuestion> expected = List.of(
new QuizQuestion("Q", new String[]{"A", "B"}, 0)
);
FakeQuizLocalDataSource local = new FakeQuizLocalDataSource(expected);
DefaultQuizRepository repository = new DefaultQuizRepository(local);
assertEquals(expected, repository.loadQuestions());
CountDownLatch latch = new CountDownLatch(1);
repository.loadQuestions(result -> {
assertEquals(expected, result);
latch.countDown();
});
assertTrue(latch.await(1, TimeUnit.SECONDS));
}
}