Skip to content

Commit d25c351

Browse files
Stream quiz JSON parsing on background executor
1 parent 2226a32 commit d25c351

File tree

9 files changed

+99
-50
lines changed

9 files changed

+99
-50
lines changed
Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
package com.d4rk.androidtutorials.java.data.repository;
22

3-
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
43
import com.d4rk.androidtutorials.java.data.source.QuizLocalDataSource;
54

6-
import java.util.List;
7-
85
/**
96
* Default implementation of {@link QuizRepository} using a local data source.
107
*/
@@ -17,7 +14,7 @@ public DefaultQuizRepository(QuizLocalDataSource localDataSource) {
1714
}
1815

1916
@Override
20-
public List<QuizQuestion> loadQuestions() {
21-
return localDataSource.loadQuestions();
17+
public void loadQuestions(QuestionsCallback callback) {
18+
localDataSource.loadQuestions(callback::onResult);
2219
}
2320
}

app/src/main/java/com/d4rk/androidtutorials/java/data/repository/QuizRepository.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@
88
* Abstraction over quiz data operations.
99
*/
1010
public interface QuizRepository {
11-
List<QuizQuestion> loadQuestions();
11+
12+
interface QuestionsCallback {
13+
void onResult(List<QuizQuestion> questions);
14+
}
15+
16+
void loadQuestions(QuestionsCallback callback);
1217
}

app/src/main/java/com/d4rk/androidtutorials/java/data/source/DefaultQuizLocalDataSource.java

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,70 @@
44

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

7-
import org.json.JSONArray;
8-
import org.json.JSONException;
9-
import org.json.JSONObject;
7+
import android.util.JsonReader;
108

119
import java.io.IOException;
1210
import java.io.InputStream;
11+
import java.io.InputStreamReader;
1312
import java.nio.charset.StandardCharsets;
1413
import java.util.ArrayList;
1514
import java.util.Collections;
1615
import java.util.List;
16+
import java.util.concurrent.ExecutorService;
1717

1818
/**
1919
* Reads quiz questions from the assets folder.
2020
*/
2121
public class DefaultQuizLocalDataSource implements QuizLocalDataSource {
2222

2323
private final AssetManager assetManager;
24+
private final ExecutorService executorService;
2425

25-
public DefaultQuizLocalDataSource(AssetManager assetManager) {
26+
public DefaultQuizLocalDataSource(AssetManager assetManager, ExecutorService executorService) {
2627
this.assetManager = assetManager;
28+
this.executorService = executorService;
2729
}
2830

2931
@Override
30-
public List<QuizQuestion> loadQuestions() {
31-
try (InputStream is = assetManager.open("quiz_questions.json")) {
32-
byte[] buffer = new byte[is.available()];
33-
int read = is.read(buffer);
34-
String json = new String(buffer, 0, read, StandardCharsets.UTF_8);
35-
JSONArray array = new JSONArray(json);
32+
public void loadQuestions(QuestionsCallback callback) {
33+
executorService.execute(() -> {
3634
List<QuizQuestion> result = new ArrayList<>();
37-
for (int i = 0; i < array.length(); i++) {
38-
JSONObject obj = array.getJSONObject(i);
39-
String question = obj.getString("question");
40-
JSONArray opts = obj.getJSONArray("options");
41-
String[] options = new String[opts.length()];
42-
for (int j = 0; j < opts.length(); j++) {
43-
options[j] = opts.getString(j);
35+
try (InputStream is = assetManager.open("quiz_questions.json");
36+
JsonReader reader = new JsonReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
37+
reader.beginArray();
38+
while (reader.hasNext()) {
39+
reader.beginObject();
40+
String question = null;
41+
List<String> options = new ArrayList<>();
42+
int answer = -1;
43+
while (reader.hasNext()) {
44+
String name = reader.nextName();
45+
switch (name) {
46+
case "question" -> question = reader.nextString();
47+
case "options" -> {
48+
reader.beginArray();
49+
while (reader.hasNext()) {
50+
options.add(reader.nextString());
51+
}
52+
reader.endArray();
53+
}
54+
case "answer" -> answer = reader.nextInt();
55+
default -> reader.skipValue();
56+
}
57+
}
58+
reader.endObject();
59+
if (question != null && !options.isEmpty() && answer >= 0) {
60+
result.add(new QuizQuestion(
61+
question,
62+
options.toArray(new String[0]),
63+
answer));
64+
}
4465
}
45-
int answer = obj.getInt("answer");
46-
result.add(new QuizQuestion(question, options, answer));
66+
reader.endArray();
67+
} catch (IOException e) {
68+
result = Collections.emptyList();
4769
}
48-
return result;
49-
} catch (IOException | JSONException e) {
50-
return Collections.emptyList();
51-
}
70+
callback.onResult(result);
71+
});
5272
}
5373
}

app/src/main/java/com/d4rk/androidtutorials/java/data/source/QuizLocalDataSource.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,10 @@
88
* Contract for reading quiz data from local storage.
99
*/
1010
public interface QuizLocalDataSource {
11-
List<QuizQuestion> loadQuestions();
11+
12+
interface QuestionsCallback {
13+
void onResult(List<QuizQuestion> questions);
14+
}
15+
16+
void loadQuestions(QuestionsCallback callback);
1217
}

app/src/main/java/com/d4rk/androidtutorials/java/di/AppModule.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,9 +224,9 @@ public SetConsentAcceptedUseCase provideSetConsentAcceptedUseCase(SettingsReposi
224224

225225
@Provides
226226
@Singleton
227-
public QuizLocalDataSource provideQuizLocalDataSource(Application application) {
227+
public QuizLocalDataSource provideQuizLocalDataSource(Application application, ExecutorService executorService) {
228228
AssetManager manager = application.getAssets();
229-
return new DefaultQuizLocalDataSource(manager);
229+
return new DefaultQuizLocalDataSource(manager, executorService);
230230
}
231231

232232
@Provides

app/src/main/java/com/d4rk/androidtutorials/java/domain/quiz/LoadQuizQuestionsUseCase.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@
22

33
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
44
import com.d4rk.androidtutorials.java.data.repository.QuizRepository;
5+
56
import java.util.List;
67

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

12+
public interface Callback {
13+
void onResult(List<QuizQuestion> questions);
14+
}
15+
1116
public LoadQuizQuestionsUseCase(QuizRepository repository) {
1217
this.repository = repository;
1318
}
1419

15-
public List<QuizQuestion> invoke() {
16-
return repository.loadQuestions();
20+
public void invoke(Callback callback) {
21+
repository.loadQuestions(callback::onResult);
1722
}
1823
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/android/lessons/buttons/buttons/tabs/ButtonsTabLayoutFragment.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,10 @@
2020
import com.d4rk.androidtutorials.java.utils.FontManager;
2121
import com.google.android.gms.ads.AdRequest;
2222

23+
import java.io.BufferedReader;
2324
import java.io.IOException;
2425
import java.io.InputStream;
26+
import java.io.InputStreamReader;
2527
import java.nio.charset.StandardCharsets;
2628
import java.util.HashMap;
2729
import java.util.Map;
@@ -61,14 +63,15 @@ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
6163
for (Map.Entry<Integer, CodeView> entry : buttonXMLResources.entrySet()) {
6264
Integer resourceId = entry.getKey();
6365
CodeView codeView = entry.getValue();
64-
try (InputStream inputStream = getResources().openRawResource(resourceId)) {
65-
byte[] bytes = new byte[inputStream.available()];
66-
int result = inputStream.read(bytes);
67-
if (result != -1) {
68-
String text = new String(bytes, StandardCharsets.UTF_8);
69-
codeView.setText(text);
70-
CodeHighlighter.applyXmlTheme(codeView);
66+
try (InputStream inputStream = getResources().openRawResource(resourceId);
67+
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
68+
StringBuilder builder = new StringBuilder();
69+
String line;
70+
while ((line = reader.readLine()) != null) {
71+
builder.append(line).append('\n');
7172
}
73+
codeView.setText(builder.toString());
74+
CodeHighlighter.applyXmlTheme(codeView);
7275
} catch (IOException e) {
7376
Log.e("ButtonsTab", "Error reading button resource", e);
7477
}

app/src/main/java/com/d4rk/androidtutorials/java/ui/screens/quiz/QuizViewModel.java

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import com.d4rk.androidtutorials.java.data.model.QuizQuestion;
88
import com.d4rk.androidtutorials.java.domain.quiz.LoadQuizQuestionsUseCase;
99

10+
import java.util.Collections;
1011
import java.util.List;
1112

1213
import dagger.hilt.android.lifecycle.HiltViewModel;
@@ -18,21 +19,22 @@
1819
@HiltViewModel
1920
public class QuizViewModel extends ViewModel {
2021

21-
private final List<QuizQuestion> questions;
22+
private final MutableLiveData<List<QuizQuestion>> questions = new MutableLiveData<>(Collections.emptyList());
2223
private final MutableLiveData<Integer> currentIndex = new MutableLiveData<>(0);
2324
private final MutableLiveData<Integer> score = new MutableLiveData<>(0);
2425
private final LoadQuizQuestionsUseCase loadQuizQuestionsUseCase;
2526

2627
@Inject
2728
public QuizViewModel(LoadQuizQuestionsUseCase loadQuizQuestionsUseCase) {
2829
this.loadQuizQuestionsUseCase = loadQuizQuestionsUseCase;
29-
questions = loadQuizQuestionsUseCase.invoke();
30+
loadQuizQuestionsUseCase.invoke(result -> questions.postValue(result));
3031
}
3132

3233
public QuizQuestion getCurrentQuestion() {
33-
if (questions.isEmpty()) return null;
34+
List<QuizQuestion> list = questions.getValue();
35+
if (list == null || list.isEmpty()) return null;
3436
int index = currentIndex.getValue();
35-
return questions.get(Math.min(index, questions.size() - 1));
37+
return list.get(Math.min(index, list.size() - 1));
3638
}
3739

3840
public LiveData<Integer> getCurrentIndex() {
@@ -43,6 +45,10 @@ public LiveData<Integer> getScore() {
4345
return score;
4446
}
4547

48+
public LiveData<List<QuizQuestion>> getQuestions() {
49+
return questions;
50+
}
51+
4652
public void answer(int optionIndex) {
4753
QuizQuestion question = getCurrentQuestion();
4854
if (question != null && optionIndex == question.answerIndex()) {
@@ -52,6 +58,7 @@ public void answer(int optionIndex) {
5258
}
5359

5460
public int getTotalQuestions() {
55-
return questions.size();
61+
List<QuizQuestion> list = questions.getValue();
62+
return list != null ? list.size() : 0;
5663
}
5764
}

app/src/test/java/com/d4rk/androidtutorials/java/data/repository/DefaultQuizRepositoryTest.java

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import org.junit.Test;
77

88
import java.util.List;
9+
import java.util.concurrent.CountDownLatch;
10+
import java.util.concurrent.TimeUnit;
911

1012
import static org.junit.Assert.*;
1113

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

2123
@Override
22-
public List<QuizQuestion> loadQuestions() {
23-
return questions;
24+
public void loadQuestions(QuestionsCallback callback) {
25+
callback.onResult(questions);
2426
}
2527
}
2628

2729
@Test
28-
public void loadQuestionsReturnsLocalData() {
30+
public void loadQuestionsReturnsLocalData() throws InterruptedException {
2931
List<QuizQuestion> expected = List.of(
3032
new QuizQuestion("Q", new String[]{"A", "B"}, 0)
3133
);
3234
FakeQuizLocalDataSource local = new FakeQuizLocalDataSource(expected);
3335
DefaultQuizRepository repository = new DefaultQuizRepository(local);
34-
assertEquals(expected, repository.loadQuestions());
36+
CountDownLatch latch = new CountDownLatch(1);
37+
repository.loadQuestions(result -> {
38+
assertEquals(expected, result);
39+
latch.countDown();
40+
});
41+
assertTrue(latch.await(1, TimeUnit.SECONDS));
3542
}
3643
}

0 commit comments

Comments
 (0)